Compare commits

..

925 Commits

Author SHA1 Message Date
Nuno Diegues d6036d96f0 Release 2022.1.0 2022-01-06 11:42:49 +00:00
Nuno Diegues a6faa0c376 TUN-5600: Add coverage to component tests for various transports
This parameterizes relevant component tests by transport protocol
where applicable.
The motivation is to have coverage for (graceful or not) shutdown
that was broken in QUIC. That logic (as well as reconnect) is
different depending on the transport, so we should have it
parameterized. In fact, the test is failing for QUIC (and passing
for others) right now, which is expected until we roll out some
edge fixes for QUIC. So we could have caught this earlier on.
2022-01-06 10:09:17 +00:00
Nuno Diegues 1086d5ede5 TUN-5204: Unregister QUIC transports on disconnect
This adds various bug fixes when investigating why QUIC transports were
not being unregistered when they should (and only when the graceful shutdown
started).

Most of these bug fixes are making the QUIC transport implementation closer
to its HTTP2 counterpart:
 - ServeControlStream is now a blocking function (it's up to the transport to handle that)
 - QUIC transport then handles the control plane as part of its Serve, thus waiting for it on shutdown
 - QUIC transport now returns "non recoverable" for connections with similar semantics to HTTP2 and H2mux
 - QUIC transport no longer has a loop around its Serve logic that retries connections on its own (that logic is upstream)
2022-01-06 10:08:38 +00:00
Nuno Diegues c314d58b69 TUN-5616: Never fallback transport if user chooses it on purpose 2022-01-06 09:20:59 +00:00
Nuno Diegues 628545d229 TUN-5600: Close QUIC transports as soon as possible while respecting graceful shutdown
This does a few fixes to make sure that the QUICConnection returns from
Serve when the context is cancelled.

QUIC transport now behaves like other transports: closes as soon as there
is no traffic, or at most by grace-period. Note that we do not wait for
UDP traffic since that's connectionless by design.
2022-01-06 08:59:53 +00:00
Nuno Diegues ead93e9f26 TUN-5551: Internally published debian artifacts are now named just cloudflared even though they are FIPS compliant
This way we will force the adoption of FIPS compliant cloudflared without having
to handle the transition for systems that already have it installed (since we
were previously using new artifacts with fips suffix)  nor without having to
segregate the resulting binary name (since we were always generating a binary
just called cloudflared from the unpacked debian archive to avoid having to change
any automation that assumes the binary to be called just that).
2022-01-05 08:24:58 +00:00
João Oliveirinha 5f380f3a54 TUN-5612: Make tls min/max version public visible 2022-01-03 18:13:57 +00:00
João Oliveirinha 7814e870a7 TUN-5612: Add support for specifying TLS min/max version 2022-01-03 16:20:20 +00:00
Nuno Diegues 7c7cf688e6 Release 2021.12.4 2021-12-29 12:14:38 +00:00
Nuno Diegues a39d95d5f7 TUN-5551: Show whether the binary was built for FIPS compliance
This is shown in 3 ways:
 - the version output with `cloudflared version` and alike commands
 - the build_info prometheus metric
 - a logging message
2021-12-28 19:03:16 +00:00
Nuno Diegues 01ad2785ee TUN-5551: Change internally published debian package to be FIPS compliant
This changes existing Makefile targets to make it obvious that they are
used to publish debian packages for internal Cloudflare usage. Those are
now FIPS compliant, with no alternative provided. This only affects amd64
builds (and we only publish internally for Linux).

This new Makefile target is used by all internal builds (including nightly
that is used for e2e tests).

Note that this Makefile target renames the artifact to be just `cloudflared`
so that this is used "as is" internally, without expecting people to opt-in
to the new `cloudflared-fips` package (as we are giving them no alternative).
2021-12-28 19:01:03 +00:00
Nuno Diegues 6822e4f8ab TUN-5482: Refactor tunnelstore client related packages for more coherent package 2021-12-28 17:17:49 +00:00
cthuang 834c0eaeed Release 2021.12.3 2021-12-23 15:42:15 +00:00
cthuang 74a3026963 TUN-5597: Log session ID when session is terminated by edge 2021-12-23 11:43:23 +00:00
Igor Postelnik 8445b88d3c TUN-5593: Read full packet from UDP connection, even if it exceeds MTU of the transport. When packet length is greater than the MTU of the transport, we will silently drop packets (for now). 2021-12-22 17:18:22 -06:00
cthuang 7a55208c61 TUN-5590: QUIC datagram max user payload is 1217 bytes 2021-12-22 19:53:58 +00:00
Nuno Diegues 581cfb8480 TUN-5584: Changes for release 2021.12.2 2021-12-22 08:58:11 +00:00
Nuno Diegues 201c462902 Release 2021.12.2 2021-12-22 08:53:38 +00:00
cthuang ebae7a7024 TUN-5494: Send a RPC with terminate reason to edge if the session is closed locally 2021-12-21 09:52:39 +00:00
Nuno Diegues 70e675f42c TUN-5551: Reintroduce FIPS compliance for linux amd64 now as separate binaries
This is a cherry-pick of 157f5d1412
followed by build/CI changes so that amd64/linux FIPS compliance is
provided by new/separate binaries/artifacts/packages.

The reasoning being that FIPS compliance places excessive requirements
in the encryption algorithms used for regular users that do not care
about that. This can cause cloudflared to reject HTTPS origins that
would otherwise be accepted without FIPS checks.

This way, by having separate binaries, existing ones remain as they
were, and only FIPS-needy users will opt-in to the new FIPS binaries.
2021-12-20 21:50:42 +00:00
cthuang 8f46065ab5 TUN-5570: Only log RPC server events at error level to reduce noise 2021-12-20 16:50:24 +00:00
cthuang 41b9c22216 TUN-5571: Remove redundant session manager log, it's already logged in origin/tunnel.ServeQUIC 2021-12-20 12:00:55 +00:00
Nuno Diegues 88ce63e785 Release 2021.12.1 2021-12-16 00:50:28 +00:00
Nuno Diegues 2dc5f6ec8c TUN-5549: Revert "TUN-5277: Ensure cloudflared binary is FIPS compliant on linux amd64"
This reverts commit 157f5d1412.

FIPS compliant binaries (for linux/amd64) are causing HTTPS origins to not
be reachable by cloudflared in certain cases (e.g. with Let's Encrypt certificates).

Origins that are not HTTPS for cloudflared are not affected.
2021-12-16 00:29:01 +00:00
Nuno Diegues 8d41f99f2f Release 2021.12.0 2021-12-15 10:01:29 +00:00
Nuno Diegues 173190aa79 TUN-5544: Update CHANGES.md for next release 2021-12-15 09:15:53 +00:00
cthuang 9251b3aa1f TUN-5530: Get current time from ticker 2021-12-13 13:35:40 +00:00
Nuno Diegues b0e27d1eac TUN-5504: Fix upload of packages to public repo 2021-12-09 13:06:24 +00:00
cthuang 73a265f2fc TUN-5488: Close session after it's idle for a period defined by registerUdpSession RPC 2021-12-09 11:55:34 +00:00
Nuno Diegues 9bc59bc78c TUN-5519: Adjust URL for virtual_networks endpoint to match what we will publish 2021-12-07 13:06:46 +00:00
cthuang b73c588254 TUN-5422: Define RPC to unregister session 2021-12-06 16:37:09 +00:00
João Oliveirinha 7e47667b08 TUN-5481: Create abstraction for Origin UDP Connection
Creates an abstraction over UDP Conn for origin "connection" which can
be useful for future support of complex protocols that may require
changing ports during protocol negotiation (eg. SIP, TFTP)
In addition, it removes a dependency from ingress on connection package.
2021-12-06 16:37:09 +00:00
cthuang eea3d11e40 TUN-5301: Separate datagram multiplex and session management logic from quic connection logic 2021-12-06 16:37:09 +00:00
cthuang dd32dc1364 TUN-5299: Send/receive QUIC datagram from edge and proxy to origin as UDP 2021-12-06 16:37:09 +00:00
cthuang fc2333c934 TUN-5300: Define RPC to register UDP sessions 2021-12-06 16:37:09 +00:00
Nuno Diegues 571380b3f5 TUN-5362: Adjust route ip commands to be aware of virtual networks 2021-12-03 09:10:20 +00:00
Nuno Diegues eec6b87eea TUN-5361: Commands for managing virtual networks 2021-12-03 08:48:52 +00:00
Nuno Diegues 6cc7d99e32 TUN-5441: Fix message about available protocols 2021-11-18 14:30:22 +00:00
Nuno Diegues 59bbd51065 TUN-5129: Remove -dev suffix when computing version and Git has uncommitted changes
When building the docker image, this `-dev` suffix is being added to the
cloudflared binary version.
The reason must be that there's some file, which is tracked by git, and
that is modified during that build.

It's not clear which file is it. But, at the same time, it's not clear what
advantage this `-dev` suffix is bringing. So we're simply removing it so that
we generate proper versions (so that our tracking/observability can correctly
aggregate these values).
2021-11-17 12:28:05 +00:00
Nuno Diegues e35f744b36 TUN-5393: Content-length is no longer a control header for non-h2mux transports
- Refactors some h2mux specific logic from connection/header.go to connection/h2mux_header.go
 - Do the same for the unit tests
 - Add a non-h2mux "is control response header" function (we don't need one for the request flow)
 - In that new function, do not consider "content-length" as a control header
 - Use that function in the non-h2mux flow for response (and it will be used also in origintunneld)
2021-11-17 10:56:15 +00:00
Silver a96d4243ba Merge pull request #508 from bplessis/master
Fix for Issue #501: Unexpected User-agent insertion when tunneling http request
2021-11-15 10:08:56 -06:00
Silver d4733efb25 Merge pull request #507 from rex4539/typos
Fix typos
2021-11-15 10:04:13 -06:00
Benoit Plessis b88e0bc8f8 Fix for Issue #501: Unexpected User-agent insertion when tunneling http request
When forwarding an UA-less request to the origin server cloudflared insert the default golang http User-Agent, this is unexpected and can lead to issue.

This simple fix force setting the UA to the empty string when it isn't originaly provided.
2021-11-13 01:34:19 +01:00
Dimitris Apostolou 197a70c9c4 Fix typos 2021-11-12 17:38:06 +02:00
cthuang e71b88fcaa TUN-5408: Update quic package to v0.24.0 2021-11-10 22:10:38 +00:00
Nuno Diegues 157f5d1412 TUN-5277: Ensure cloudflared binary is FIPS compliant on linux amd64 2021-11-10 21:27:25 +00:00
cthuang 7024d193c9 TUN-5405: Update net package to v0.0.0-20211109214657-ef0fda0de508
This version contains fix to https://github.com/golang/go/issues/43989
2021-11-10 17:20:13 +00:00
Nuno Diegues 794635fb54 TUN-5397: Log cloudflared output when it fails to connect tunnel 2021-11-10 10:34:34 +00:00
Nuno Diegues 1ee540a166 TUN-5368: Log connection issues with LogLevel that depends on tunnel state
Connections from cloudflared to Cloudflare edge are long lived and may
break over time. That is expected for many reasons (ranging from network
conditions to operations within Cloudflare edge). Hence, logging that as
Error feels too strong and leads to users being concerned that something
is failing when it is actually expected.

With this change, we wrap logging about connection issues to be aware
of the tunnel state:
 - if the tunnel has no connections active, we log as error
 - otherwise we log as warning
2021-11-10 09:00:05 +00:00
Sudarsan Reddy 6bcc9a76e9 TUN-5387: Updated CHANGES.md for 2021.11.0 2021-11-04 14:55:18 +00:00
Sudarsan Reddy 43f1c6ba82 Release 2021.11.0 2021-11-04 14:15:42 +00:00
Sudarsan Reddy 0146a8d8ed TUN-5285: Fallback to HTTP2 immediately if connection times out with no network activity 2021-11-04 10:42:53 +00:00
Silver 36479ef11f Merge pull request #478 from echtish/secret-flag
Add flag to 'tunnel create' subcommand to specify a base64-encoded secret
2021-11-02 16:21:01 -05:00
Nuno Diegues 58538619ea Release 2021.10.5 2021-10-25 20:15:34 +01:00
Nuno Diegues 573d410606 Revert "TUN-5184: Make sure outstanding websocket write is finished, and no more writes after shutdown"
This reverts commit f8fbbcd806.
2021-10-25 19:51:52 +01:00
cthuang f6f10305a6 Update change log for release 2021.10.4 2021-10-25 09:37:47 +00:00
cthuang 588f1a03c4 Release 2021.10.4 2021-10-25 09:29:45 +01:00
cthuang f8fbbcd806 TUN-5184: Make sure outstanding websocket write is finished, and no more writes after shutdown 2021-10-25 08:27:40 +00:00
cthuang 2ca4633f89 TUN-5286: Upgrade crypto/ssh package to fix CVE-2020-29652 2021-10-22 16:50:35 +00:00
cthuang 2ce11a20c4 TUN-5287: Fix misuse of wait group in TestQUICServer that caused the test to exit immediately 2021-10-22 13:40:51 +00:00
cthuang ff7c48568c TUN-5261: Collect QUIC metrics about RTT, packets and bytes transfered and log events at tracing level 2021-10-21 15:26:57 +01:00
Nuno Diegues 958650be1f TUN-5262: Improvements to max-fetch-size that allow to deal with large number of tunnels in account
* `max-fetch-size` can now be set up in the config YAML
 * we no longer pass that to filter commands that filter by name
 * flag changed to signed int since altsrc does not support UInt flags
 * we now look up each non UUID (to convert it to a UUID) when needed, separately
2021-10-19 18:28:29 +01:00
Nuno Diegues eb51ff0a6d TUN-5262: Allow to configure max fetch size for listing queries
This can be useful/important for accounts with many tunnels that exceed
the 1000 default page size.

There are various tunnel subcommands that use listing underneath, so we make
that flag a tunnel one, rather than adding it to each subcommand.
2021-10-18 11:07:02 +01:00
Nuno Diegues 3f4407ce27 Release 2021.10.3 2021-10-14 17:52:50 +01:00
Nuno Diegues d9636c73b4 TUN-5257: Fix more cfsetup targets that were broken by recent package changes 2021-10-14 16:47:34 +01:00
Nuno Diegues 6cbf90883d TUN-5255: Fix potential panic if Cloudflare API fails to respond to GetTunnel(id) during delete command 2021-10-14 15:18:34 +00:00
Nuno Diegues 76ade67f95 Release 2021.10.2 2021-10-14 14:42:56 +01:00
Nuno Diegues 997f2cf612 TUN-5250: Add missing packages for cfsetup to succeed in github release pkgs target 2021-10-14 08:56:21 +00:00
Sudarsan Reddy ceb509ee98 TUN-5138: Switch to QUIC on auto protocol based on threshold 2021-10-14 09:18:20 +01:00
Sudarsan Reddy 5a3c0fdffa Release 2021.10.1 2021-10-13 19:29:24 +01:00
Sudarsan Reddy 2822fbe3db TUN-5249: Revert "TUN-5138: Switch to QUIC on auto protocol based on threshold"
This reverts commit e445fd92f7
2021-10-13 19:06:31 +01:00
Sudarsan Reddy 5148d00516 TUN-5246: Use protocol: quic for Quick tunnels if one is not already set 2021-10-13 08:04:21 +00:00
Sudarsan Reddy bb10e1dee5 Release 2021.10.0 2021-10-12 09:50:49 +01:00
Sudarsan Reddy e445fd92f7 TUN-5138: Switch to QUIC on auto protocol based on threshold 2021-10-11 11:05:20 +00:00
Sudarsan Reddy bccf4a63dc UN-5213: Increase MaxStreams value for QUIC transport
The default max streams value of 100 is rather small when subject to
high load in terms of connecting QUIC with streams faster than it can
create new ones. This high value allows for more throughput.
2021-10-08 13:48:20 +01:00
Sudarsan Reddy 7059ef8e13 TUN-5195: Do not set empty body if not applicable
Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
  * the request body blocks
  * the content length is not set (or set to -1)
  * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
  * there is no transfer-encoding=chunked already set.
So for non websocket requests, if transfer-encoding isn't chunked and content length is 0, we dont set a request body.
2021-10-07 15:47:27 +01:00
Jeremy Teale 1239006e96 Add flag to 'tunnel create' subcommand to specify a base64-encoded secret 2021-09-29 18:56:38 -05:00
Nuno Diegues cbdf88ea28 TUN-5164: Update README and clean up references to Argo Tunnel (using Cloudflare Tunnel instead) 2021-09-29 08:27:47 +00:00
Sudarsan Reddy 79ebfa8304 TUN-5169: Release 2021.9.2 CHANGES.md 2021-09-29 08:21:22 +00:00
Sudarsan Reddy 5a5e49179a Release 2021.9.2 2021-09-28 13:03:04 +01:00
Sudarsan Reddy 470a85e65d TUN-5160: Set request.ContentLength when this value is in request header 2021-09-27 14:12:11 +01:00
Sudarsan Reddy d7da74cb9e TUN-5142: defer close rpcconn inside unregister instead of ServeControlStream 2021-09-24 12:56:31 +01:00
Sudarsan Reddy 27e1277a3b TUN-5142: Add asynchronous servecontrolstream for QUIC
ServeControlStream accidentally became non-blocking in the last quic
change causing stream to not be returned until a SIGTERM was received.
This change makes ServeControlStream be non-blocking for QUIC streams.
2021-09-24 10:00:43 +00:00
cthuang 6238fd9022 TUN-5141: Make sure websocket pinger returns before streaming returns 2021-09-23 16:54:55 +01:00
Nuno Diegues f985ed567f TUN-5128: Enforce maximum grace period
This maximum grace period will be honored by Cloudflare edge such that
either side will close the connection after unregistration at most
by this time (3min as of this commit):
 - If the connection is unused, it is already closed as soon as possible.
 - If the connection is still used, it is closed on the cloudflared configured grace-period.

Even if cloudflared does not close the connection by the grace-period time,
the edge will do so.
2021-09-21 16:48:37 +00:00
cthuang d54c8cc745 TUN-5129: Use go 1.17 and copy .git folder to docker build to compute version 2021-09-21 15:50:35 +00:00
Sudarsan Reddy 548e85829a Release 2021.9.1 2021-09-21 08:13:07 +01:00
Sudarsan Reddy fd14bf440b TUN-5118: Quic connection now detects duplicate connections similar to http2 2021-09-21 06:30:09 +00:00
Silver e2b18364f4 Merge pull request #467 from nirantak/patch-1
Fix TryCloudflare link
2021-09-15 11:08:36 -05:00
Nirantak Raghav 52c4f875fa Fix TryCloudflare link 2021-09-15 16:41:18 +05:30
Areg Harutyunyan dac077fef1 Release 2021.9.0 2021-09-14 11:14:07 -05:00
Silver 0e68b1d24e Merge pull request #463 from colabsoftware/add-tunnel-credentials-env-var
Add support for taking named tunnel credentials from an environment var
2021-09-13 14:13:10 -05:00
Riley Flynn 6968b714d0 Add support for taking named tunnel credentials from an environment variable 2021-09-13 13:51:37 -02:30
Silver 9310241e1a Merge pull request #395 from cloudflare/abelinkinbio-update-readme
Update README.md
2021-09-13 11:17:24 -05:00
Silver 1cb22817db Merge pull request #436 from Mongey/cm-arm-darwin
Allow building on arm64 platforms
2021-09-13 10:54:42 -05:00
Silver fe4b9b1535 Merge pull request #455 from josephvoss/update-trycloudflare-link
Fix broken TryCloudflare link
2021-09-13 10:42:59 -05:00
Areg Harutyunyan d04f48d872 TUN-5029: Do not strip cf- prefixed headers 2021-09-02 12:21:01 -05:00
Joseph Voss b5f6559179 Fix broken TryCloudflare link 2021-09-02 12:56:35 -04:00
Silver 89d408e3bd Merge pull request #386 from Maartincm/allow_token_secret_as_env_var_in_cloudflared_access
Allow TokenID and TokenSecret as env vars for cloudflares access
2021-09-02 10:15:29 -05:00
Areg Harutyunyan 533d005159 TUN-5011: Use the region parameter in fallback SRV lookup 2021-08-31 22:16:21 -05:00
cthuang 98c3957d30 TUN-5010: --region should be a string flag 2021-08-30 14:40:07 +00:00
Sudarsan Reddy 671754fd19 TUN-5012: Use patched go-sumtype 2021-08-30 10:10:25 +01:00
Sudarsan Reddy d1801776b0 TUN-5009: Updated github action to use go 1.17.x for checks 2021-08-29 09:29:09 +00:00
cthuang 11c06b5a1f Release 2021.8.7 2021-08-28 17:57:32 +01:00
cthuang 27cd83c2d3 Revert "TUN-4926: Implement --region configuration option"
This reverts commit d0a1daac3b.
2021-08-28 16:42:55 +01:00
Nuno Diegues 75bdc96763 Release 2021.8.6 2021-08-27 23:03:32 +01:00
Nuno Diegues c51879b17f TUN-5003: Fix cfsetup for non-FIPS golang version 2021-08-27 19:14:22 +01:00
Nuno Diegues 738b4f8d25 TUN-5000: De-flake logging to dir component test in Windows by increasing to buffer to cope with more logging 2021-08-27 18:13:45 +00:00
Nuno Diegues c7a44009d3 Release 2021.8.5 2021-08-27 15:49:39 +01:00
Nuno Diegues 754ad59512 Release 2021.8.4 2021-08-27 13:28:24 +00:00
Sudarsan Reddy 414cb12f02 TUN-4961: Update quic-go to latest
- Updates fips-go to be the latest on cfsetup.yaml
- Updates sumtype's x/tools to be latest to avoid Internal: nil pkg
  errors with fips.
2021-08-27 12:26:00 +01:00
Areg Harutyunyan d0a1daac3b TUN-4926: Implement --region configuration option 2021-08-27 09:11:10 +00:00
Nuno Diegues 2afa307765 TUN-4981: Improve readability of prepareTunnelConfig method 2021-08-26 18:15:36 +01:00
Rishabh Bector a4a9f45b0a TUN-4821: Make quick tunnels the default in cloudflared 2021-08-26 15:53:02 +00:00
Sudarsan Reddy 1da4fbbe0b TUN-4970: Only default to http2 for warp-routing if protocol is h2mux 2021-08-26 15:12:31 +00:00
Nuno Diegues 836149a5b0 TUN-4974: Fix regression where we were debug logging by accident 2021-08-26 09:53:17 +01:00
Sudarsan Reddy 4ff215ee8c Release 2021.8.3 2021-08-24 17:26:24 +01:00
Sudarsan Reddy 17e3073a17 TUN-4613: Add a no-op protocol version slot 2021-08-24 15:21:26 +00:00
Sudarsan Reddy 071d595371 TUN-4940: Fix cloudflared not picking up correct NextProtos for quic 2021-08-23 15:30:45 +00:00
Sudarsan Reddy b6c85401a5 TUN-4889: Add back appendtagheaders function
TUN-4701 accidentally removed adding tagheaders. This commit adds it
back.
2021-08-23 15:29:48 +00:00
Areg Harutyunyan 3ef3e7a99a TUN-4927: Parameterize region in edge discovery code 2021-08-17 14:27:21 -05:00
Sudarsan Reddy 12ad264eb3 TUN-4866: Add Control Stream for QUIC
This commit adds support to Register and Unregister Connections via RPC
on the QUIC transport protocol
2021-08-17 14:50:32 +00:00
Sudarsan Reddy 1082ac1c36 TUN-4922: Downgrade quic-go library to 0.20.0 2021-08-13 15:45:13 +01:00
Conor Mongey a233f975c1 Allow building on arm64 platforms 2021-08-12 02:25:52 +01:00
Sudarsan Reddy 5f6e867685 TUN-4602: Added UDP resolves to Edge discovery 2021-08-09 18:41:43 +00:00
Sudarsan Reddy 78a9454023 Release 2021.8.2 2021-08-09 17:00:55 +01:00
Sudarsan Reddy ca85df10ff TUN-4911: Append Environment variable to Path instead of overwriting it
Environment Setting last specifies which part of the variable to append to. It was previously set to all which meant replace. (Ref: https://wixtoolset.org/documentation/manual/v3/xsd/wix/environment.html) .
(#432)
2021-08-09 15:45:29 +01:00
Sudarsan Reddy b8333b44a2 TUN-4795: Remove Equinox releases 2021-08-05 08:24:36 +00:00
Sudarsan Reddy e49a7a4389 TUN-4597: Added HTTPProxy for QUIC 2021-08-04 12:50:18 +00:00
Sudarsan Reddy 5749425671 Release 2021.8.1 2021-08-04 09:14:32 +01:00
Sudarsan Reddy fac9dfb6e5 TUN-4875: Added amd64-linux builds back to releases 2021-08-04 08:18:43 +01:00
Sudarsan Reddy d9ec18314d TUN-4873: Disable unix domain socket test for windows unit tests 2021-08-03 12:08:08 +01:00
Sudarsan Reddy ed024d0741 TUN-4597: Add a QUIC server skeleton
- Added a QUIC server to accept streams
- Unit test for this server also tests ALPN
- Temporary echo capability for HTTP ConnectionType
2021-08-03 10:03:47 +00:00
Sudarsan Reddy fd4000184c TUN-4855: Added CHANGES.md for release 2021.8.0 2021-08-02 09:50:44 +01:00
Sudarsan Reddy bcc847963e Release 2021.8.0 2021-08-02 09:28:51 +01:00
Nuno Diegues 1e8dea9112 TUN-4851: Component tests to sanity check that Proxy DNS and Tunnel are only run when expected 2021-08-02 08:01:09 +00:00
Sudarsan Reddy cd4af5696d TUN-4772: Release built executables with packages 2021-07-30 13:56:19 +00:00
Nuno Diegues 8527d03a29 TUN-4847: Allow to list tunnels by prefix name or exclusion prefix name 2021-07-30 12:00:26 +01:00
Nuno Diegues fa8aa02270 TUN-4819: Tolerate protocol TXT record lookup failing 2021-07-29 11:44:39 +00:00
Nuno Diegues aa24338225 TUN-4832: Prevent tunnel from running accidentally when only proxy-dns should run 2021-07-29 11:05:12 +00:00
Nuno Diegues 0924549efd TUN-4811: Publish quick tunnels' hostname in /metrics under userHostname for backwards-compatibility 2021-07-29 10:20:43 +01:00
Sudarsan Reddy 67a3be5b7a Release 2021.7.4 2021-07-28 10:35:17 +01:00
Sudarsan Reddy c33a516663 TUN-4812: Disable CGO for cloudflared builds 2021-07-28 09:33:59 +00:00
Sudarsan Reddy ed1389ef08 TUN-4814: Revert "TUN-4699: Make quick tunnels the default in cloudflared"
This reverts commit 18992efa0c.
2021-07-28 10:02:55 +01:00
Sudarsan Reddy 8fb6508ae6 Release 2021.7.3 2021-07-27 18:00:03 +01:00
Sudarsan Reddy ee8c8bd4c6 TUN-4799: Build deb, msi and rpm packages with fips 2021-07-27 11:38:22 +01:00
Sudarsan Reddy 0269062b24 Release 2021.7.2 2021-07-27 10:01:39 +01:00
Sudarsan Reddy bccf650165 Fixed a syntax error with python logging. 2021-07-27 09:33:41 +01:00
Sudarsan Reddy cecc74d3fb Release 2021.7.1 2021-07-26 17:19:43 +01:00
Rishabh Bector 18992efa0c TUN-4699: Make quick tunnels the default in cloudflared 2021-07-26 15:57:36 +00:00
Sudarsan Reddy bd8af7d80d TUN-4771: Upload deb, rpm and msi packages to github
- cfsetup now has a build command `github-release-pkgs` to release linux
   and msi packages to github.
 - github_message.py now has an option to upload all assets in a provided
   directory.
2021-07-26 13:49:17 +01:00
Sudarsan Reddy dff694b218 TUN-4761: Added a build-all-packages target to cfsetup 2021-07-22 16:36:49 +01:00
Sudarsan Reddy 38af26e232 TUN-4755: Add a windows msi release option to Make 2021-07-21 10:31:16 +01:00
Sudarsan Reddy 8f3526289a TUN-4701: Split Proxy into ProxyHTTP and ProxyTCP
http.Request now is only used by ProxyHTTP and not required if the
proxying is TCP. The dest conversion is handled by the transport layer.
2021-07-19 13:43:59 +00:00
Sudarsan Reddy 81dff44bb9 TUN-4596: Add QUIC application protocol for QUIC stream handshake
- Vendored the capnproto library to cloudflared.
- Added capnproto schema defining application protocol.
- Added Pogs and application level read write of the protocol.
2021-07-15 17:35:25 +00:00
cthuang 6e45e0d53b TUN-4714: Name nightly package cloudflared-nightly to avoid apt conflict 2021-07-14 20:45:29 +01:00
Sudarsan Reddy a7d2de1e12 Release 2021.7.0 2021-07-12 10:29:22 +01:00
Rishabh Bector 59cae0f622 TUN-4698: Add cloudflared metrics endpoint to serve quick tunnel hostname 2021-07-12 09:26:07 +00:00
Sudarsan Reddy d678584d89 TUN-4655: ingress.StreamBasedProxy.EstablishConnection takes dest input
This change extracts the need for EstablishConnection to know about a
request's entire context. It also removes the concern of populating the
http.Response from EstablishConnection's responsibilities.
2021-07-07 15:58:04 +01:00
Sudarsan Reddy f1b57526b3 TUN-4626: Proxy non-stream based origin websockets with http Roundtrip.
Reuses HTTPProxy's Roundtrip method to directly proxy websockets from
eyeball clients (determined by websocket type and ingress not being
connection oriented , i.e. Not ssh or smb for example) to proxy
websocket traffic.
2021-07-07 14:31:42 +00:00
Rishabh Bector 3eb9efd9f0 TUN-4521: Modify cloudflared to use zoneless-tunnels-worker for free tunnels 2021-06-29 09:39:18 +01:00
Michael Borkenstein 8d99e92852 AUTH-3475: Updated GetAppInfo error message 2021-06-25 10:37:48 -05:00
Nuno Diegues 31ff7caeeb Release 2021.6.0 2021-06-21 14:43:35 +01:00
Nuno Diegues 5d84874d53 TUN-4571: Changelog for 2021.6.0 2021-06-21 12:04:05 +01:00
Nuno Diegues b06fe0fc5f TUN-4571: Fix proxying to unix sockets when using HTTP2 transport to Cloudflare Edge 2021-06-21 11:43:14 +01:00
abe bf38e5aa12 Update README.md
Updated our README.md to reflect the supported versions of cloudflared.
2021-06-09 12:56:44 -05:00
Nuno Diegues bf068e728f TUN-4504: Fix component tests in windows 2021-06-08 19:34:42 +01:00
Nuno Diegues f88732277a TUN-4502: Make cloudflared tunnel route subcommands described consistently 2021-06-07 09:20:11 +01:00
Martin Cuesta a11c64b091 🎨 Prefix env var parameters with TUNNEL
This is for TokenID and TokenSecret in the cloudflare access CLI.
2021-05-31 15:38:46 -03:00
Martin Cuesta e404c29edb 🖌️ Allow providing TokenID and TokenSecret as env vars when calling cloudflared access
Implements #232
2021-05-31 13:04:39 -03:00
Nuno Diegues 98a0844f56 TUN-4461: Log resulting DNS hostname if one is received from Cloudflare API 2021-05-27 17:32:16 +01:00
Sudarsan Reddy d6d65d183b Release 2021.5.10 2021-05-26 12:05:30 +01:00
Sudarsan Reddy 951d13d76c TUN-4456: Replaced instances of Tick() with Ticker() in h2mux paths
time.Tick() does not get garbage collected because the channel
underneath never gets deleted and the underlying Ticker can never be
recovered by the garbage collector. We replace this with NewTicker() to
avoid this.
2021-05-25 18:07:06 +01:00
Michael Borkenstein f99ae90ca1 Release 2021.5.9 2021-05-20 14:08:22 -05:00
Michael Borkenstein e99d0b5bc4 Update changelog 2021-05-20 13:09:07 -05:00
Michael Borkenstein 48c5721bc6 TUN-4426: Fix centos builds 2021-05-20 11:29:14 -05:00
Michael Borkenstein 235897ba21 AUTH-3426: Point to new transfer service URL and eliminate PUT /ok 2021-05-19 19:39:56 +00:00
Adam Chalmers bf737a0efc Release 2021.5.8 2021-05-19 13:30:15 -05:00
Adam Chalmers 4c5ebccacc TUN-4425: --overwrite-dns flag for in adhoc and route dns cmds 2021-05-19 18:22:01 +00:00
Adam Chalmers a3153c6add TUN-4419: Improve error message when cloudflared cannot reach origin 2021-05-18 15:35:51 +00:00
cthuang a2bbe1bdc2 Release 2021.5.7 2021-05-17 17:12:21 +01:00
cthuang 6526211a69 TUN-4421: Named Tunnels will automatically select the protocol to connect to Cloudflare's edge network 2021-05-17 15:57:44 +01:00
Silver 103b2eca00 Merge pull request #371 from thundergolfer/patch-1
Fix typo in Changes.md
2021-05-17 11:18:55 +01:00
Jonathon Belotti 14ea6298bc Fix typo in Changes.md 2021-05-17 12:03:59 +10:00
Adam Chalmers b297e8bb90 Release 2021.5.6 2021-05-14 18:22:58 -05:00
Igor Postelnik 6cc8c39fa8 Release 2021.5.5 2021-05-14 17:51:08 -05:00
Adam Chalmers 4d4eedd1c0 Release 2021.5.4 2021-05-14 14:00:17 -05:00
Adam Chalmers a00eda9538 TUN-4411: Fix Go version 2021-05-14 19:40:12 +01:00
Adam Chalmers 9ff65c1e90 Release 2021.5.1 2021-05-13 02:05:20 +01:00
Adam Chalmers 2dcf3bd011 Release 2021.5.0 2021-05-13 02:05:19 +01:00
Nuno Diegues ae460b340b TUN-4342: Fix false positive warning about unused hostname property 2021-05-13 02:05:19 +01:00
Michael Borkenstein bc54a7f87b AUTH-3537: AUDs in JWTs are now always arrays 2021-05-13 02:05:19 +01:00
Adam Chalmers 75c3ca2f4a TUN-4384: Silence log from automaxprocs 2021-05-13 02:05:19 +01:00
Adam Chalmers 4bd17766a9 TUN-4359: Warn about unused keys in 'tunnel ingress validate' 2021-05-13 02:05:19 +01:00
Adam Chalmers b87cb9aee8 TUN-4357: Bump Go to 1.16 2021-05-13 02:05:18 +01:00
Adam Chalmers 07af2a33b7 TUN-4356: Set AUTOMAXPROCS to the CPU limit when running in a Linux container 2021-05-13 02:05:18 +01:00
abelinkinbio 209091da39 TUN-4000: Release notes for cloudflared replica model 2021-05-13 02:05:18 +01:00
Areg Harutyunyan 4d43a70a38 Setup a Github action for checking the cloudflared build 2021-05-04 14:31:22 +01:00
Adam Chalmers 5b35e968f3 TUN-4343: Fix broken build by setting debug field correctly 2021-05-04 01:05:47 +01:00
Daniel Hwang f3b0b33dc5 debug: log host / path
to help understand why the ingress rule logged is being selected.

in addition, combine "Request Headers..." and "Serving with ingress..." logs
into this updated log.

Co-authored-by: Silver <sssilver@users.noreply.github.com>
2021-05-03 15:45:54 +01:00
Michael Borkenstein aca0c93461 AUTH-3513: Checks header for app info in case response is a 403/401 from the edge 2021-04-20 12:06:03 -05:00
Adam Chalmers eed7d7bbc9 TUN-4206: Better error message when user is only using one ingress rule 2021-04-12 12:04:54 -05:00
Areg Harutyunyan 1073f8db40 TUN-2853: rename STDIN-CONTROL env var to STDIN_CONTROL 2021-04-09 16:43:01 +01:00
Igor Postelnik e45695d4a3 Release 2021.4.0 2021-04-07 13:19:08 -05:00
Igor Postelnik 7ce0aefea4 Publish change log for 2021.4.0 2021-04-07 11:59:24 -05:00
Igor Postelnik 3ad99b241c TUN-4168: Transparently proxy websocket connections using stdlib HTTP client instead of gorilla/websocket; move websocket client code into carrier package since it's only used by access subcommands now (#345). 2021-04-07 16:25:46 +00:00
Nuno Diegues b25d38dd72 TUN-4177: Running with proxy-dns should not prevent running Named Tunnels 2021-04-05 11:28:05 +01:00
Nuno Diegues 1720ac0fc6 TUN-4178: Fix component test for running as a service in MacOS to not assume a named tunnel 2021-04-05 10:29:23 +01:00
Nuno Diegues 0929e5f0ff Release 2021.3.6 2021-03-31 08:51:43 +01:00
Nuno Diegues 36787d9cf7 TUN-4153: Revert best-effort HTTP2 usage when talking to origins
This commit reverts part of 1cf6ae37eb.
2021-03-31 08:25:59 +01:00
Adam Chalmers 5afa3251dd TUN-4150: Only show the connector table in 'tunnel info' if there are connectors. Don't show rows with zero connections. 2021-03-30 20:13:00 +00:00
Igor Postelnik 8ca0d86c85 TUN-3863: Consolidate header handling logic in the connection package; move headers definitions from h2mux to packages that manage them; cleanup header conversions
All header transformation code from h2mux has been consolidated in the connection package since it's used by both h2mux and http2 logic.
Exported headers used by proxying between edge and cloudflared so then can be shared by tunnel service on the edge.
Moved access-related headers to corresponding packages that have the code that sets/uses these headers.
Removed tunnel hostname tracking from h2mux since it wasn't used by anything. We will continue to set the tunnel hostname header from the edge for backward compatibilty, but it's no longer used by cloudflared.
Move bastion-related logic into carrier package, untangled dependencies between carrier, origin, and websocket packages.
2021-03-29 21:57:56 +00:00
Adam Chalmers ebf5292bf9 TUN-4146: Unhide and document grace-period 2021-03-29 16:29:18 -05:00
Adam Chalmers f9062ab473 TUN-4141: Better error messages for tunnel info subcommand. 2021-03-26 14:45:35 -05:00
Nuno Diegues d14f3b39a7 Release 2021.3.5 2021-03-26 18:50:54 +00:00
Nuno Diegues fd0529748a Publish change log for 2021.3.5 2021-03-26 18:27:45 +00:00
Nuno Diegues 9d3a7bd08e TUN-4125: Change component tests to run in CI with its own dedicated resources 2021-03-26 11:41:56 +00:00
Sudarsan Reddy 1cf6ae37eb TUN-3896: http-service and tunnelstore client use http2 transport.
- If origin services are http2 and https is the service url, http2
   transport is preferred.

   - The tunnelstore client is now upgraded to use http2.
2021-03-26 10:31:40 +00:00
Michael Borkenstein 66da530ba3 Release 2021.3.4 2021-03-25 18:39:55 -05:00
Michael Borkenstein e9167f7f58 Release 2021.3.3 2021-03-25 12:41:25 -05:00
Michael Borkenstein 4494a27fab Update changelog 2021.3.3 2021-03-25 11:51:12 -05:00
Michael Borkenstein 63833b07dd AUTH-3455: Generate short-lived ssh cert per hostname 2021-03-25 10:38:43 -05:00
Igor Postelnik da4d0b2bae TUN-4067: Reformat code for consistent import order, grouping, and fix formatting. Added goimports target to the Makefile to make this easier in the future. 2021-03-24 10:53:29 -05:00
cthuang 027168c23a TUN-4123: Don't capture output in reconnect componet test 2021-03-24 14:14:47 +00:00
Igor Postelnik 50435546c5 TUN-4118: Don't overwrite existing file with tunnel credentials. For ad-hoc tunnels, this means tunnel won't start if there's a file in the way. 2021-03-24 08:26:22 -05:00
Igor Postelnik 9018ee5d5e TUN-4116: Ingore credentials-file setting in configuration file during tunnel create and delete opeations.
This change has two parts:
1. Update to newer version of the urfave/cli fork that correctly sets flag value along the context hierarchy while respecting config file overide behavior of the most specific instance of the flag.
2. Redefine --credentials-file flag so that create and delete subcommand don't use value from the config file.
2021-03-24 08:15:36 -05:00
cthuang 12447677cf TUN-4112: Skip testing graceful shutdown with SIGINT on Windows 2021-03-23 21:52:10 +00:00
Nuno Diegues 6106737281 TUN-4082: Test logging when running as a service 2021-03-23 20:14:53 +00:00
Nuno Diegues 8250b67a9f TUN-4111: Warn the user if both properties "tunnel" and "hostname" are used 2021-03-23 20:14:29 +00:00
Michael Borkenstein db5c6f2556 Release 2021.3.2 2021-03-23 11:08:54 -05:00
Michael Borkenstein 9dd7898792 Publish changelog for 2021.3.2 2021-03-23 10:31:46 -05:00
cthuang 92b3e8311f TUN-4042: Capture cloudflared output to debug component tests 2021-03-23 13:21:37 +00:00
Nuno Diegues 4a7763e497 TUN-3998: Allow to cleanup the connections of a tunnel limited to a single client 2021-03-23 08:48:54 +00:00
cthuang 9767ba1853 TUN-4096: Reduce tunnel not connected assertion backoff to address flaky termination tests 2021-03-18 08:28:38 +00:00
Michael Borkenstein 2c75326021 AUTH-3394: Ensure scheme on token command 2021-03-17 10:50:03 -05:00
Igor Postelnik 9023daba24 TUN-3715: Apply input source to the correct context 2021-03-17 14:59:39 +00:00
Nuno Diegues 89d0e45d62 TUN-3993: New cloudflared tunnel info to obtain details about the active connectors for a tunnel 2021-03-17 14:08:18 +00:00
Igor Postelnik a34099724e TUN-4094: Don't read configuration file for access commands 2021-03-16 17:36:46 -05:00
Igor Postelnik 8c5498fad1 TUN-3715: Only read config file once, right before invoking the command 2021-03-16 17:22:13 -05:00
Adam Chalmers 2c746b3361 TUN-4081: Update log severities to use Zerolog's levels 2021-03-16 19:04:49 +00:00
cthuang 954cd6adca TUN-4091: Use flaky decorator to rerun reconnect component tests when they fail 2021-03-16 17:10:15 +00:00
Nuno Diegues 8432735867 TUN-4060: Fix Go Vet warnings (new with go 1.16) where t.Fatalf is called from a test goroutine 2021-03-16 16:12:11 +00:00
cthuang d67fbbf94f TUN-4089: Address flakiness in component tests for termination 2021-03-16 11:31:20 +00:00
Nuno Diegues 39901e1d60 Release 2021.3.1 2021-03-15 18:46:26 +00:00
cthuang 9df60276a9 TUN-4052: Add component tests to assert service mode behavior 2021-03-15 17:45:25 +00:00
cthuang 6a9ba61242 TUN-4051: Add component-tests to test graceful shutdown 2021-03-15 14:41:32 +00:00
Nuno Diegues 848c44bd0b Release 2021.3.0 2021-03-15 11:49:44 +00:00
Nuno Diegues 9f84706eae Publish change log for 2021.3.0 2021-03-15 10:28:11 +00:00
Michael Borkenstein 841344f1e7 AUTH-3394: Creates a token per app instead of per path - with fix for
free tunnels
2021-03-12 15:49:47 +00:00
cthuang 25cfbec072 TUN-4050: Add component tests to assert reconnect behavior 2021-03-12 09:29:29 +00:00
cthuang f23e33c082 TUN-4049: Add component tests to assert logging behavior when running from terminal 2021-03-12 09:18:15 +00:00
Nuno Diegues d22b374208 TUN-4066: Set permissions in build agent before 'scp'-ing to machine hosting package repo 2021-03-11 19:02:26 +00:00
Nuno Diegues d6e867d4d1 TUN-4066: Remove unnecessary chmod during package publish to CF_PKG_HOSTS 2021-03-11 11:43:34 +00:00
cthuang a7344435a5 TUN-4062: Read component tests config from yaml file 2021-03-10 21:29:33 +00:00
Lee Valentine 206523344f TUN-4017: Add support for using cloudflared as a full socks proxy.
To use cloudflared as a socks proxy, add an ingress on the server
side with your desired rules. Rules are matched in the order they
are added.  If there are no rules, it is an implicit allow.  If
there are rules, but no rule matches match, the connection is denied.

ingress:
  - hostname: socks.example.com
    service: socks-proxy
    originRequest:
      ipRules:
        - prefix: 1.1.1.1/24
          ports: [80, 443]
          allow: true
        - prefix: 0.0.0.0/0
          allow: false

On the client, run using tcp mode:
cloudflared access tcp --hostname socks.example.com --url 127.0.0.1:8080

Set your socks proxy as 127.0.0.1:8080 and you will now be proxying
all connections to the remote machine.
2021-03-10 21:26:12 +00:00
Adam Chalmers b0e69c4b8a Revert "AUTH-3394: Creates a token per app instead of per path"
This reverts commit 8e340d9598.
2021-03-10 13:54:38 -06:00
Adam Chalmers aa5ebb817a TUN-4075: Dedup test should not compare order of list 2021-03-10 13:48:59 -06:00
Michael Borkenstein 8e340d9598 AUTH-3394: Creates a token per app instead of per path 2021-03-10 17:15:16 +00:00
Nuno Diegues 4296b23087 TUN-4069: Fix regression on support for websocket over proxy 2021-03-09 19:43:10 +00:00
Benjamin Buzbee 452f8cef79 Allow partial reads from a GorillaConn; add SetDeadline (from net.Conn) (#330)
* Allow partial reads from a GorillaConn; add SetDeadline (from net.Conn)

The current implementation of GorillaConn will drop data if the
websocket frame isn't read 100%. For example, if a websocket frame is
size=3, and Read() is called with a []byte of len=1, the 2 other bytes
in the frame are lost forever.

This is currently masked by the fact that this is used primarily in
io.Copy to another socket (in ingress.Stream) - as long as the read buffer
used by io.Copy is big enough (it is 32*1024, so in theory we could see
this today?) then data is copied over to the other socket.

The client then can do partial reads just fine as the kernel will take
care of the buffer from here on out.

I hit this by trying to create my own tunnel and avoiding
ingress.Stream, but this could be a real bug today I think if a
websocket frame bigger than 32*1024 was received, although it is also
possible that we are lucky and the upstream size which I haven't checked
uses a smaller buffer than that always.

The test I added hangs before my change, succeeds after.

Also add SetDeadline so that GorillaConn fully implements net.Conn

* Comment formatting; fast path

* Avoid intermediate buffer for first len(p) bytes; import order
2021-03-09 19:57:04 +04:00
Igor Postelnik 39065377b5 TUN-4063: Cleanup dependencies between packages.
- Move packages the provide generic functionality (such as config) from `cmd` subtree to top level.
- Remove all dependencies on `cmd` subtree from top level packages.
- Consolidate all code dealing with token generation and transfer to a single cohesive package.
2021-03-09 14:02:59 +00:00
Areg Harutyunyan d83d6d54ed TUN-3905: Cannot run go mod vendor in cloudflared due to fips 2021-03-09 17:31:59 +04:00
Nuno Diegues a2b41ea3e6 TUN-4016: Delegate decision to update for Worker service 2021-03-08 19:25:42 +00:00
cthuang 4481b9e46c TUN-4047: Add cfsetup target to run component test 2021-03-08 11:57:18 +00:00
cthuang e5d6f969be TUN-4055: Skeleton for component tests 2021-03-08 11:08:34 +00:00
Adam Chalmers ded9dec4f0 TUN-3819: Remove client-side check that deleted tunnels have no connections 2021-03-05 21:21:10 +00:00
Nuno Diegues 89b738f8fa TUN-4026: Fix regression where HTTP2 edge transport was no longer propagating control plane errors 2021-03-04 18:45:39 +00:00
Adam Chalmers 4f88982584 TUN-3994: Log client_id when running a named tunnel 2021-03-03 17:27:23 +00:00
Nuno Diegues bcd71b56e9 TUN-3989: Check in with Updater service in more situations and convey messages to user 2021-03-03 13:57:04 +00:00
Adam Chalmers 5c7b451e17 TUN-3995: Optional --features flag for tunnel run.
These features will be included in the ClientInfo.Features field when
running a named tunnel.
2021-03-02 16:21:17 -06:00
cthuang b73c039070 TUN-3988: Log why it cannot check if origin cert exists 2021-03-01 21:37:44 +00:00
PaulC 53a69a228a Issue #285 - Makefile does not detect TARGET_ARCH correctly on FreeBSD (#325)
* Issue-285: Detect TARGET_ARCH correctly for FreeBSD amd64 (uname -m returns amd64 not x86_64)

See: https://github.com/cloudflare/cloudflared/issues/285

* Add note not to `go get github.com/BurntSushi/go-sumtype` in build directory as this will cause vendor issues

Co-authored-by: PaulC <paulc@>
2021-03-01 21:43:08 +04:00
Areg Harutyunyan eda3a7a305 TUN-3983: Renew CA certs in cloudflared 2021-03-01 16:30:28 +00:00
Nuno Diegues f1ca2de515 TUN-3978: Unhide teamnet commands and improve their help 2021-03-01 11:59:46 +00:00
Adam Chalmers 27507ab192 TUN-3970: Route ip show has alias route ip list 2021-02-26 17:15:43 +00:00
Igor Postelnik 6db934853d TUN-3963: Repoint urfave/cli/v2 library at patched branch at github.com/ipostelnik/cli/v2@fixed which correctly handles reading flags declared at multiple levels of subcommands. 2021-02-24 20:04:59 +00:00
Nuno Diegues 792520d313 Release 2021.2.5 2021-02-23 18:33:42 +00:00
Nuno Diegues 8b9cfcde78 Publish change notes for 2021.2.5 2021-02-23 17:23:46 +00:00
Nuno Diegues 5ba3b3b309 TUN-3939: Add logging that shows that Warp-routing is enabled 2021-02-23 14:19:47 +00:00
cthuang 63a29f421a TUN-3895: Tests for socks stream handler 2021-02-23 14:19:47 +00:00
Sudarsan Reddy e20c4f8752 TUN-3838: ResponseWriter no longer reads and origin error tests 2021-02-23 14:19:47 +00:00
cthuang ab4dda5427 TUN-3868: Refactor singleTCPService and bridgeService to tcpOverWSService and rawTCPService 2021-02-23 14:19:47 +00:00
cthuang 5943808746 TUN-3889: Move host header override logic to httpService 2021-02-23 14:19:47 +00:00
Sudarsan Reddy ed57ee64e8 TUN-3853: Respond with ws headers from the origin service rather than generating our own 2021-02-23 14:19:47 +00:00
Igor Postelnik 9c298e4851 TUN-3855: Add ability to override target of 'access ssh' command to a different host for testing 2021-02-23 14:19:47 +00:00
Sudarsan Reddy 8b794390e5 TUN-3799: extended the Stream interface to take a logger and added debug logs for io.Copy errors 2021-02-23 14:19:47 +00:00
Sudarsan Reddy a6c2348127 TUN-3817: Adds tests for websocket based streaming regression 2021-02-23 14:19:47 +00:00
Nuno Diegues 6681d179dc TUN-3809: Allow routes ip show to output as JSON or YAML
It also fixes the marshelling of CIDR into JSON since otherwise
it would show garbled characters as the mask.
2021-02-23 14:19:47 +00:00
cthuang 2146f71b45 TUN-3753: Select http2 protocol when warp routing is enabled 2021-02-23 14:19:47 +00:00
cthuang 3b93914612 TUN-3764: Actively flush data for TCP streams 2021-02-23 14:19:47 +00:00
Sudarsan Reddy b4700a52e3 TUN-3725: Warp-routing is independent of ingress
- Changed warp-routing configuration to its own yaml.
    - Ingress Rules host matching is indepedent of warp-routing.
2021-02-23 14:19:47 +00:00
Sudarsan Reddy 368066a966 TUN-3615: added support to proxy tcp streams
added ingress.DefaultStreamHandler and a basic test for tcp stream proxy
moved websocket.Stream to ingress
cloudflared no longer picks tcpstream host from header
2021-02-23 14:19:47 +00:00
cthuang e2262085e5 TUN-3617: Separate service from client, and implement different client for http vs. tcp origins
- extracted ResponseWriter from proxyConnection
 - added bastion tests over websocket
 - removed HTTPResp()
 - added some docstrings
 - Renamed some ingress clients as proxies
 - renamed instances of client to proxy in connection and origin
 - Stream no longer takes a context and logger.Service
2021-02-23 14:19:44 +00:00
cthuang 5e2b43adb5 Release 2021.2.4 2021-02-23 12:47:21 +00:00
cthuang c7dca16300 Publish release notes for 2021.2.4 2021-02-23 12:39:18 +00:00
cthuang 9d5bd256be TUN-3964: Revert "TUN-3922: Repoint urfave/cli/v2 library at patched branch at github.com/ipostelnik/cli/v2@fixed which correctly handles reading flags declared at multiple levels of subcommands."
This reverts commit 1670ee87fb.
2021-02-23 12:32:07 +00:00
Nuno Diegues e9c2afec56 TUN-3948: Log error when retrying connection 2021-02-23 11:40:29 +00:00
cthuang 4e33281337 Release 2021.2.3 2021-02-23 10:28:46 +00:00
cthuang ac559f86c9 Publish release notes for 2021.2.3 2021-02-23 10:17:43 +00:00
Areg Harutyunyan 117766562b TUN-3945: Fix runApp signature for generic service 2021-02-19 22:06:57 +00:00
jmonson 5d76e940c7 Added support for proxy (#318)
Co-authored-by: Jeff Monson <jeff.monson@armis.com>
2021-02-19 21:27:22 +04:00
Igor Postelnik 1670ee87fb TUN-3922: Repoint urfave/cli/v2 library at patched branch at github.com/ipostelnik/cli/v2@fixed which correctly handles reading flags declared at multiple levels of subcommands. 2021-02-18 18:12:20 +00:00
Igor Postelnik a8ae6de213 TUN-3924: Removed db-connect command. Added a placeholder handler for this command that informs users that command is no longer supported. 2021-02-17 20:13:51 -06:00
David Jimenez d7c4a89106 Add max upstream connections dns-proxy option (#290)
* Add max upstream connections dns-proxy option

Allows defining a limit to the number of connections that can be
established with the upstream DNS host.

If left unset, there may be situations where connections fail to
establish, which causes the Transport to create an influx of connections
causing upstream to throttle our requests and triggering a runaway
effect resulting in high CPU usage. See https://github.com/cloudflare/cloudflared/issues/91

* Code review with proposed changes

* Add max upstream connections flag to tunnel flags

* Reduce DNS proxy max upstream connections default value

Reduce the default value of maximum upstream connections on the DNS
proxy to guarantee it works on single-core and other low-end hardware.
Further testing could allow for a safe increase of this value.

* Update dns-proxy flag name

Also remove `MaxUpstreamConnsFlag` const as it's no longer referenced in more than one place and to make things more consistent with how the other flags are referenced.

Co-authored-by: Adam Chalmers <achalmers@cloudflare.com>
2021-02-12 21:32:29 +04:00
Nuno Diegues e7354f4768 Update the TryCloudflare link 2021-02-11 15:19:28 +00:00
Samuel Rhea 18ec338d4c Update README.md
Updates to the new Cloudflare documentation links
2021-02-11 15:19:28 +00:00
Adam Chalmers 7f97e2f030 TUN-3913: Help gives wrong exit code for autoupdate 2021-02-11 08:39:56 -06:00
Adam Chalmers a278753bbf TUN-3902: Add jitter to backoffhandler
Jitter is important to avoid every cloudflared in the world trying to
reconnect at t=1, 2, 4, etc. That could overwhelm the backend. But
if each cloudflared randomly waits for up to 2, then up to 4, then up
to 8 etc, then the retries get spread out evenly across time.

On average, wait times should be the same (e.g. instead of waiting for
exactly 1 second, cloudflared will wait betweeen 0 and 2 seconds).

This is the "Full Jitter" algorithm from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
2021-02-11 14:36:13 +00:00
Akemi Davisson 3004703074 Release 2021.2.2 2021-02-10 11:27:31 -06:00
Akemi Davisson 67680f5536 AUTH-3375 exchangeOrgToken deleted cookie fix 2021-02-10 16:09:50 +00:00
Adam Chalmers d8bee0b4d9 TUN-3890: Code coverage for cloudflared in CI
Also changed the socks test code so that it binds to localhost, so that
we don't get popups saying "would you like to allow socks.test to use
the network"
2021-02-09 13:16:00 -06:00
Security Generation a4f185fd28 Update error message to use login command
Unless I'm mistaken, when there is no existing token for an app, the `login` command needs to be run to obtain a token (not the `token` command, which itself doesn't generate a token).
2021-02-09 17:15:13 +00:00
Igor Postelnik cf562ef8c8 TUN-3635: Send event when unregistering tunnel for gracful shutdown so /ready endpoint reports down status befoe connections finish handling pending requests. 2021-02-08 15:38:42 +00:00
Areg Harutyunyan 820e0dfe51 TUN-3878: Do not supply -tags when none are specified 2021-02-08 15:22:12 +00:00
Igor Postelnik 0b16a473da TUN-3869: Improve reliability of graceful shutdown.
- Don't rely on edge to close connection on graceful shutdown in h2mux, start muxer shutdown from cloudflared.
- Don't retry failed connections after graceful shutdown has started.
- After graceful shutdown channel is closed we stop waiting for retry timer and don't try to restart tunnel loop.
- Use readonly channel for graceful shutdown in functions that only consume the signal
2021-02-08 14:30:32 +00:00
Adam Chalmers dbd90f270e TUN-3864: Users can choose where credentials file is written after creating a tunnel 2021-02-05 11:20:51 -06:00
Areg Harutyunyan 7420439ed2 Release 2021.2.1 2021-02-04 15:13:38 +00:00
Areg Harutyunyan 352207e933 TUN-3858: Do not suffix cloudflared version with -fips 2021-02-04 14:58:15 +00:00
Areg Harutyunyan 5eb2874a13 Release 2021.2.0 2021-02-04 11:19:01 +00:00
Adam Chalmers dca77ee13e TUN-3854: cloudflared tunnel list flags to sort output 2021-02-03 23:47:49 +00:00
Adam Chalmers 0d22106416 TUN-3848: Use transport logger for h2mux 2021-02-03 17:31:16 -06:00
Areg Harutyunyan 88b53eb886 TUN-3826: Use go-fips when building cloudflared for linux/amd64 2021-02-02 18:12:14 +00:00
Areg Harutyunyan b72ee69eef TUN-3837: Remove automation_email from cloudflared status page test 2021-02-01 15:07:37 +00:00
Adam Chalmers 8a020d12e1 TUN-3830: Use Go 1.15.7 2021-01-28 22:37:23 -06:00
Igor Postelnik a945518404 TUN-3811: Better error reporting on http2 connection termination. Registration errors from control loop are now propagated out of the connection server code. Unified error handling between h2mux and http2 connections so we log and retry errors the same way, regardless of underlying transport. 2021-01-28 10:38:30 -06:00
Igor Postelnik 6cdd20e820 TUN-3792: Handle graceful shutdown correctly when running as a windows service. Only expose one shutdown channel globally, which now triggers the graceful shutdown sequence across all modes. Removed separate handling of zero-duration grace period, instead it's checked only when we need to wait for exit. 2021-01-27 07:21:34 -06:00
Igor Postelnik d87bfcbe55 TUN-3795: Removed errant test 2021-01-26 11:56:02 -06:00
Igor Postelnik 0df4f7dd24 TUN-3795: Use RFC-3339 style date format for logs, produce timestamp in UTC 2021-01-26 15:04:33 +00:00
Adam Chalmers 7df3a1ab67 TUN-3806: Use a .dockerignore 2021-01-26 14:04:53 +00:00
Areg Harutyunyan c4fbb05c1b TUN-3165: Add reference to Argo Tunnel documentation in the help output 2021-01-25 16:17:00 +00:00
Igor Postelnik d503aeaf77 TUN-3118: Changed graceful shutdown to immediately unregister tunnel from the edge, keep the connection open until the edge drops it or grace period expires 2021-01-22 11:14:36 -06:00
Igor Postelnik db0562c7b8 Fixed connection error handling by removing duplicated errors, standardizing on non-pointer error types 2021-01-22 10:58:06 -06:00
Igor Postelnik ce22dd681a TUN-3195: Don't colorize console logs when stderr is not a terminal 2021-01-21 17:03:51 -06:00
Nuno Diegues a129572749 TUN-3773: Add back pprof endpoints 2021-01-19 16:51:15 +00:00
Nuno Diegues 2d0b86f2e4 TUN-3777: Fix /ready endpoint for classic tunnels
Classic tunnels flow was triggering an event for RegisteringTunnel for
every connection that was about to be established, and then a Connected
event for every connection established.

However, the RegistreringTunnel event had no connection ID, always
causing it to unset/disconnect the 0th connection making the /ready
endpoint report incorrect numbers for classic tunnels.
2021-01-19 13:02:44 +00:00
Nuno Diegues ffac598fab Release 2021.1.5 2021-01-18 12:06:38 +00:00
Igor Postelnik 4a76ed12e7 TUN-3766: Print flags defined at all levels of command hierarchy, not just locally defined flags for a command. This fixes output of overriden settings for subcommand. 2021-01-18 11:16:42 +00:00
Igor Postelnik 04b1e4f859 TUN-3738: Refactor observer to avoid potential of blocking on tunnel notifications 2021-01-18 11:16:23 +00:00
Nuno Diegues 8c9d725eeb TUN-3768: Reuse file loggers
This change is focused on fixing rotating loggers in Windows
where it was failing due to Windows file semantics disallowing
the rotation while that file was still being open (because we
had multiple lumberjacks pointing to the same file).

This is fixed by ensuring the initialization happens only once.
2021-01-18 10:16:20 +00:00
Nuno Diegues de27361ffa TUN-3767: Tolerate logging errors
This addresses a bug where logging would not be output when
cloudflared was run as a Windows Service.

That was happening because Windows Services have no stderr/out,
and the ConsoleWriter log was failing inside zerolog, which would
then not proceed to the next logger (the file logger).

We now overcome that by using our own multi writer that is resilient
to errors.
2021-01-18 10:16:09 +00:00
Nuno Diegues 8da61274b8 TUN-3765: Fix doubly nested log output by logfile option 2021-01-17 19:58:00 +00:00
Nuno Diegues 146c2d944a TUN-3594: Log ingress response at debug level 2021-01-15 19:06:54 +00:00
Nuno Diegues d90a111c1d Release 2021.1.4 2021-01-14 16:44:10 +00:00
Nuno Diegues d26df1c248 TUN-3759: Single file logging output should always append 2021-01-14 16:23:56 +00:00
Nuno Diegues 42cdb557a0 Release 2021.1.3 2021-01-14 13:20:02 +00:00
Nuno Diegues 7c3ceeeaef TUN-3757: Fix legacy Uint flags that are incorrectly handled by ufarve library
The following UInt flags:
 * Uint64 - heartbeat-count, compression-quality
 * Uint - retries, port, proxy-port

were not being correctly picked from the configuration YAML
since the multi origin refactor

This is due to a limitation of the ufarve library, which we
overcome for now with handling those as Int flags.
2021-01-14 13:08:55 +00:00
Nuno Diegues 391facbedf TUN-3756: File logging output must consider the directory 2021-01-14 11:53:35 +00:00
Nuno Diegues 1c9f3ac7d4 Release 2021.1.2 2021-01-14 00:24:44 +00:00
Nuno Diegues 6852047ef1 TUN-3747: Fix logging in Windows 2021-01-13 23:23:31 +00:00
Nuno Diegues a2109e4a78 Release 2021.1.1 2021-01-13 17:59:15 +00:00
Nuno Diegues 01f0d67875 TUN-3744: Fix compilation error in windows service 2021-01-13 16:20:41 +00:00
Nuno Diegues 33d9b4027b Release 2021.1.0 2021-01-13 15:18:31 +00:00
Nuno Diegues 9ed536c990 TUN-3738: Consume UI events even when UI is disabled
Not doing so was causing cloudflared to become stuck after
some time. This would happen because the Observer pattern
was sending events to the UI channel (that has 16 slots) but
no one was consuming those when the UI is not enabled (which
is the default case).

Hence, events (such as connection disconnect / reconnect) would
cause that buffer to be full and cause cloudflared to become
apparently stuck, in the sense that the connections would not be
reconnected.
2021-01-13 13:10:30 +00:00
Adam Chalmers 119d96cdc1 TUN-3670: Update Teamnet API gateway prefixes 2021-01-11 13:40:44 -06:00
Adam Chalmers 94ca4f98dd Review from Igor 2021-01-11 19:36:31 +00:00
Adam Chalmers b601b24f52 Adam's suggestions 2021-01-11 19:36:31 +00:00
TownLake b40d8557cf TUN-3691: Edit Teamnet help text 2021-01-11 19:36:31 +00:00
Areg Harutyunyan a34604cfc7 Merge remote-tracking branch 'upstream/master' 2021-01-08 13:50:11 +00:00
Samuel Rhea 1c1a8f21b2 Update to add deprecated version note (#271) 2021-01-08 11:38:20 +00:00
Adam Chalmers 78ffb1b846 TUN-3688: Subcommand for users to check which route an IP proxies through 2021-01-07 15:31:26 +00:00
Adam Chalmers 92fd039440 TUN-3722: Teamnet API paths include /network 2021-01-06 17:54:51 -06:00
Areg Harutyunyan 55bf904689 TUN-3471: Add structured log context to logs 2021-01-05 20:21:16 +00:00
Adam Chalmers abab78730d TUN-3708: Better info message about system root certpool on Windows 2020-12-31 10:10:47 -06:00
Adam Chalmers b855e33327 TUN-3706: Quit if any origin service fails to start 2020-12-30 13:48:19 -06:00
Adam Chalmers 32336859f8 TUN-3689: Delete routes via cloudflared CLI 2020-12-29 13:53:48 -06:00
Adam Chalmers 94c639d225 TUN-3669: Teamnet commands to add/show Teamnet routes. 2020-12-29 17:39:08 +00:00
Areg Harutyunyan 2ea491b1d0 TUN-3607: Set up single-file logger with zerolog 2020-12-23 14:15:39 -06:00
Areg Harutyunyan 9bc1c0c70b TUN-3472: Set up rolling logger with zerolog and lumberjack 2020-12-23 14:15:37 -06:00
Areg Harutyunyan 870f5fa907 TUN-3470: Replace in-house logger calls with zerolog 2020-12-23 14:15:17 -06:00
Maxime Guerreiro 06404bf3e8 TUN-3650: Remove unused awsuploader package 2020-12-15 18:02:17 +01:00
Adam Chalmers 3b4ed70cf3 Release 2020.12.0 2020-12-08 16:17:24 -06:00
Adam Chalmers d45ca67498 TUN-3612: Upgrade to Go 1.15.6 2020-12-04 23:24:16 +00:00
Sudarsan Reddy 1c0dac77d7 TUN-3599: improved delete if credentials isnt found.
Tunnel delete is successful even if we don't find the credentials
file in the user's filesystem. We no longer "error" indicating this
is a problem. This fix also enables chaining of the delete command
by removing a pre-mature return if the credentials file is not found.
2020-12-04 11:44:13 +00:00
Samuel Rhea 65e70650dc Update to add deprecated version note (#271) 2020-12-03 21:32:23 +04:00
Adam Chalmers 38fb0b28b6 TUN-3593: /ready endpoint for k8s readiness. Move tunnel events out of UI package, into connection package. 2020-12-02 15:22:59 -06:00
cthuang bda8fe2fbe TUN-3594: Log response status at debug level 2020-11-27 12:28:20 +00:00
Adam Chalmers 6a8c7c0727 Release 2020.11.11 2020-11-25 10:09:42 -06:00
Adam Chalmers 69fd502db3 TUN-3581: Tunnels can be run by name using only --credentials-file, no
origin cert necessary.
2020-11-25 09:54:28 -06:00
Michael Borkenstein fcc393e2f0 AUTH-3221: Saves org token to disk and uses it to refresh the app token 2020-11-24 21:38:59 +00:00
Areg Harutyunyan cad58b9b57 TUN-3561: Unified logger configuration 2020-11-23 16:49:07 +00:00
Joe Groocock 78cb60b85f EDGEPLAT-2958 remove deb-compression, defaulting to gzip
dpkg does not support bzip2 compression, so fails to unpack and install
the built package. By omitting the option, fpm defaults to gzip which is
the default supported option by dpkg.

Signed-off-by: Joe Groocock <jgroocock@cloudflare.com>
2020-11-23 16:27:11 +00:00
Adam Chalmers a08a7030d1 TUN-3578: cloudflared tunnel route dns should allow wildcard subdomains 2020-11-23 09:37:46 -06:00
Adam Chalmers 87203bbe25 Release 2020.11.10 2020-11-20 12:25:44 -06:00
Joe Groocock 11acb50cf7 EDGEPLAT-2958 build cloudflared for Bullseye
Signed-off-by: Joe Groocock <jgroocock@cloudflare.com>
2020-11-20 18:24:58 +00:00
Adam Chalmers 23f2a04ed7 TUN-3562: Fix panic when using bastion mode ingress rule 2020-11-20 11:20:39 -06:00
cthuang 1805261263 Release 2020.11.9 2020-11-19 09:29:12 +00:00
Adam Chalmers 53de779a0a TUN-3544: Upgrade to Go 1.15.5 2020-11-18 16:13:54 -06:00
Adam Chalmers b7e91466f5 TUN-3558: cloudflared allows empty config files 2020-11-18 21:13:06 +00:00
Troy Varney 4c1b89576c DEVTOOLS-7936: Remove redundant chgrp from publish
This removes the redundant chgrp command from the publish step when
pushing packages to our public repositories. The directory being pushed
to has the setgid bit set on it, which means that we don't need to force
the group using this command. Further, attempting to do so resulted in
an error as the cfsync user does not have the appropriate permissions to
use the chgrp command.
2020-11-18 19:35:26 +00:00
cthuang a1a554a29d TUN-3559: Share response meta header with other packages 2020-11-18 16:51:03 +00:00
cthuang fdb1f961b3 TUN-3557: Detect SSE if content-type starts with text/event-stream 2020-11-18 15:59:41 +00:00
Adam Chalmers 293b9af4a7 Release 2020.11.8 2020-11-17 17:15:50 -06:00
Adam Chalmers 029f7e0378 TUN-3555: Single origin service should default to localhost:8080 2020-11-17 23:12:32 +00:00
Adam Chalmers 58c5e25b9a Release 2020.11.7 2020-11-16 14:13:04 -06:00
Adam Chalmers 25e72f7760 TUN-3549: Use a separate handler for each websocket proxy 2020-11-16 20:05:35 +00:00
Adam Chalmers 7613410855 TUN-3548, TUN-3547: Bastion mode can be specified as a service, doesn't
require URL.
2020-11-16 20:04:36 +00:00
cthuang c40cb7dc56 TUN-3514: Stop setting --is-autoupdated flag after autoupdate because it can break named tunnel running in k8s 2020-11-16 09:40:38 +00:00
Adam Chalmers 9ae5f306bf Release 2020.11.6 2020-11-14 20:13:18 -06:00
Adam Chalmers 6159cb536f TUN-3546: Fix panic in tlsconfig.LoadOriginCA 2020-11-14 20:03:29 -06:00
Adam Chalmers ae4f687754 Release 2020.11.5 2020-11-13 17:39:51 -06:00
Adam Chalmers ce7d0572fe TUN-3543: ProxyAddress not using default in single-origin mode 2020-11-13 17:27:55 -06:00
Troy Varney 030b768eeb DEVTOOLS-7936: Set permissions on public packages
This updates the public repository upload process to change the group on
the uploaded files to `cf` and adds the write permission for members of
the group. This should allow the `cf` user to properly overwrite the
file when signing it.
2020-11-13 19:02:40 +00:00
Adam Chalmers f36dc6cfd8 TUN-3540: Better copy in ingress rules error messages 2020-11-12 17:57:19 -06:00
cthuang 6dad2bf9c4 Release 2020.11.4 2020-11-12 18:09:09 +00:00
Adam Chalmers 1475cf61ee TUN-3534: Specific error message when credentials file is a .pem not .json 2020-11-12 16:38:24 +00:00
cthuang ebc003d478 TUN-3514: Transport logger write to UI when UI is enabled 2020-11-11 15:21:00 +00:00
cthuang 543169c893 TUN-3490: Make sure OriginClient implementation doesn't write after Proxy return 2020-11-11 15:21:00 +00:00
cthuang d5769519b2 TUN-3489: Add unit tests to cover proxy logic in connection package of cloudflared 2020-11-11 15:21:00 +00:00
cthuang 5974fb4cfd TUN-3500: Integrate replace h2mux by http2 work with multiple origin support 2020-11-11 15:20:57 +00:00
cthuang eef5b78eac TUN-3480: Support SSE with http2 connection, and add SSE handler to hello-world server 2020-11-11 15:12:17 +00:00
cthuang 6b86f81c4a TUN-3403: Unit test for origin/proxy to test serving HTTP and Websocket 2020-11-11 15:12:15 +00:00
cthuang a490443630 TUN-3458: Upgrade to http2 when available, fallback to h2mux when we reach max retries 2020-11-11 15:11:42 +00:00
cthuang b5cdf3b2c7 TUN-3456: New protocol option auto to automatically select between http2 and h2mux 2020-11-11 15:11:42 +00:00
cthuang 6886e5f90a TUN-3467: Serialize cf-cloudflared-response-meta during package initialization using jsoniter 2020-11-11 15:11:42 +00:00
cthuang 9ac40dcf04 TUN-3462: Refactor cloudflared to separate origin from connection 2020-11-11 15:11:42 +00:00
cthuang a5a5b93b64 TUN-3420: Establish control plane and send RPC over control plane 2020-11-11 15:11:42 +00:00
cthuang cb39f26f27 TUN-3406: Proxy websocket requests over Go http2 2020-11-11 15:11:42 +00:00
cthuang 8d7b2575ba TUN-3400: Use Go HTTP2 library as transport to connect with the edge 2020-11-11 15:11:42 +00:00
cthuang d7498b0c03 TUN-3449: Use flag to select transport protocol implementation 2020-11-11 15:11:42 +00:00
Adam Chalmers 59051ba425 Release 2020.11.3 2020-11-11 08:52:04 -06:00
cthuang d7268af555 TUN-3533: Set config for single origin ingress 2020-11-11 13:54:20 +00:00
Adam Chalmers 8c1deb4064 Release 2020.11.2 2020-11-10 17:02:12 -06:00
Adam Chalmers 986102401b Release 2020.11.1 2020-11-10 15:43:33 -06:00
Adam Chalmers 196762d9d3 TUN-3527: More specific error for invalid YAML/JSON 2020-11-10 21:42:26 +00:00
Samuel Rhea 350a6f2bf5 Update README.md (#256)
* Update README.md

* Update README.md

* Update README.md
2020-11-10 21:36:25 +00:00
Adam Chalmers 3de3b2f734 Release 2020.11.0 2020-11-10 09:44:29 -06:00
Adam Chalmers 4698ec8dee TUN-3461: Show all origin services in the UI 2020-11-10 14:25:37 +00:00
Igor Postelnik 8c6181db9f TUN-3524: Don't ignore errors from app-level action handler (#248) 2020-11-10 13:06:49 +00:00
Adam Chalmers 64d3836645 TUN-3522: ingress validate checks that the config file exists 2020-11-09 12:31:50 -06:00
Adam Chalmers 87e2679744 TUN-3516: Better error message when parsing invalid YAML config 2020-11-09 10:35:28 -06:00
cthuang 61c814bd79 TUN-3506: OriginService needs to set request host and scheme for websocket requests 2020-11-05 16:34:02 +00:00
cthuang be9a558867 TUN-3503: Matching ingress rule should not take port into account 2020-11-05 15:36:12 +00:00
cthuang b00bfe8e0b TUN-3505: Response body for status code origin returns EOF on Read 2020-11-05 14:47:46 +00:00
Adam Chalmers bc015995d8 TUN-3484: OriginService that responds with configured HTTP status 2020-11-04 21:28:33 +00:00
Adam Chalmers d01770107e TUN-3492: Refactor OriginService, shrink its interface 2020-11-04 21:28:33 +00:00
cthuang 18c359cb86 TUN-3494: Proceed to create tunnel if at least one edge address can be resolved 2020-10-30 18:18:34 +00:00
Adam Chalmers e933ef9e1a TUN-2640: Users can configure per-origin config. Unify single-rule CLI
flow with multi-rule config file code.
2020-10-30 07:42:20 -05:00
cthuang ea71b78e6d TUN-3478: Increase download timeout to 60s 2020-10-22 10:38:18 +01:00
Igor Postelnik 04fa10ea7b Release 2020.10.2 2020-10-21 13:42:12 -05:00
Dalton 8e0be7cff1 AUTH-3185 fixed indention error 2020-10-21 12:14:39 -05:00
cthuang 3c13080fe1 Release 2020.10.1 2020-10-21 17:09:19 +01:00
Igor Postelnik b6cd54d854 TUN-3459: Make service install on linux use named tunnels 2020-10-21 10:46:29 -05:00
cthuang f0cfad8efa TUN-3476: Fix conversion to string and int slice 2020-10-21 16:03:25 +01:00
Igor Postelnik ed54d150fe Move raw ingress rules to config package 2020-10-20 12:00:34 -05:00
Igor Postelnik ca4887fb19 Split out typed config from legacy command-line switches; refactor ingress commands and fix tests 2020-10-20 10:10:19 -05:00
Igor Postelnik eaf03305bd TUN-3475: Unify config file handling with typed config for new fields 2020-10-20 08:55:30 -05:00
Igor Postelnik 051908aaef TUN-3463: Let users run a named tunnel via config file setting 2020-10-19 12:27:18 +00:00
Adam Chalmers acd03e36e6 TUN-3465: Use Go 1.15.3 2020-10-15 15:55:16 -05:00
Adam Chalmers c96b9e8d8f TUN-3464: Newtype to wrap []ingress.Rule 2020-10-15 12:48:14 -05:00
Adam Chalmers 4a4a1bb6b1 TUN-3441: Multiple-origin routing via ingress rules 2020-10-13 08:55:17 -05:00
Adam Chalmers 0eebc7cef9 TUN-3438: move ingress into own package, read into TunnelConfig 2020-10-12 16:33:22 +00:00
Igor Postelnik 53a1fa46a8 TUN-3452: Fix loading of flags from config file for tunnel run subcommand. This change also cleans up building of tunnel subcommand list, hides deprecated subcommands and improves help. 2020-10-09 12:07:17 -05:00
Adam Chalmers 86a7af3dc4 TUN-3451: Cloudflared tunnel ingress command 2020-10-08 22:06:40 +00:00
Adam Chalmers 407c9550d7 TUN-3440: 'tunnel rule' command to test ingress rules 2020-10-08 22:06:40 +00:00
Adam Chalmers 2319003e10 TUN-3439: 'tunnel validate' command to check ingress rules 2020-10-08 22:06:40 +00:00
cthuang 1e6399c2f0 TUN-3446: Use go 1.15.2 and add a step to build cloudflared in the dev Dockerfile 2020-10-07 21:28:54 +00:00
Adam Chalmers b05d826d22 TUN-3436, TUN-3437: Parse ingress from YAML, ensure last rule catches everything 2020-10-07 16:36:28 +00:00
Dalton d21989dba4 AUTH-3148 fixed cloudflared copy and match all the files in the checksum upload 2020-10-06 11:39:40 -05:00
Igor Postelnik a986355a61 Release 2020.10.0 2020-10-06 09:54:04 -05:00
cthuang 812244d79f TUN-3443: Decode as v4api response on non-200 status 2020-10-06 11:34:41 +00:00
Dalton be7b7c7149 AUTH-2993 cleaned up worker service tests 2020-10-02 13:01:05 -05:00
cthuang 03d7320a44 TUN-3430: Copy flags to configure proxy to run subcommand, print relevant tunnel flags in help 2020-10-01 21:44:27 +00:00
Dalton ba4c8d8849 AUTH-2993 added workers updater logic 2020-10-01 14:41:58 -05:00
cthuang 2c9b7361b7 TUN-3427: Define a struct that only implements RegistrationServer in tunnelpogs 2020-10-01 09:08:32 +01:00
Lee Valentine 8e8513e325 TRAFFIC-448: allow the user to specify the proxy address and port to bind to, falling back to 127.0.0.1 and random port if not specified 2020-09-25 09:54:40 -05:00
Adam Chalmers 607dcff697 Release 2020.9.3 2020-09-24 12:07:27 -05:00
Adam Chalmers 1d1cf6a827 Release 2020.9.2 2020-09-24 19:23:53 +04:00
Adam Chalmers 61f3fab757 TUN-3410: Request the v1 Tunnelstore API 2020-09-24 19:23:53 +04:00
Lee Valentine e2ff7f65fc TRAFFIC-448: build cloudflare for junos and publish to s3 2020-09-24 19:23:53 +04:00
Dalton da0792a321 AUTH-3109 upload the checksum to workers kv on github releases 2020-09-23 18:01:43 +04:00
Bojan Zelic fa061ab54e updater service exit code should be 11 2020-09-22 18:26:56 +04:00
cthuang 197d65659a TUN-3291: cloudflared tunnel run -h explains how to use flags from parent command 2020-09-21 19:07:30 +00:00
Igor Postelnik 85d0afd3b0 TUN-3295: Show route command results 2020-09-21 16:32:08 +00:00
Michael Borkenstein 20623255dd AUTH-3110-use-cfsetup-precache 2020-09-18 16:26:33 -05:00
Dalton afa5e68fe5 AUTH-3103 CI build fixes 2020-09-18 19:33:30 +00:00
Areg Harutyunyan 747427f816 TUN-3216: UI improvements 2020-09-17 13:22:08 +04:00
Rachel Williams f99b6c6421 TUN-3335: Dynamically set connection table size for UI 2020-09-17 11:52:10 +04:00
Rachel Williams 250bc54110 TUN-3333: Add text to UI explaining how to exit 2020-09-17 11:52:10 +04:00
Rachel Williams bb530b87dd TUN-3328: Filter out free tunnel has started log from UI 2020-09-17 11:52:10 +04:00
Rachel Williams 02587c1edc TUN-3321: Add box around logs on UI 2020-09-17 11:52:10 +04:00
Rachel Williams 26fc20d406 TUN-3198: Handle errors while running tunnel UI 2020-09-17 11:52:10 +04:00
Rachel Williams 8a829b773a Rebased and passed TunnelEventChan to LogServerInfo in new ReconnectTunnel function 2020-09-17 11:52:10 +04:00
Rachel Williams 71b98e6111 TUN-3261: Display connections on UI for free classic tunnels 2020-09-17 11:52:10 +04:00
Rachel Williams fee13dc62f TUN-3255: Update UI to display URL instead of hostname 2020-09-17 11:52:10 +04:00
Rachel Williams 094e0c7592 TUN-3238: Update UI when connection re-connects 2020-09-17 11:52:10 +04:00
Rachel Williams b57a953caa TUN-3200: Add connection information to UI 2020-09-17 11:52:10 +04:00
Rachel Williams d8ebde37ca TUN-3201: Create base cloudflared UI structure 2020-09-17 11:52:07 +04:00
Adam Chalmers 0708a49848 Release 2020.9.1 2020-09-16 12:54:59 -05:00
Adam Chalmers b26f3082e6 Use Go 1.15.2 2020-09-16 12:45:49 -05:00
Igor Postelnik cb6d424765 TUN-3395: Improve help for list command 2020-09-16 16:48:43 +00:00
Igor Postelnik 5753aa9f18 TUN-3294: Perform basic validation on arguments of route command; remove default pool name which wasn't valid 2020-09-16 16:48:43 +00:00
Igor Postelnik bfae12008d TUN-3395: Improve help for list command 2020-09-16 08:30:24 -05:00
Igor Postelnik c52e0dc8ef TUN-3395: Unhide named tunnel subcommands, tweak help 2020-09-15 12:41:15 +00:00
Adam Chalmers 55346444c9 Release 2020.9.0 2020-09-14 16:41:59 -05:00
Igor Postelnik ba785ec58d TUN-3293: Try to use error information from the body of a failed tunnelstore reresponse if available 2020-09-11 17:12:17 -05:00
Adam Chalmers 3be2545ad4 TUN-3292: Mention cleanup in tunnel run help. 2020-09-10 11:02:31 -05:00
Dalton 4a8597c245 AUTH-2653 add notarization to mac build 2020-09-09 19:43:00 +00:00
Igor Postelnik 741cd66c9e TUN-3375: Upgrade coredns and prometheus dependencies 2020-09-09 13:14:38 -05:00
Adam Chalmers 7acea1ac99 TUN-3375: Upgrade x/text and gorilla websocket deps 2020-09-09 12:07:35 -05:00
cthuang 22d771b51d TUN-3284: Use cloudflared/<version> as user agent of tunnelstore client 2020-09-09 10:34:26 +01:00
Dalton 00d6ab2eb7 AUTH-2653 search for the certificate and not the identity 2020-09-08 12:21:10 -05:00
Dalton cc0a5ac3df AUTH-2653 disble error pipe to see what is failing 2020-09-08 10:41:06 -05:00
cthuang 5fb938d6d6 TUN-3345: tunnel run accepts name of tunnel as argument 2020-09-05 09:32:50 +00:00
Dalton cd5bdb837e AUTH-2653 handle duplicate key import errors 2020-09-04 17:08:09 -05:00
Dalton 107abf9d29 AUTH-2653 changed to proper file extension 2020-09-04 20:23:42 +00:00
Adam Chalmers 218ee30206 TUN-3377: Tunnel route should check dns/lb before checking tunnel ID 2020-09-04 18:44:06 +00:00
Dalton 8764fbfdfa AUTH-2653 renabled signing 2020-09-04 11:29:17 -05:00
Dalton 2463d6b92c TUN-3352 extra debug logging for websockets 2020-08-31 17:14:02 -05:00
Dalton 810d268c99 AUTH-2016 fixed variable fail 2020-08-20 12:56:12 -05:00
Dalton f5b479dbbc Release 2020.8.2 2020-08-20 11:15:11 -05:00
Dalton b52728e829 AUTH-3021 fixed the git version call by using the older flag 2020-08-20 16:13:10 +00:00
cthuang 8eeb452cce TUN-3268: Each connection has its own event digest to reconnect 2020-08-20 16:44:22 +01:00
Dalton 9323844ea7 Release 2020.8.1 2020-08-19 21:59:21 -05:00
Dalton b698fe5ef3 AUTH-2864 - add macos build to github release 2020-08-19 21:31:50 -05:00
Dalton edb980d439 AUTH-2653 disabling signing until we can get the keys 2020-08-19 15:32:49 -05:00
Dalton 310bd0dbf1 AUTH-2712 fixed the mac build script 2020-08-19 14:57:43 +00:00
Adam Chalmers 1a96889141 TUN-3286: Use either ID or name in Named Tunnel subcommands. 2020-08-19 14:39:45 +00:00
Dalton Cherry 60de05bfc1 AUTH-2712 added MSI build for a windows agent 2020-08-17 14:44:28 -05:00
cthuang 3deef6197f TUN-3213: Create, route and run named tunnels in one command 2020-08-17 19:38:38 +00:00
Dalton 70114c2145 AUTH-2977 log file protection 2020-08-17 17:55:58 +00:00
Dalton 5499c77e62 AUTH-2975 don't check /etc on windows 2020-08-17 12:40:36 -05:00
cthuang 292a7f07a2 TUN-3243: Refactor tunnel subcommands to allow commands to compose better 2020-08-11 10:02:52 +01:00
Igor Postelnik 679f36303a TUN-3242: Build with go 1.14 2020-08-10 23:17:23 +00:00
Adam Chalmers 1b61d699c4 TUN-3237: By default, don't show connections that are pending reconnect 2020-08-07 10:16:13 -05:00
cthuang a7562dff68 TUN-3233: List tunnels support filtering by deleted, name, existed at and id 2020-08-07 10:09:26 +01:00
Dalton 8daf1e882f AUTH-2857 update homebrew script to use new url 2020-08-04 16:47:10 -05:00
Michael Borkenstein 20466dacb7 Release 2020.8.0 2020-08-03 12:18:44 -06:00
Dalton 5b3b592108 AUTH-2927 run message update after all github builds are done 2020-08-03 10:12:03 -05:00
Adam Chalmers 9952ce0069 TUN-3221: ConnectionOptions tracks numPreviousAttempts. 2020-07-31 20:22:33 +00:00
cthuang 1cbc8fb8ac TUN-3220: tunnel route reports created route 2020-07-31 17:48:58 +01:00
Michael Borkenstein edc69694cb AUTH-2714: Adds arm64 cloudflared build 2020-07-29 13:12:57 -06:00
Igor Postelnik bd15c6b8c3 TUN-3208: Reduce copies and allocations on h2mux write path. Pre-allocate 16KB write buffer on the first write if possible. Use explicit byte array for chunks on write thread to avoid copying through intermediate buffer due to io.CopyN.
benchmark                                    old ns/op     new ns/op     delta
BenchmarkSingleStreamLargeResponseBody-8     17786594      12163494      -31.61%

benchmark                                    old allocs     new allocs     delta
BenchmarkSingleStreamLargeResponseBody-8     17086          15869          -7.12%

benchmark                                    old bytes     new bytes     delta
BenchmarkSingleStreamLargeResponseBody-8     58215169      21604391      -62.89%
2020-07-29 14:30:12 +00:00
Igor Postelnik 42fe2e7266 TUN-3208: Add benchmark for large response write 2020-07-29 14:30:12 +00:00
Igor Postelnik 44e3be2c88 TUN-3209: improve performance and reduce allocations during user header serialization from h1 to h2
benchmark                                    old ns/op     new ns/op     delta
BenchmarkH1ResponseToH2ResponseHeaders-4     10360         5048          -51.27%

benchmark                                    old allocs     new allocs     delta
BenchmarkH1ResponseToH2ResponseHeaders-4     135            26             -80.74%

benchmark                                    old bytes     new bytes     delta
BenchmarkH1ResponseToH2ResponseHeaders-4     8543          3667          -57.08%
2020-07-29 14:29:07 +00:00
Igor Postelnik 61d5461138 TUN-3209: Add benchmark for header serialization 2020-07-29 14:29:07 +00:00
Michael Borkenstein b696ca8b1c Removes centos 6 build 2020-07-29 14:01:34 +00:00
Dalton 6a4d020c27 AUTH-2016 adds sha256 hashes to releases 2020-07-27 10:36:31 -05:00
Igor Postelnik 4791ba3b87 TUN-3194: Don't render log output when level is not enabled 2020-07-23 18:36:31 -05:00
Igor Postelnik cf1c9a3083 TUN-3192: Use zone ID in tunnelstore request path; improve debug logging 2020-07-21 18:07:50 -05:00
Igor Postelnik d61e3fb130 TUN-3190: Initialize logger using command line flags in tunnels subcommands 2020-07-21 17:25:48 +00:00
Dalton ca7d6797e1 AUTH-2902 redirect with just the root host on curl commands 2020-07-21 11:08:31 -05:00
Areg Harutyunyan af0d04d0f3 Release 2020.7.4 2020-07-21 11:02:29 +01:00
Ivan Babrou 6274567e16 Build cloudflared for arm64 on native agents 2020-07-20 17:42:50 -07:00
cthuang 8836ee1dda TUN-3156: Add route subcommand under tunnel 2020-07-17 05:51:24 +08:00
Michael Borkenstein 7afde79600 AUTH-2890: adds error handler to cli actions 2020-07-15 14:33:36 +00:00
Rachel Williams 3d782f7162 TUN-3048: Handle error when user tries to delete active tunnel 2020-07-14 15:32:46 +00:00
Michael Borkenstein 058598ea58 Release 2020.7.3 2020-07-13 13:32:38 -05:00
Michael Borkenstein 8e617df914 Change scp command to use file glob that matches both cloudflared rpms and debs 2020-07-13 12:15:01 -05:00
Michael Borkenstein 7260f3e487 Release 2020.7.2 2020-07-13 09:57:36 -05:00
Michael Borkenstein a42b66e8bd AUTH-2872: Adds centos-6 build 2020-07-10 20:39:50 +00:00
Michael Borkenstein 28d556b8d4 AUTH-2858: Set file to disable autoupdate 2020-07-10 18:03:07 +00:00
Areg Harutyunyan 33701f50ec Merge branch 'master' of github.com:cloudflare/cloudflared 2020-07-08 16:35:04 +01:00
Michael Borkenstein abfeebf67d AUTH-2871: fix rpm builds 2020-07-08 14:39:28 +00:00
Sven Höxter 3b293048f4 beautify package meta information generated by fpm (#218)
Current Debian and RPM meta information are very generic, e.g.
$ apt-cache show cloudflared
Package: cloudflared
Version: 2020.7.0
License: unknown
Vendor: @k8s-managed-krwtk.teamcity-agents.svc.cluster.local
Architecture: amd64
Maintainer: <@k8s-managed-krwtk.teamcity-agents.svc.cluster.local>
Installed-Size: 42883
Section: default
Priority: extra
Homepage: http://example.com/no-uri-given
Description: no description given
2020-07-08 15:36:42 +01:00
Igor Postelnik ac3638f6b1 TUN-3169: Move on to the next address when edge returns duplicate connection. There's no point in trying to connect to the same address since it will be hashed to the same metal.
Improve logging of errors from serve tunnel loop, hide useless context cancelled error.
2020-07-07 16:35:44 -05:00
Rachel Williams f7ff41f1dc TUN-3150: cloudflared tunnel list's table should use intelligent column width 2020-07-07 15:48:56 +00:00
cthuang f5c8ff77e9 TUN-3008: Implement cloudflared tunnel cleanup command 2020-07-07 21:56:46 +08:00
cthuang 87e06100df TUN-3131: Allow user to specify tunnel credentials path, and remove it in tunnel delete command 2020-07-07 14:22:08 +08:00
Michael Borkenstein 1ed9e0fceb AUTH-2644: Change install location and add man page 2020-07-06 19:27:25 +00:00
Michael Borkenstein b9d6aaebbe Release 2020.7.1 2020-07-06 11:47:37 -05:00
Adam Chalmers a99780ed9d TUN-3161: Upgrade golang.org/x/ deps 2020-07-06 16:42:48 +00:00
Dalton 92765b4261 AUTH-2850 log config file path 2020-07-06 16:32:57 +00:00
Robert McNeil 8b88b4a403 DEVTOOLS-7321: Push GitHub homebrew updates to master 2020-07-02 16:51:04 +00:00
Michael Borkenstein 2ce6720a6e AUTH-2854: Create cloudflared RPMs 2020-07-01 14:50:38 -05:00
Michael Borkenstein 772ccc9607 Release 2020.7.0 2020-07-01 10:52:35 -05:00
Michael Borkenstein 7724ff8176 AUTH-2860: Fix builds 2020-07-01 15:51:02 +00:00
Igor Postelnik 2a3d486126 TUN-3007: Implement named tunnel connection registration and unregistration.
Removed flag for using quick reconnect, this logic is now always enabled.
2020-07-01 04:19:30 +00:00
Rachel Williams 932e383051 TUN-3140: Add timestamps to terminal log entries 2020-06-30 22:28:22 +00:00
Adam Chalmers dbe3516204 Release 2020.6.6 2020-06-30 12:55:54 -05:00
Dalton 6fc9f1b405 AUTH-2849 all log output to stderr 2020-06-30 16:54:31 +00:00
Michael Borkenstein 370c17e48c AUTH-2718: Add target for publishing deb to pkg.cloudflare repo 2020-06-30 14:46:24 +00:00
Michael Borkenstein 92da73aa9d AUTH-2652: Update cloudflare repo 2020-06-26 14:24:20 +00:00
Dalton 0c65daaa7d AUTH-2712 mac package build script and better config file handling when started as a service 2020-06-25 16:44:57 -05:00
Michael Borkenstein b46acd7f63 AUTH-2685: Adds script to create release 2020-06-25 18:39:37 +00:00
Michael Borkenstein e3a9aa4296 AUTH-2652: Adds .docker-images to push images to docker hub 2020-06-23 11:35:48 -05:00
cthuang 3886021ba5 TUN-3107: UnregisterConnection shouldn't wrap nil error as RPC error 2020-06-18 21:17:43 +08:00
Adam Chalmers 4d3ebaf984 TUN-3106: Pass NamedTunnel config to StartServer 2020-06-17 23:20:37 +00:00
Dalton 9131e842a5 Release 2020.6.5 2020-06-17 14:40:53 -05:00
Dalton 4f9cfa6542 TUN-3100 make updater report the right text 2020-06-17 17:33:19 +00:00
Adam Chalmers a1a8645294 TUN-3066: Command line action for tunnel run 2020-06-17 17:25:23 +00:00
Adam Chalmers b95b289a8c TUN-3101: Tunnel list command should only show non-deleted, by default 2020-06-16 17:55:33 -05:00
Dalton 425554077f AUTH-2815 flag check was wrong. stupid oversight 2020-06-16 16:19:38 -05:00
Dalton 7b2f286210 fix for a flaky test 2020-06-16 21:18:55 +00:00
Robert McNeil 0f893fab47 DEVTOOLS-7321: Don't skip macOS builds based on tag 2020-06-16 20:36:50 +00:00
Dalton 12c615e2e6 Release 2020.6.4 2020-06-16 14:13:06 -05:00
Dalton 6e5ccd7c85 AUTH-2815 add the log file to support the config.yaml file
added small delay to handle the possiblity of the server not being started yet
2020-06-16 17:48:12 +00:00
Adam Chalmers 3ec500bdbb TUN-3084: Generate and store tunnel_secret value during tunnel creation 2020-06-16 11:45:27 -05:00
Igor Postelnik 8f75feac94 TUN-3085: Pass connection authentication information using TunnelAuth struct 2020-06-16 16:35:46 +00:00
Igor Postelnik 448a7798f7 TUN-3015: Add a new cap'n'proto RPC interface for connection registration as well as matching client and server implementations. The old interface extends the new one for backward compatibility. 2020-06-16 16:35:46 +00:00
cthuang dc3a228d51 Release 2020.6.3 2020-06-16 08:45:03 +08:00
Dalton 554c97a8cb AUTH-2813 adds back a single file support a cloudflared log file 2020-06-16 00:43:09 +00:00
Robert McNeil fd1941dfbe DEVTOOLS-7321: Add openssh-client pkg for missing ssh-keyscan 2020-06-15 17:08:10 -07:00
Adam Chalmers 5fd348dcfc Release 2020.6.2 2020-06-15 16:40:44 +00:00
Dalton 1a6403b2fd AUTH-2694 added destination header support to config file 2020-06-15 10:10:22 -05:00
Dalton 55acf7283c AUTH-2810 added warn for backwards compatibility sake 2020-06-12 22:15:28 +00:00
Adam Chalmers acb7d604fd TUN-3038: Add connections to tunnel list table 2020-06-12 11:43:06 -05:00
Adam Chalmers 6e761cb7ae TUN-3090: Upgrade crypto dep 2020-06-12 00:03:09 -05:00
Dalton ae8d784e36 AUTH-2763 don't redirect from curl command 2020-06-11 15:38:11 -05:00
Dalton c716dd273c AUTH-2648 updated usage text 2020-06-11 11:08:05 -05:00
Dalton eb3c4a7a9f Release 2020.6.1 2020-06-09 15:33:32 -05:00
Dalton 97a901a229 AUTH-2796 fixed windows build 2020-06-09 19:49:13 +00:00
Dalton e054c5bb7b Release 2020.6.0 2020-06-09 11:44:09 -05:00
Dalton 0d87279b2f AUTH-2785 service token flag fix and logger fix 2020-06-09 11:00:56 -05:00
Dalton f8638839c0 AUTH-2729 added log file and level to cmd flags to match config file settings 2020-06-08 19:42:34 +00:00
Dalton 2f70b05c64 AUTH-2169 make access login page more generic 2020-06-08 11:20:30 -05:00
Robert McNeil e02d09a731 DEVTOOLS-7321: Push to a test branch instead of to master 2020-06-08 15:44:28 +00:00
Robert McNeil 0ff491905f DEVTOOLS-7321: Use SSH key from env for pushing to GitHub 2020-06-08 15:44:28 +00:00
Robert McNeil 8c59254488 DEVTOOLS-7321: Add scripts for macOS builds and homebrew uploads 2020-06-08 15:44:28 +00:00
Dalton 9e76e42e3c AUTH-2687 don't copy config unnecessarily 2020-06-08 15:24:36 +00:00
Dalton e376a13025 AUTH-2645 protect against user mistaken flag input 2020-06-05 15:10:09 -05:00
cthuang 3a086e9cc2 TUN-3020: Remove declarative tunnel related RPC code 2020-05-30 06:15:25 +08:00
cthuang fb82b2ced5 TUN-3019: Remove declarative tunnel entry code 2020-05-30 05:54:17 +08:00
Michael Fornaro be0514c5c9 Adding support for multi-architecture images and binaries (#184)
* Allow Dockerfile --build-args to override GOOS and GOARCH defaults

Allow Dockerfile --build-args to override GOOS and GOARCH defaults

Support building multi architecture binaries

remove default OS and ARCH to avoid tag confusion when compiling image through Makefile

Tag image with corrosponding OS and ARCH build variables

updating Makefile

Signed-off-by: Michael Fornaro <20387402+xUnholy@users.noreply.github.com>

* remove duplicate import on windows_service.go

Signed-off-by: Michael Fornaro <20387402+xUnholy@users.noreply.github.com>
2020-05-29 02:06:27 +01:00
Dalton 046be63253 AUTH-2596 added new logger package and replaced logrus 2020-05-27 17:07:19 -05:00
Igor Postelnik a908453aa4 TUN-2928, TUN-2929, TUN-2930: Add tunnel subcommands to interact with tunnel store service 2020-05-21 15:36:49 -05:00
Michael Borkenstein 7a77ead423 AUTH-2682: Create buster build 2020-05-20 11:59:55 -05:00
Michael Borkenstein b89cc22896 AUTH-2369: RDP Bastion prototype 2020-05-19 21:10:50 -05:00
Michael Borkenstein 6a7418e1af AUTH-2686: Added error handling to tunnel subcommand 2020-05-18 15:36:25 -05:00
Dalton df3ad2b223 AUTH-2529 added deprecation text to db-connect command 2020-05-14 10:26:09 -05:00
Dalton 8c870c19a6 AUTH-2505 added aliases 2020-05-13 15:15:08 -05:00
Igor Postelnik 83a1dc93d8 Release 2020.5.1 2020-05-11 16:12:19 -05:00
Dalton 2b7fbbb7b7 AUTH-2588 add DoH to service mode 2020-05-11 17:09:16 +00:00
Michael Borkenstein 2c878c47ed AUTH-2564: error handling and minor fixes 2020-05-11 15:42:35 +00:00
Igor Postelnik 8cc69f2a95 TUN-2860: Enable quick reconnect feature by default 2020-05-07 14:41:55 -05:00
cthuang 7bb9e0af4c Release 2020.5.0 2020-05-06 11:15:29 +08:00
Igor Postelnik fbe2989f61 TUN-2955: Fix connection and goroutine leaks when tunnel conection is terminated on error. Only unregister tunnels that had connected successfully. Close edge connection used to unregister the tunnel. Use buffered channels for error channels where receiver may quit early on context cancellation. 2020-05-06 03:13:24 +00:00
cthuang c3fa4552aa TUN-2872: Exit with non-0 status code when the binary is updated so launchd will restart the service 2020-05-06 05:53:14 +08:00
Adam Chalmers 2cf327ba01 TUN-2943: Copy certutil from edge into cloudflared 2020-05-04 17:37:29 -05:00
Igor Postelnik dd0881f32b TUN-2940: Added delay parameter to stdin reconnect command. 2020-05-01 15:58:19 +00:00
Dalton 41c358147c AUTH-2587 add config watcher and reload logic for access client forwarder 2020-04-29 11:07:35 -05:00
Igor Postelnik 976eb24883 TUN-2921: Rework address selection logic to avoid corner cases 2020-04-27 14:47:47 -05:00
Dalton c782716e49 Release 2020.4.0 2020-04-14 13:00:38 -05:00
Austin Cherry f18209af7d ARES-899: Fixes DoH client as system resolver. Fixes #91 2020-04-14 12:37:59 -05:00
Elvin Tan 6d63f84a75 AUTH-2235 GetTokenIfExists now parses JWT payload for json expiry field to detect if the cached access token is expired 2020-04-14 15:29:30 +00:00
Areg Harutyunyan 1c6ea36e73 TUN-2894: ResponseMetaHeader should be public 2020-04-11 01:01:05 +01:00
Areg Harutyunyan 06f29306cd TUN-2881: Parameterize response meta information header name in the generating function 2020-04-10 20:26:09 +01:00
Areg Harutyunyan 322f909edb TUN-2880: Return metadata about source of the response from cloudflared 2020-04-10 01:01:38 +01:00
Dalton a37da2b165 AUTH-2394 added socks5 proxy 2020-04-07 13:30:28 -05:00
Areg Harutyunyan 710f66b0bb Release 2020.3.2 2020-03-31 17:56:07 +01:00
Areg Harutyunyan 0b2b6c8e12 TUN-2850: Tunnel stripping Cloudflare headers 2020-03-31 16:52:13 +00:00
Adam Chalmers acea15161c TUN-2854: Quick Reconnects should be an optional supported feature 2020-03-31 08:59:00 -05:00
Areg Harutyunyan c76283a2b4 Release 2020.3.1 2020-03-27 17:20:31 +00:00
Areg Harutyunyan ae374c0463 TUN-2846: Trigger debug reconnects from stdin commands, not SIGUSR1 2020-03-27 17:04:21 +00:00
Areg Harutyunyan 42246f986c Release 2020.3.0 2020-03-26 15:17:01 +00:00
Areg Harutyunyan 80f387214c TUN-2796: Implement HTTP2 CONTINUATION headers correctly 2020-03-24 13:46:17 +00:00
Dalton a368fbbe9b AUTH-2394 fixed header for websockets. Added TCP alias 2020-03-23 10:27:53 -05:00
Adam Chalmers 32df01a9da TUN-2823: Bugfix. cloudflared would hang forever if error occurred. 2020-03-19 17:48:42 -05:00
Adam Chalmers 6dcf3a4cbc TUN-2819: cloudflared should close its connections when a signal is sent 2020-03-19 21:02:15 +00:00
Areg Harutyunyan 96f11de7ab TUN-2820: Serialized headers for Websockets 2020-03-19 18:08:58 +00:00
Adam Chalmers d03469b6d3 TUN-2816: cloudflared metrics server should be more discoverable 2020-03-18 09:53:24 -05:00
Adam Chalmers 0cf6ce9aeb TUN-2812: Tunnel proxies and RPCs can share an edge address 2020-03-16 15:50:57 -05:00
Adam Chalmers e8f55cc911 TUN-2756: Set connection digest after reconnect.
Previously it was only being set after RegisterTunnel.
2020-03-13 17:31:05 -05:00
Adam Chalmers 35cee13175 TUN-2807: cloudflared hello-world shouldn't assume it's my first tunnel 2020-03-13 12:09:21 -05:00
Adam Chalmers 5376df5439 TUN-2788: cloudflared should store one ConnDigest per HA connection 2020-03-11 18:19:22 -05:00
Adam Chalmers db9b6541d0 TUN-2797: Fix panic in SetConnDigest by making mutexes values. 2020-03-10 12:21:02 -05:00
Areg Harutyunyan 5bd4028ea7 TUN-2761: Use the new header management functions in cloudflared 2020-03-08 03:38:54 +00:00
Adam Chalmers 1b2a96f96b TUN-2755: ReconnectTunnel RPC now transmits ConnectionDigest 2020-03-06 14:48:16 -06:00
Adam Chalmers d50fee4fa0 TUN-2754: Add ConnDigest to cloudflared and its RPCs 2020-03-05 15:25:15 -06:00
Adam Chalmers 6b3e2b020b TUN-2785: Use reconnect token by default 2020-03-05 16:12:49 +00:00
Areg Harutyunyan 6624a24040 TUN-2748: Insecure randomness vulnerability in github.com/miekg/dns 2020-03-05 13:52:45 +00:00
Nick Vollmar 7b81cf8aa6 TUN-2779: update sample HTML pages 2020-03-03 19:35:41 +00:00
Areg Harutyunyan 26f5f80811 TUN-2776: Add header serialization feature in cloudflared 2020-02-28 17:36:29 +00:00
Roman Iuvshyn 29f4650e25 do not terminate tunnel if origin is not reachable on start-up (#177) 2020-02-27 23:03:00 +00:00
Areg Harutyunyan a14aa0322c TUN-2767: Test for large headers 2020-02-27 22:15:04 +00:00
Areg Harutyunyan 8f9bbcb9a0 Release 2020.2.1 2020-02-27 16:02:52 +00:00
Areg Harutyunyan afc2cd38e1 TUN-2765: Add list of features to tunnelrpc 2020-02-25 23:45:48 +00:00
Cameron Steel a5f67091bf Tweak HTTP host header. Fixes #107 (#168) 2020-02-24 17:08:14 +00:00
Rueian 464bb53049 perf(cloudflared): reuse memory from buffer pool to get better throughput (#161)
* perf(cloudflared): reuse memory from buffer pool to get better throughput

https://github.com/cloudflare/cloudflared/issues/160
2020-02-24 17:06:19 +00:00
Areg Harutyunyan 6488843ac4 TUN-2746: Add the new header management functions 2020-02-21 18:37:38 +00:00
Areg Harutyunyan 52ab2c8227 TUN-2745: Rename existing header management functions 2020-02-20 20:31:34 +00:00
Adam Chalmers 269351bbea TUN-2703: Muxer.Serve terminates when its context is Done 2020-02-20 20:09:10 +00:00
Adam Chalmers a83b6a2155 TUN-2725: Specify in code that --edge is for internal testing only 2020-02-19 16:18:48 -06:00
Adam Chalmers a60c0273f5 TUN-2714: New edge discovery. Connections try to reconnect to the same edge IP. 2020-02-14 19:49:54 +00:00
Areg Harutyunyan d6c2c4ee4a TUN-2717: Function to serialize/deserialize HTTP headers 2020-02-10 19:18:06 +00:00
Adam Chalmers 5b1bea7892 Release 2020.2.0 2020-02-06 16:48:02 -06:00
Nick Vollmar 54b386188a TUN-2651: Fix panic in h2mux reader when a stream error is encountered 2020-01-31 09:59:10 -06:00
Adam Chalmers 386b02355a TUN-2707: Inconsistent cardinality in tunnel error metrics 2020-01-29 12:42:55 -06:00
Adam Chalmers 203b939614 TUN-2690: cloudflared reconnect uses wrong context 2020-01-28 22:26:27 +00:00
Adam Chalmers e729dfc51e TUN-2699: Metrics for Authenticate RPCs 2020-01-28 14:33:41 -06:00
Adam Chalmers d5139d3882 TUN-2696: Add unknown registerRPCName 2020-01-28 11:29:33 -06:00
Adam Chalmers e31ff3a70f TUN-2693: Metrics for ReconnectTunnel 2020-01-28 10:46:37 -06:00
Adam Chalmers dfe61fda88 TUN-2645: Revert "TUN-2645: Turn on reconnect tokens"
This reverts commit 053b2c17f1.
2020-01-27 14:59:07 -06:00
Adam Chalmers 053b2c17f1 TUN-2645: Turn on reconnect tokens 2020-01-13 15:23:42 -06:00
Nick Vollmar 7367827a11 TUN-2646: Make --edge flag work again for local development 2019-12-23 23:11:00 -06:00
Areg Harutyunyan 7d7bdffde5 Release 2019.12.0 2019-12-20 02:03:08 +04:00
Nick Vollmar 7e31b77646 TUN-2637: Manage edge IPs in a region-aware manner 2019-12-19 21:58:23 +00:00
Tyler Cook 87102a2646 Fix timer scheduling for systemd update service (#159) 2019-12-19 20:53:06 +04:00
Niels Hofmans 789ca6f6f4 refactor(docker): optimize Dockerfile (#126)
* refactor(docker): optimize Dockerfile

Remove obsolete upx binary compression
Run as unprivileged user

* Use go 1.13.3

* Use debian buster distroless
2019-12-17 05:25:17 +04:00
Rueian cc2a1d1204 bug(cloudflared): Set the MaxIdleConnsPerHost of http.Transport to proxy-keepalive-connections (#155)
Setting the MaxIdleConns is not enough, the MaxIdleConnsPerHost must be set as well.
Otherwise, http.Transport will use the DefaultMaxIdleConnsPerHost, which is 2,
and then the connection pool will have only 2 connection hold.
2019-12-17 05:02:28 +04:00
Nick Vollmar 6aa48d2eb2 TUN-2554: cloudflared calls ReconnectTunnel 2019-12-13 18:48:48 +00:00
Nick Vollmar 8b43454024 TUN-2631: only notify that activeStreamMap is closed if ignoreNewStreams=true 2019-12-11 17:05:28 -06:00
Nick Vollmar 5e7ca14412 TUN-2555: origin/supervisor.go calls Authenticate 2019-12-06 11:26:54 -06:00
Nick Vollmar b499c0fdba TUN-2608: h2mux.Muxer.Shutdown always returns a non-nil channel 2019-12-04 17:21:30 +00:00
Nick Vollmar bbf31377c2 TUN-2607: add RPC stream helpers 2019-12-03 15:38:16 -06:00
Nick Vollmar 8f4fd70783 TUN-2606: add DialEdge helpers 2019-12-03 14:52:33 -06:00
Tim Bart 92736b2677 bug(cloudflared): nil pointer deference on h2DictWriter Close() (#154)
Unlike other h2DictWriter methods, the Close() method does check whether
w.comp is nil.

This PR adds a check for non nil compressor before attempting to close

Bug: #141
2019-12-03 16:29:40 +04:00
Adam Chalmers 379cb16efe TUN-2591: ReconnectTunnel now sends EventDigest 2019-11-25 18:11:50 +00:00
Ashcon Partovi 43babbc2f9 Fix "happy eyeballs" not being disabled since Golang 1.12 upgrade
* The Dialer.DualStack setting is now ignored and deprecated; RFC 6555 Fast Fallback ("Happy Eyeballs") is now enabled by default. To disable, set Dialer.FallbackDelay to a negative value.
2019-11-25 17:54:20 +00:00
Nick Vollmar dfd1ca5fb5 Merge remote-tracking branch 'github/master' 2019-11-22 13:07:40 -06:00
Adam Chalmers f51712bef9 TUN-2582: EventDigest field in tunnelrpc 2019-11-22 12:04:14 -06:00
Chung-Ting Huang b0d31a0ef3 TUN-2573: Refactor TunnelRegistration into PermanentRegistrationError, RetryableRegistrationError and SuccessfulTunnelRegistration 2019-11-22 17:30:41 +00:00
Khaled Elkhawaga dd614881b6 Fix Docker build failure (#149)
* Enables module-mode in Docker to fix build

* Statically compile binary in Docker to fix missing dependency errors

* Ensure target OS is set to Linux for Docker builds
2019-11-22 20:27:28 +04:00
Adam Chalmers 23e12cf5a3 TUN-2575: Constructors + simpler conversions for AuthOutcome 2019-11-22 02:08:57 +00:00
Chung-Ting Huang 9605f00c77 Release 2019.11.3 2019-11-20 16:33:38 -06:00
Chung-Ting Huang 871c3a194f TUN-2562: Update Cloudflare Origin CA RSA root 2019-11-20 15:37:16 -06:00
Chung-Ting Huang 8ee1faf317 Release 2019.11.2 2019-11-19 15:16:15 -06:00
Chung-Ting Huang c5bacf4d95 TUN-2563: Exposes config_version metrics 2019-11-19 20:42:41 +00:00
Nick Vollmar 1ba5abfdb3 Release 2019.11.1 2019-11-19 19:56:25 +00:00
Adam Chalmers 7173da9359 TUN-2567: AuthOutcome can be turned back into AuthResponse 2019-11-19 04:34:00 +00:00
Nick Vollmar 0676923d24 TUN-2551: TunnelRPC definitions for ReconnectTunnel flow 2019-11-18 14:26:58 -06:00
Adam Chalmers ca7fbf43da TUN-2547: TunnelRPC definitions for Authenticate flow 2019-11-18 16:39:01 +00:00
Nick Vollmar 6ea9b5c3ff TUN-2490: respect original representation of HTTP request path 2019-11-14 22:56:07 -06:00
Nick Vollmar 1d96bccc04 TUN-2178: public API to create new h2mux.MuxedStreamRequest 2019-11-13 14:04:19 -06:00
Adam Chalmers c5af83af66 TUN-2053: Add a /healthcheck endpoint to the metrics server 2019-11-12 16:55:40 -06:00
Ashcon Partovi 759cd019be Add db-connect, a SQL over HTTPS server 2019-11-12 20:34:39 +00:00
Chung-Ting Huang 13bf65ce4e TUN-2506: Expose active streams metrics 2019-11-07 14:09:31 -06:00
Chung-Ting Huang 3a2e12818f Release 2019.11.0 2019-11-07 10:11:56 -06:00
Nick Vollmar e14ec1a1fb TUN-2505: Terminate stream on receipt of RST_STREAM; MuxedStream.CloseWrite() should terminate the MuxedStream.Write() loop 2019-11-06 21:24:18 +00:00
Chung-Ting Huang 3a9a0a0d75 TUN-2489: Delete stream from activestreammap when read and write are both closed 2019-11-05 11:06:11 -06:00
Areg Harutyunyan 068b148e05 TUN-2500: Don't send client registration errors to Sentry 2019-11-05 12:29:11 +00:00
Nick Vollmar e5335b6c1b TUN-2502: Switch to go modules 2019-11-04 15:05:02 -06:00
Chung-Ting Huang fa841fc89a TUN-2451: Log inavlid path 2019-10-30 14:07:28 -05:00
Michael Borkenstein e9a8c9850f AUTH-2177: Reads and writes error streams 2019-10-25 19:44:14 +00:00
Chung-Ting Huang a52f47c9a9 TUN-2460: Configure according to the ClientConfig recevied from a successful Connect 2019-10-24 14:53:14 +00:00
Michael Borkenstein ad9559c66a AUTH-2173: Prepends access login url with scheme if one doesnt exist 2019-10-23 20:35:12 +00:00
Chung-Ting Huang 7133eceb9b TUN-2425: Enable cloudflared to serve multiple Hello World servers by having each of them create its own ServeMux 2019-10-22 10:09:59 -05:00
Areg Harutyunyan e0c2758b64 Release 2019.10.4 2019-10-21 14:48:22 -05:00
Areg Harutyunyan 5464408ea7 TUN-2450: Remove Brew publishing formula 2019-10-21 13:44:49 -05:00
Areg Harutyunyan 802e538c42 Release 2019.10.3 2019-10-21 10:20:34 -05:00
Areg Harutyunyan 79065514ff Merge remote-tracking branch 'upstream/master' 2019-10-18 15:33:27 -05:00
Michael Borkenstein 51acf2ef08 Release 2019.10.2 2019-10-18 13:11:07 -05:00
Felix Bünemann 1f6a330098 Fix #129: Excessive memory usage streaming large files (#142)
This drops the default size auf the h2mux write buffer from 512 MB to 1 MB.
This massively reduces memory usage, since each stream has its own buffer.
2019-10-17 17:15:51 -05:00
Michael Borkenstein 28cc1c65af AUTH-2167: Adds CLI option for host key directory 2019-10-17 16:31:43 -05:00
Michael Borkenstein 6322c5029d Release 2019.10.1 2019-10-17 13:17:42 -05:00
Michael Borkenstein 4bb5b97518 Adds variable to fix windows build 2019-10-17 18:15:59 +00:00
Michael Borkenstein 99daa76715 Release 2019.10.0 2019-10-17 10:58:33 -05:00
Michael Borkenstein babe684141 AUTH-2135: Adds support for IPv6 and tests 2019-10-16 16:06:07 -05:00
Michael Borkenstein 8b6e3bc1d1 AUTH-2159: Moves shutdownC close into error handling
AUTH-2161: Lowers size of preamble length
AUTH-2160: Fixes url parsing logic
2019-10-16 11:41:51 -05:00
Michael Borkenstein 95704b11fb AUTH-2114: Uses short lived cert auth for outgoing client connection 2019-10-15 14:35:15 -05:00
Nick Vollmar 4d2583edf5 TUN-2344: log more details: http2.Framer.ErrorDetail() if available, connectionID 2019-10-15 10:59:24 -05:00
Michael Borkenstein a4b3ee5959 AUTH-2105: Dont require --destination arg 2019-10-11 12:26:23 -05:00
Michael Borkenstein 91d9dca34e AUTH-2105: Adds support for local forwarding. Refactor auditlogger creation.
AUTH-2088: Adds dynamic destination routing
2019-10-10 15:25:03 -05:00
Michael Borkenstein dbde3870da AUTH-2089: Revise ssh server to function as a proxy 2019-10-07 13:04:04 -05:00
Chung-Ting Huang b3bcce97da Release 2019.9.2 2019-09-26 14:07:22 -05:00
Areg Harutyunyan a99fac1e31 TUN-2355: Roll back TUN-2276
This reverts commit 02f0ed951f.
2019-09-26 14:02:01 -05:00
Michael Borkenstein 71d66ae7ee Release 2019.9.1 2019-09-26 10:18:28 -05:00
Michael Borkenstein 133e6fdc88 AUTH-2077: Quotes open browser command in windows 2019-09-24 18:27:37 +00:00
Michael Borkenstein 1d5cc45ac7 AUTH-2055: Verifies token at edge on access login 2019-09-24 18:22:33 +00:00
Nick Vollmar a412f629c2 TUN-2334: remove tlsConfig.ServerName special case 2019-09-24 15:00:55 +00:00
Michael Borkenstein 979e5be8ab AUTH-2067: Log commands correctly 2019-09-23 20:42:41 +00:00
Michael Borkenstein 2789d0cf36 AUTH-2052: Adds tests for SSH server 2019-09-23 09:19:43 -05:00
Chung-Ting Huang 5bcb2da0fe TUN-2309: Split ConnectResult into ConnectError and ConnectSuccess, each implementing its own capnp serialization logic 2019-09-19 13:47:41 -05:00
Adam Chalmers 4f23da2a6d TUN-2315: Replace Scope with IntentLabel 2019-09-18 15:11:46 -05:00
Chung-Ting Huang fe032843f3 TUN-2307: Capnp is the only serialization format used in tunnelpogs 2019-09-17 16:29:50 +00:00
Michael Borkenstein ff795a7beb AUTH-2056: Writes stderr to its own stream for non-pty connections 2019-09-16 14:43:05 -05:00
Michael Borkenstein 40d9370bb6 AUTH-2050: Adds time.sleep to temporarily avoid hitting tunnel muxer dealock issue 2019-09-12 14:10:34 +00:00
Areg Harutyunyan 02f0ed951f TUN-2276: Path encoding broken 2019-09-11 19:28:23 +00:00
Michael Borkenstein c2a71c5a51 AUTH-2037: Adds support for ssh port forwarding 2019-09-11 10:41:09 -05:00
Dalton 945bf76897 AUTH-1942 added event log to ssh server 2019-09-10 10:16:32 -05:00
Michael Borkenstein d3b254f9ae AUTH-2036: Refactor user retrieval, shutdown after ssh server stops, add custom version string 2019-09-09 17:31:23 +00:00
Dalton ee588eeeaa AUTH-1943 hooked up uploader to logger, added timestamp to session logs, add tests 2019-09-06 15:57:32 -05:00
Adam Chalmers dd521aba29 TUN-2280: Revert "TUN-2260: add name/group to CapnpConnectParameters, remove Scope"
This reverts commit 817c3be9da5465043c2a2fda6c48f7ada760682e.
2019-09-06 15:59:32 +00:00
Adam Chalmers a06390a078 TUN-2201: change SRV records used by cloudflared
This changes cloudflarewarp.com to argotunnel.com and _warp to
_origintunneld. We've changed which zone we host the SRV records
for Argo Tunnel on.
2019-09-06 15:01:58 +00:00
Areg Harutyunyan 2cac1f0f78 Release 2019.9.0 2019-09-05 22:15:19 -05:00
Areg Harutyunyan a10785a701 TUN-2279: Revert path encoding fix
This reverts commit f6c7f8a96c.
2019-09-05 22:03:31 -05:00
Nick Vollmar dc730615f2 TUN-2260: add name/group to CapnpConnectParameters, remove Scope 2019-09-05 15:36:16 +00:00
Nick Vollmar b836cb350a TUN-2162: Decomplect OpenStream to allow finer-grained timeouts 2019-09-05 15:23:41 +00:00
Austin Cherry 5e85a8bd16 AUTH-1943: Adds session logging 2019-09-03 13:54:29 -05:00
Dalton f41121f4e5 AUTH-2021 - check error for failing tests 2019-08-30 15:20:49 -05:00
Michael Borkenstein 93a9504824 AUTH-2026: Adds support for non-pty sessions and inline command exec 2019-08-30 14:26:53 -05:00
Michael Borkenstein 7abbe91d41 AUTH-2030: Support both authorized_key and short lived cert authentication simultaniously without specifiying at start time 2019-08-30 19:23:10 +00:00
Areg Harutyunyan cf314ddb58 Release 2019.8.4 2019-08-30 13:50:41 -05:00
Areg Harutyunyan ff97fb6dc8 Merge branch 'master' of github.com:cloudflare/cloudflared 2019-08-30 13:45:40 -05:00
David Barr dc48cdce1a Fix #111: Add support for specifying a specific HTTP Host: header on the origin. (#114) 2019-08-29 22:55:54 -05:00
Dalton f130e6d4d7 AUTH-2021 - s3 bucket uploading for SSH logs 2019-08-29 16:54:54 -05:00
Adam Chalmers ef5b44b2d0 TUN-1968: Gracefully diff StreamHandler.UpdateConfig 2019-08-29 16:51:23 +00:00
Michael Borkenstein 858ef29868 AUTH-2022: Adds ssh timeout configuration 2019-08-28 15:22:35 -05:00
Michael Borkenstein baec3e289e AUTH-2018: Adds support for authorized keys and short lived certs 2019-08-28 09:58:42 -05:00
Adam Chalmers df25ed9bde TUN-2244: Add NO_AUTOUPDATE env var 2019-08-27 15:53:28 -05:00
Adam Chalmers 4e1df1a211 TUN-2243: Revert "STOR-519: Add db-connect, a SQL over HTTPS server"
This reverts commit 5da2109811.
2019-08-26 16:50:12 -05:00
Michael Borkenstein c3c88cc31e AUTH-2014: Checks users login shell 2019-08-26 18:14:39 +00:00
Adam Chalmers fb8ff33203 TUN-2165: Add ClientConfig to tunnelrpc.ConnectResult 2019-08-26 16:27:28 +00:00
Michael Borkenstein 188f4667cb AUTH-2004: Adds static host key support 2019-08-26 10:29:13 -05:00
Austin Cherry 30c9e2af9b AUTH-1941: Adds initial SSH server implementation 2019-08-21 15:49:03 -05:00
Chung-Ting Huang 66e087a825 Release 2019.8.3 2019-08-21 11:15:36 -05:00
Michael Borkenstein 47254113ee Revert "AUTH-1941: Adds initial SSH server implementation"
This reverts commit e9c9bf3cbd.
2019-08-20 17:20:48 -05:00
Chung-Ting Huang 026f2d1cc9 Release 2019.8.2 2019-08-20 16:47:23 -05:00
Austin Cherry e9c9bf3cbd AUTH-1941: Adds initial SSH server implementation 2019-08-20 16:18:37 -05:00
Ashcon Partovi 5da2109811 STOR-519: Add db-connect, a SQL over HTTPS server 2019-08-20 13:13:29 -05:00
Chung-Ting Huang 28f6c2ed7c TUN-2163: Add GrapQLType method to Scope interface 2019-08-11 12:18:54 -05:00
Areg Harutyunyan f6c7f8a96c TUN-2152: Requests with a query in the URL are erroneously escaped 2019-08-06 17:47:00 -05:00
Areg Harutyunyan 1d73c2752e Release 2019.8.1 2019-08-06 16:40:02 -05:00
Areg Harutyunyan 5afbb3fd6d Revert "TUN-1736: Missing headers when passing an invalid path"
This reverts commit 3c93d9b300.
2019-08-06 16:35:41 -05:00
Chung-Ting Huang fd4ab314dc TUN-2111: Implement custom serialization logic for FallibleConfig and OriginConfig 2019-08-06 10:56:16 -05:00
Michael Borkenstein 993a9bc4b9 Release 2019.8.0 2019-08-06 09:49:49 -05:00
Michael Borkenstein 8d1ea7202a Merge branch 'mike/AUTH-1972-delete-token-lockfile' of ssh://bitbucket.cfdata.org:7999/tun/cloudflared 2019-08-06 08:12:02 -05:00
Michael Borkenstein 9adbab96af AUTH-1972: Deletes token lock file if backoff retry attempts exceeded and intercepts signals until lock is released 2019-08-06 08:07:48 -05:00
Chung-Ting Huang bdd70e798a TUN-2110: Implement custom deserialization logic for OriginConfig 2019-08-05 19:28:51 -05:00
Adam Chalmers 5feba7e3a9 TUN-2147: Implemented ScopeUnmarshaler 2019-08-05 18:14:32 -05:00
Adam Chalmers d3d9460f64 TUN-2125: Add PostgresType() to Scope 2019-08-02 10:21:09 -05:00
Nick Vollmar 74f3a55c57 TUN-2117: read group/system-name from CLI, send it to edge 2019-08-01 22:04:05 +00:00
Areg Harutyunyan 3c93d9b300 TUN-1736: Missing headers when passing an invalid path 2019-07-31 16:56:29 +00:00
Nick Vollmar 3c0ea0db65 TUN-1956: Go 1.12 update 2019-07-12 22:09:16 +00:00
Austin Cherry 7c03e2cbd2 Release 2019.7.0 2019-07-11 10:22:37 -05:00
Austin Cherry 8f25704a90 AUTH-1736: Better handling of token revocation
We removed all token validation from cloudflared and now rely on
the edge to do the validation. This is better because the edge is
the only thing that fully knows about token revocation. So if a user
logs out or the application revokes all it's tokens cloudflared will
now handle that process instead of barfing on it.

When we go to fetch a token we will check for the existence of a
lock file. If the lock file exists, we stop and poll every half
second to see if the lock is still there. Once the lock file is
removed, it will restart the function to (hopefully) go pick up
the valid token that was just created.
2019-07-10 21:35:46 +00:00
Areg Harutyunyan 583bad4972 Merge branch 'master' of ssh://stash.cfops.it:7999/tun/cloudflared 2019-07-10 11:46:13 -05:00
Areg Harutyunyan b5f340d64c Merge branch 'master' of github.com:cloudflare/cloudflared 2019-07-10 11:45:57 -05:00
Frank Denis 9cd0b7048a DoH: change the media type to application/dns-message
The media type for DoH was changed from `application/dns-udpwireformat` to `application/dns-message` in May 2018.

Ref: RFC8484 section 6.
2019-07-03 14:13:28 -05:00
Chung-Ting Huang 4858ce79d0 TUN-1977: Validate OriginConfig has valid URL, and use scheme to determine if a HTTPOriginService is expecting HTTP or Unix 2019-07-01 15:31:58 -05:00
Chung-Ting Huang 4090049fff TUN-1981: Write response header & body on proxy error to notify eyeballs of failure category 2019-07-01 20:20:59 +00:00
Adam Chalmers e255a7da26 TUN-2005: Upgrade logrus 2019-06-26 12:25:58 -05:00
Chung-Ting Huang c2a3ac3991 TUN-1982: Load custom origin CA when OriginCAPool is specified 2019-06-21 11:44:26 -05:00
Chung-Ting Huang 2fa09e1cc6 TUN-1976: Pass tunnel hostname through header 2019-06-21 10:43:06 -05:00
Chung-Ting Huang 0a742feb98 TUN-1885: Reconfigure cloudflared on receiving new ClientConfig 2019-06-20 19:07:59 -05:00
Chung-Ting Huang 80a15547e3 TUN-1961: Create EdgeConnectionManager to maintain outbound connections to the edge 2019-06-18 16:37:38 -05:00
Chung-Ting Huang d26a8c5d44 TUN-1893: Proxy requests to the origin based on tunnel hostname 2019-06-13 14:46:22 -05:00
Chung-Ting Huang ca619a97bc TUN-1952: Group ClientConfig fields by the component that uses the config, and return the part of the config that failed to be applied 2019-06-13 14:39:51 -05:00
Areg Harutyunyan 25a04e0c69 Merge branch 'master' of ssh://stash.cfops.it:7999/tun/cloudflared 2019-06-06 13:53:03 +04:00
Johan Bergström d3f9aa2ae7 Build a docker container
Use debian/distroless (glibc) as a base which in general gives
slightly higher performance than alpine (musl) while trading
off container size (about 10mb).

The build phase makes the assumption that any capnproto-files
are pre-processed before building in-docker.
2019-06-06 13:50:13 +04:00
Chung-Ting Huang d32fb8e82c TUN-1913: Define OriginService for each type of origin 2019-06-04 17:02:34 -05:00
Austin Cherry acd17f6ab6 Release 2019.6.0 2019-06-04 11:29:24 -05:00
Austin Cherry 1ca841d220 AUTH-1811: ssh-gen config fixes 2019-06-04 16:25:34 +00:00
Chung-Ting Huang 39d60d1239 TUN-1914: Conflate HTTP and Unix OriginConfig, and add TLS config to WebSocketOriginConfig 2019-06-03 12:09:24 -05:00
Austin Cherry 713a2d689e AUTH-1802: Fixed ssh-config templating 2019-05-30 15:25:08 +00:00
Areg Harutyunyan babcd9fe2b Merge branch 'master' of github.com:cloudflare/cloudflared 2019-05-29 15:40:52 -05:00
Christoph Blecker a1403fe968 Handle exit code on err
fixes #96.

This change checks the err returned from the StartServer function, and
if it exists, passes a non-zero error code through to the urfave/cli
framework. This should allow processes like launchd to detect if
cloudflared exited gracefully or with an error.
2019-05-29 12:59:19 -05:00
Nick Vollmar 1485ca0fc7 TUN-1828: Update declarative tunnel config struct 2019-05-28 14:02:47 -05:00
Chung-Ting Huang 4bff1ef9df Release 2019.5.0 2019-05-23 10:20:48 -05:00
Austin Cherry 25cfffd0d1 AUTH-1781: fixed race condition for short lived certs, doc required config 2019-05-23 10:17:43 -05:00
Chung-Ting Huang 4662e40068 TUN-1880: Save debug and warn level log to logfile 2019-05-22 11:05:24 -05:00
Austin Cherry 38d78f9844 AUTH-1706: fixes and testing 2019-05-21 13:14:15 -05:00
Nick Vollmar 6f8708d33c TUN-1847: Log a distinct message when OpenStream fails while waiting for response headers 2019-05-13 16:46:25 -05:00
Austin Cherry fa17b0200f AUTH-1557: Short Lived Certs 2019-05-07 11:21:11 -05:00
Chung-Ting Huang 14f50d0922 TUN-1807: Send cloudflared version in Connect RPC 2019-05-06 16:13:12 -05:00
Nick Vollmar 945320880a TUN-1786: Remove low-level Windows service logging 2019-04-30 11:00:35 -05:00
Chung-Ting Huang 809d2f3f28 TUN-1781: ServeStream should return early on error 2019-04-29 09:36:41 -05:00
Areg Harutyunyan 137928ecaf Release 2019.4.1 2019-04-19 16:42:51 -05:00
Nick Vollmar 8183af31e2 Merge pull request #142 in TUN/cloudflared from nvollmar/TUN-1669 to master
* commit '28f890a70157ac234368c4a6235cc99e9662ce9b':
  TUN-1669: Update license message in help text. Also fix test
2019-04-18 20:15:00 +00:00
Nick Vollmar 28f890a701 TUN-1669: Update license message in help text. Also fix test 2019-04-18 10:42:48 -05:00
Areg Harutyunyan bab7583a97 TUN-1734: Pin packages at exact versions 2019-04-17 18:16:56 -05:00
Adam Chalmers 2e2fa29637 TUN-1732: cloudflared metrics should track userHostnames 2019-04-16 16:02:45 -05:00
Chung-Ting Huang 2bef5dbe72 TUN-1682: Add context to OpenStream to prevent it from blocking indefinitely. 2019-04-11 13:44:42 -05:00
Nick Vollmar 13d25a52a9 TUN-1606: Define CloudflaredConfig RPC structure, interface for cloudflared's RPC server 2019-04-09 15:58:40 -05:00
Nick Vollmar 9a43a92b1c TUN-1577: decompose carrier.StartServer to make TestStartServer less flappy 2019-04-09 15:09:58 -05:00
Chung-Ting Huang 102b364cc9 TUN-1619: Add flag to test declarative tunnels. 2019-04-05 10:35:23 -05:00
Chung-Ting Huang c18702f297 TUN-1626: Create new supervisor to establish connection with origintunneld 2019-04-04 15:39:22 -05:00
Nick Vollmar 980bee22a5 Release 2019.4.0 2019-04-03 14:31:34 -05:00
Chung-Ting Huang 80d3d989eb TUN-1673: Conflate Hello and Connect RPCs 2019-04-01 13:50:30 -05:00
Adam Chalmers 6804a5ff9d TUN-1648: ConnectionID is now a UUID 2019-03-28 15:03:30 -05:00
Chung-Ting Huang b5dab1f5da Release 2019.3.2 2019-03-25 13:54:40 -05:00
Chung-Ting Huang 8c6cfa34aa TUN-1604: Define Connect RPC call 2019-03-22 17:30:23 -05:00
Adam Chalmers 8560436487 TUN-1637: Free tunnels shouldn't require cert.pem 2019-03-22 14:43:30 -05:00
Nick Vollmar 619bc95501 Release 2019.3.1 2019-03-18 16:08:24 -05:00
Marek Vavruša 781e1fef7b TUN-1615: revert miekg/dns to last known working revision
The last known good commit is 6da3249dfb57fbaa16efafcd8744cee8809d80cd before the first release tag.
2019-03-17 20:40:18 -07:00
Adam Chalmers 541cf68608 TUN-1613: improved cloudflared RegisterTunnel fail metrics 2019-03-15 18:46:53 -05:00
Areg Harutyunyan eafc89bf73 Merge branch 'master' of github.com:cloudflare/cloudflared 2019-03-11 14:57:03 -05:00
Chung-Ting Huang fd451ca158 Release 2019.3.0 2019-03-11 13:55:10 -05:00
Nick Vollmar d22e214000 TUN-1522: If we can't get SRV from default resolver, get them from 1.1.1.1 DoT 2019-03-11 12:08:04 -04:00
Joel Miles 92d6d73f9c Add rdp as a supported protocol in URL validation (#76) 2019-03-09 22:23:30 -06:00
Areg Harutyunyan b02718f86b TUN-1389: Non-scalar flags in a cloudflared config.yml don't get logged 2019-03-08 02:43:07 -06:00
Areg Harutyunyan 5a0b7ecb62 Merge branch 'master' of github.com:cloudflare/cloudflared 2019-03-06 18:34:42 -06:00
Kristian Mide 07a409ffef Support unix sockets. 2019-03-06 18:33:23 -06:00
Austin Cherry 58e5e9c9a4 AUTH-1531: Named flags for ssh service tokens 2019-03-06 13:09:13 -06:00
Nick Vollmar 6ca642e572 TUN-1550: Add validation timeout for non-responsive origins 2019-03-06 11:30:29 -06:00
Adam Chalmers 073c5bfdaa TUN-1562: Refactor connectedSignal to be safe to close multiple times 2019-03-05 15:51:35 -06:00
Nick Vollmar fea3569956 TUN-1451: Make non-interactive, non-service execution possible on Windows 2019-03-04 19:49:57 -06:00
Adam Chalmers abdbc76a46 TUN-1559: fix nil dereference in TunnelConfig.CloseConnOnce 2019-02-28 16:56:47 -06:00
Areg Harutyunyan f22202b31b Merge branch 'master' of github.com:cloudflare/cloudflared 2019-02-27 18:38:01 -06:00
Adam Chalmers 4586ed3e51 Release 2019.2.1 2019-02-27 17:59:54 -06:00
Adam Chalmers 7475e3e487 TUN-1510: Wrap the close() in sync.Once.Do 2019-02-26 10:32:07 -06:00
Adam Chalmers e025a4cd7b TUN-1525: cloudflared metrics for registration success/fail 2019-02-19 17:11:04 -06:00
Austin Cherry 850f804c47 AUTH-1519: Added logging 2019-02-19 12:53:33 -06:00
Nick Vollmar 1558280185 TUN-1467: build with Go 1.11 2019-02-15 14:57:02 -06:00
Kane Dou 15d68a0eed make http transport aware of proxy from envvar 2019-02-15 11:26:34 -06:00
Areg Harutyunyan e3aed15c08 TUN-1381: should tell you if you're on the latest version rather than just exiting silently 2019-02-14 17:11:52 -06:00
Austin Cherry 99713e3439 Release 2019.2.0 2019-02-11 14:06:56 -06:00
Austin Cherry 27c6977746 AUTH-1403: Print the paths in the ssh-config instructions 2019-02-11 10:56:06 -06:00
Austin Cherry 200f9a3786 AUTH-1503: Added RDP support 2019-02-08 14:28:47 -06:00
Austin Cherry 92defa26d4 AUTH-1511: Add custom headers for ssh command 2019-02-07 16:38:52 -06:00
Areg Harutyunyan c9f1c1ec70 Merge branch 'master' of github.com:cloudflare/cloudflared 2019-02-01 14:57:53 -06:00
Philippe Grégoire 10e4a2940c cloudflared/linux_service: Add missing /etc/init.d shebang
When using sysv init scripts, `cloudflared` fails to start due to the
missing shebang interpreter line.

This patch adds the missing shebang.
2019-02-01 14:55:30 -06:00
Adam Chalmers 77f12c7c34 TUN-1456: Only make one UUID 2019-02-01 14:11:12 -06:00
Austin Cherry ca33ed9f6a AUTH-1462: better curl arg parsing 2019-02-01 10:46:36 -06:00
Adam Chalmers 6a331b13ba Release 2019.1.0 2019-01-28 17:03:33 -06:00
Chung-Ting Huang 47c878b9c4 TUN-1419: Identify request/response headers/content length with ray ID 2019-01-28 16:52:17 -06:00
Chung-Ting Huang 61cd4a918d TUN-1418: Rename ProtocolLogger to TransportLogger, and use TransportLogger to log RPC events. 2019-01-28 16:09:58 -06:00
Austin Cherry 9db4b7fdfb AUTH-1337: fix url path 2019-01-25 13:47:12 -06:00
Austin Cherry 2f59b8ac22 AUTH-1211: print all the versions 2019-01-25 13:44:26 -06:00
Austin Cherry f94699e07b AUTH-1459: improved ssh streaming error message 2019-01-25 10:45:50 -06:00
Austin Cherry f7cf597f54 AUTH-1404: reauth if the token is about to expire within 15 minutes 2019-01-25 10:43:07 -06:00
Austin Cherry aeb8dd7e51 AUTH-1423: move from stdout to stderr 2019-01-24 14:48:37 -06:00
Nick Vollmar 5bf6dd8f85 TUN-1358: Close readyList after Muxer.Serve() has stopped running 2019-01-22 15:54:29 -06:00
Nick Vollmar 62b1ab8c98 TUN-1350: Enhance error messages with cloudflarestatus.com link, if relevant 2019-01-18 13:37:05 -06:00
Areg Harutyunyan 8de19dc647 TUN-1265: Silent exit when failing to parse config 2019-01-08 15:51:43 -06:00
Areg Harutyunyan 8a099d8f6e TUN-1138: Install cloudflared service directory with 755 permissions 2019-01-07 13:46:21 -06:00
Leland Garofalo ef400afe00 TUN-632 Filter out common network exceptions from going to Sentry on StartServer 2019-01-07 13:36:52 -06:00
Nick Vollmar bf596c035e TUN-1140: Show usage if invoked with no args or config 2018-12-17 15:05:03 -06:00
Areg Harutyunyan 36276e957a TUN-1231: Horizontal overflow wrapping on the Hello page 2018-12-11 16:03:44 -06:00
Chung-Ting Huang c69437ba83 Release 2018.12.1 2018-12-11 14:20:51 -06:00
Adam Chalmers b2d0c612a5 TUN-1270: cloudflared panic (HA metrics missing label) 2018-12-11 11:41:49 -06:00
Areg Harutyunyan 8e2908f889 Release 2018.12.0 2018-12-10 13:50:13 -06:00
Nick Vollmar 3e8d886c25 TUN-1250: ValidateHTTPService shouldn't follow 302s 2018-12-07 16:59:15 -06:00
Areg Harutyunyan 446c5cf60c Merge branch 'master' of github.com:cloudflare/cloudflared 2018-12-07 11:36:41 -06:00
Mohammed Naser 13f88b3739 Fix license URL typo 2018-12-07 11:33:55 -06:00
Nick Vollmar 69ee6c1d88 TUN-1204: remove 'cloudflared hello' command 2018-12-03 16:31:20 -06:00
Adam Chalmers 192ae35728 TUN-1212: Expose tunnel_id in metrics 2018-11-27 15:26:45 -06:00
Chung-Ting Huang 10d547f528 TUN-1209: TLS Config Certificates and GetCertificate can both be set 2018-11-20 14:34:56 -06:00
Chung-Ting Huang b59fd4b7d8 TUN-1196: Allow TLS config client CA and root CA to be constructed from multiple certificates 2018-11-19 15:38:10 -06:00
Chung-Ting Huang c85c8526e8 Release 2018.11.0 2018-11-16 09:53:13 -06:00
Austin Cherry f49d9dcb67 AUTH-1320: Fixed request issue and unhide the ssh command 2018-11-15 13:08:56 -06:00
Nick Vollmar c2ac282aca TUN-1190: check URL parse error when starting SSH proxy server 2018-11-14 12:16:12 -06:00
Austin Cherry 58daf6bfed AUTH-1308: get jwt even when you are already logged in 2018-11-13 17:06:17 -06:00
Nick Vollmar 611b284e20 TUN-1179: Fix log message in cmd/cloudflared/transfer.Run 2018-11-06 09:23:03 -08:00
Austin Cherry 236a0a164d AUTH-1282: Fixed an issue where we were receiving as opposed sending on the channel. 2018-10-31 13:48:19 -05:00
Nick Vollmar 148eea5899 Release 2018.10.5 2018-10-30 14:15:58 -05:00
Nick Vollmar 83c6c8713b TUN-1160: pass Host header during origin url validation 2018-10-30 13:58:07 -05:00
Areg Harutyunyan 2b820d790c Merge remote-tracking branch 'upstream/master' 2018-10-30 12:06:25 -05:00
dstockton 36286301f7 #30: Fix the Content-Length header for HTTP2->HTTP1 2018-10-30 11:52:11 -05:00
Nick Vollmar 9a48fe959d TUN-1158: Windows: use process arguments rather than trivial service arguments
TUN-1158: Fix segfault when carrier test case fails
2018-10-29 14:14:53 -05:00
Nick Vollmar f6014cb2b4 TUN-968: Flow control for large requests/responses 2018-10-26 11:16:02 -05:00
Nick Vollmar 9fe21fa906 Release 2018.10.4 2018-10-25 15:56:25 -05:00
Austin Cherry 80a75e91d2 AUTH-1188: UX Review and Changes for CLI SSH Access 2018-10-25 15:50:27 -05:00
Austin Cherry 6acc95f756 TUN-1097: Host missing from WebSocket request 2018-10-19 16:51:54 -05:00
Austin Cherry 72412db4c2 AUTH-1235: fixed packaging of deb dev file 2018-10-19 14:01:08 -05:00
Austin Cherry fa92441415 AUTH-1070: added SSH/protocol forwarding 2018-10-11 11:34:37 -05:00
3095 changed files with 501745 additions and 262043 deletions
+8
View File
@@ -0,0 +1,8 @@
images:
- name: cloudflared
dockerfile: Dockerfile
context: .
registries:
- name: docker.io/cloudflare
user: env:DOCKER_USER
password: env:DOCKER_PASSWORD
View File
+20
View File
@@ -0,0 +1,20 @@
on: [push, pull_request]
name: Check
jobs:
check:
strategy:
matrix:
go-version: [1.17.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Install go-sumtype
run: go get github.com/sudarshan-reddy/go-sumtype
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: make test
+14 -5
View File
@@ -1,9 +1,18 @@
.GOPATH/
bin/
tmp/
guide/public
/.GOPATH
/tmp
/bin
.idea
.build
.vscode
\#*\#
cscope.*
/cloudflared
/cloudflared.pkg
/cloudflared.exe
/cloudflared.msi
/cloudflared-x86-64*
/packaging
.DS_Store
*-session.log
ssh_server_tests/.env
/.cover
built_artifacts/
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# uninstall first in case this is an upgrade
/usr/local/bin/cloudflared service uninstall
# install the new service using launchctl
/usr/local/bin/cloudflared service install
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
/usr/local/bin/cloudflared service uninstall
rm /usr/local/bin/cloudflared
pkgutil --forget com.cloudflare.cloudflared
Vendored Executable
+187
View File
@@ -0,0 +1,187 @@
#!/bin/bash
if [[ "$(uname)" != "Darwin" ]] ; then
echo "This should be run on macOS"
exit 1
fi
go version
export GO111MODULE=on
# build 'cloudflared-darwin-amd64.tgz'
mkdir -p artifacts
FILENAME="$(pwd)/artifacts/cloudflared-darwin-amd64.tgz"
PKGNAME="$(pwd)/artifacts/cloudflared-amd64.pkg"
TARGET_DIRECTORY=".build"
BINARY_NAME="cloudflared"
VERSION=$(git describe --tags --always --dirty="-dev")
PRODUCT="cloudflared"
CODE_SIGN_PRIV="code_sign.p12"
CODE_SIGN_CERT="code_sign.cer"
INSTALLER_PRIV="installer.p12"
INSTALLER_CERT="installer.cer"
BUNDLE_ID="com.cloudflare.cloudflared"
SEC_DUP_MSG="security: SecKeychainItemImport: The specified item already exists in the keychain."
export PATH="$PATH:/usr/local/bin"
mkdir -p ../src/github.com/cloudflare/
cp -r . ../src/github.com/cloudflare/cloudflared
cd ../src/github.com/cloudflare/cloudflared
GOCACHE="$PWD/../../../../" GOPATH="$PWD/../../../../" CGO_ENABLED=1 make cloudflared
# Add code signing private key to the key chain
if [[ ! -z "$CFD_CODE_SIGN_KEY" ]]; then
if [[ ! -z "$CFD_CODE_SIGN_PASS" ]]; then
# write private key to disk and then import it keychain
echo -n -e ${CFD_CODE_SIGN_KEY} | base64 -D > ${CODE_SIGN_PRIV}
out=$(security import ${CODE_SIGN_PRIV} -A -P "${CFD_CODE_SIGN_PASS}" 2>&1)
exitcode=$?
if [ -n "$out" ]; then
if [ $exitcode -eq 0 ]; then
echo "$out"
else
if [ "$out" != "${SEC_DUP_MSG}" ]; then
echo "$out" >&2
exit $exitcode
fi
fi
fi
rm ${CODE_SIGN_PRIV}
fi
fi
# Add code signing certificate to the key chain
if [[ ! -z "$CFD_CODE_SIGN_CERT" ]]; then
# write certificate to disk and then import it keychain
echo -n -e ${CFD_CODE_SIGN_CERT} | base64 -D > ${CODE_SIGN_CERT}
out1=$(security import ${CODE_SIGN_CERT} -A 2>&1)
exitcode1=$?
if [ -n "$out1" ]; then
if [ $exitcode1 -eq 0 ]; then
echo "$out1"
else
if [ "$out1" != "${SEC_DUP_MSG}" ]; then
echo "$out1" >&2
exit $exitcode1
else
echo "already imported code signing certificate"
fi
fi
fi
rm ${CODE_SIGN_CERT}
fi
# Add package signing private key to the key chain
if [[ ! -z "$CFD_INSTALLER_KEY" ]]; then
if [[ ! -z "$CFD_INSTALLER_PASS" ]]; then
# write private key to disk and then import it into the keychain
echo -n -e ${CFD_INSTALLER_KEY} | base64 -D > ${INSTALLER_PRIV}
out2=$(security import ${INSTALLER_PRIV} -A -P "${CFD_INSTALLER_PASS}" 2>&1)
exitcode2=$?
if [ -n "$out2" ]; then
if [ $exitcode2 -eq 0 ]; then
echo "$out2"
else
if [ "$out2" != "${SEC_DUP_MSG}" ]; then
echo "$out2" >&2
exit $exitcode2
fi
fi
fi
rm ${INSTALLER_PRIV}
fi
fi
# Add package signing certificate to the key chain
if [[ ! -z "$CFD_INSTALLER_CERT" ]]; then
# write certificate to disk and then import it keychain
echo -n -e ${CFD_INSTALLER_CERT} | base64 -D > ${INSTALLER_CERT}
out3=$(security import ${INSTALLER_CERT} -A 2>&1)
exitcode3=$?
if [ -n "$out3" ]; then
if [ $exitcode3 -eq 0 ]; then
echo "$out3"
else
if [ "$out3" != "${SEC_DUP_MSG}" ]; then
echo "$out3" >&2
exit $exitcode3
else
echo "already imported installer certificate"
fi
fi
fi
rm ${INSTALLER_CERT}
fi
# get the code signing certificate name
if [[ ! -z "$CFD_CODE_SIGN_NAME" ]]; then
CODE_SIGN_NAME="${CFD_CODE_SIGN_NAME}"
else
if [[ -n "$(security find-certificate -c "Developer ID Application" | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)" ]]; then
CODE_SIGN_NAME=$(security find-certificate -c "Developer ID Application" | cut -d'"' -f 4 -s | grep "Developer ID Application:" | head -1)
else
CODE_SIGN_NAME=""
fi
fi
# get the package signing certificate name
if [[ ! -z "$CFD_INSTALLER_NAME" ]]; then
PKG_SIGN_NAME="${CFD_INSTALLER_NAME}"
else
if [[ -n "$(security find-certificate -c "Developer ID Installer" | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)" ]]; then
PKG_SIGN_NAME=$(security find-certificate -c "Developer ID Installer" | cut -d'"' -f 4 -s | grep "Developer ID Installer:" | head -1)
else
PKG_SIGN_NAME=""
fi
fi
# sign the cloudflared binary
if [[ ! -z "$CODE_SIGN_NAME" ]]; then
codesign -s "${CODE_SIGN_NAME}" -f -v --timestamp --options runtime ${BINARY_NAME}
# notarize the binary
if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then
zip "${BINARY_NAME}.zip" ${BINARY_NAME}
xcrun altool --notarize-app -f "${BINARY_NAME}.zip" -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID}
fi
fi
# creating build directory
rm -rf $TARGET_DIRECTORY
mkdir "${TARGET_DIRECTORY}"
mkdir "${TARGET_DIRECTORY}/contents"
cp -r ".mac_resources/scripts" "${TARGET_DIRECTORY}/scripts"
# copy cloudflared into the build directory
cp ${BINARY_NAME} "${TARGET_DIRECTORY}/contents/${PRODUCT}"
# compress cloudflared into a tar and gzipped file
tar czf "$FILENAME" "${BINARY_NAME}"
# build the installer package
if [[ ! -z "$PKG_SIGN_NAME" ]]; then
pkgbuild --identifier com.cloudflare.${PRODUCT} \
--version ${VERSION} \
--scripts ${TARGET_DIRECTORY}/scripts \
--root ${TARGET_DIRECTORY}/contents \
--install-location /usr/local/bin \
--sign "${PKG_SIGN_NAME}" \
${PKGNAME}
# notarize the package
if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then
xcrun altool --notarize-app -f ${PKGNAME} -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID}
xcrun stapler staple ${PKGNAME}
fi
else
pkgbuild --identifier com.cloudflare.${PRODUCT} \
--version ${VERSION} \
--scripts ${TARGET_DIRECTORY}/scripts \
--root ${TARGET_DIRECTORY}/contents \
--install-location /usr/local/bin \
${PKGNAME}
fi
# cleaning up the build directory
rm -rf $TARGET_DIRECTORY
Vendored Executable
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
set -euo pipefail
FILENAME="${PWD}/artifacts/cloudflared-darwin-amd64.tgz"
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 [[ ! -f "$FILENAME" ]] ; then
echo "Missing $FILENAME"
exit 1
fi
if [[ "${GITHUB_PRIVATE_KEY:-}" == "" ]] ; then
echo "Missing GITHUB_PRIVATE_KEY"
exit 1
fi
# upload to s3 bucket for use by Homebrew formula
s3cmd \
--acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \
put "$FILENAME" "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz"
s3cmd \
--acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \
cp "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz" "s3://cftunnel-docs/dl/cloudflared-stable-darwin-amd64.tgz"
SHA256=$(sha256sum "$FILENAME" | cut -b1-64)
# set up git (note that UserKnownHostsFile is an absolute path so we can cd wherever)
mkdir -p tmp
ssh-keyscan -t rsa github.com > tmp/github.txt
echo "$GITHUB_PRIVATE_KEY" > tmp/private.key
chmod 0400 tmp/private.key
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=$PWD/tmp/github.txt -i $PWD/tmp/private.key -o IdentitiesOnly=yes"
# clone Homebrew repo into tmp/homebrew-cloudflare
git clone git@github.com:cloudflare/homebrew-cloudflare.git tmp/homebrew-cloudflare
cd tmp/homebrew-cloudflare
git checkout -f master
git reset --hard origin/master
# modify cloudflared.rb
URL="https://packages.argotunnel.com/dl/cloudflared-$VERSION-darwin-amd64.tgz"
tee cloudflared.rb <<EOF
class Cloudflared < Formula
desc 'Cloudflare Tunnel'
homepage 'https://developers.cloudflare.com/cloudflare-one/connections/connect-apps'
url '$URL'
sha256 '$SHA256'
version '$VERSION'
def install
bin.install 'cloudflared'
end
end
EOF
# push cloudflared.rb
git add cloudflared.rb
git diff
git config user.name "cloudflare-warp-bot"
git config user.email "warp-bot@cloudflare.com"
git commit -m "Release Cloudflare Tunnel $VERSION"
git push -v origin master
+239
View File
@@ -0,0 +1,239 @@
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
## 2021.12.2
### Bug Fixes
- Fix logging when `quic` transport is used and UDP traffic is proxied.
- FIPS compliant cloudflared binaries will now be released as separate artifacts. Recall that these are only for linux
and amd64.
## 2021.12.1
### Bug Fixes
- Fixes Github issue #530 where cloudflared 2021.12.0 could not reach origins that were HTTPS and using certain encryption
methods forbidden by FIPS compliance (such as Let's Encrypt certificates). To address this fix we have temporarily reverted
FIPS compliance from amd64 linux binaries that was recently introduced (or fixed actually as it was never working before).
## 2021.12.0
### New Features
- Cloudflared binary released for amd64 linux is now FIPS compliant.
### Improvements
- Logging about connectivity to Cloudflare edge now only yields `ERR` level logging if there are no connections to
Cloudflare edge that are active. Otherwise it logs `WARN` level.
### Bug Fixes
- Fixes Github issue #501.
## 2021.11.0
### Improvements
- Fallback from `protocol:quic` to `protocol:http2` immediately if UDP connectivity isn't available. This could be because of a firewall or
egress rule.
## 2021.10.4
### Improvements
- Collect quic transport metrics on RTT, packets and bytes transferred.
### Bug Fixes
- Fix race condition that was writing to the connection after the http2 handler returns.
## 2021.9.2
### New features
- `cloudflared` can now run with `quic` as the underlying tunnel transport protocol. To try it, change or add "protocol: quic" to your config.yml file or
run cloudflared with the `--protocol quic` flag. e.g:
`cloudflared tunnel --protocol quic run <tunnel-name>`
### Bug Fixes
- Fixed some generic transport bugs in `quic` mode. It's advised to upgrade to at least this version (2021.9.2) when running `cloudflared`
with `quic` protocol.
- `cloudflared` docker images will now show version.
## 2021.8.4
### Improvements
- Temporary tunnels (those hosted on trycloudflare.com that do not require a Cloudflare login) now run as Named Tunnels
underneath. We recall that these tunnels should not be relied upon for production usage as they come with no guarantee
of uptime. Previous cloudflared versions will soon be unable to run legacy temporary tunnels and will require an update
(to this version or more recent).
## 2021.8.2
### Improvements
- Because Equinox os shutting down, all cloudflared releases are now present [here](https://github.com/cloudflare/cloudflared/releases).
[Equinox](https://dl.equinox.io/cloudflare/cloudflared/stable) will no longer receive updates.
## 2021.8.0
### Bug fixes
- Prevents tunnel from accidentally running when only proxy-dns should run.
### Improvements
- If auto protocol transport lookup fails, we now default to a transport instead of not connecting.
## 2021.6.0
### Bug Fixes
- Fixes a http2 transport (the new default for Named Tunnels) to work with unix socket origins.
## 2021.5.10
### Bug Fixes
- Fixes a memory leak in h2mux transport that connects cloudflared to Cloudflare edge.
## 2021.5.9
### New Features
- Uses new Worker based login helper service to facilitate token exchange in cloudflared flows.
### Bug Fixes
- Fixes Centos-7 builds.
## 2021.5.8
### New Features
- When creating a DNS record to point a hostname at a tunnel, you can now use --overwrite-dns to overwrite any existing
DNS records with that hostname. This works both when using the CLI to provision DNS, as well as when starting an adhoc
named tunnel, e.g.:
- `cloudflared tunnel route dns --overwrite-dns foo-tunnel foo.example.com`
- `cloudflared tunnel --overwrite-dns --name foo-tunnel --hostname foo.example.com`
## 2021.5.7
### New Features
- Named Tunnels will automatically select the protocol to connect to Cloudflare's edge network.
## 2021.5.0
### New Features
- It is now possible to run the same tunnel using more than one `cloudflared` instance. This is a server-side change and
is compatible with any client version that uses Named Tunnels.
To get started, visit our [developer documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/deploy-cloudflared-replicas).
- `cloudflared tunnel ingress validate` will now warn about unused keys in your config file. This is helpful for
detecting typos in your config.
- If `cloudflared` detects it is running inside a Linux container, it will limit itself to use only the number of CPUs
the pod has been granted, instead of trying to use every CPU available.
## 2021.4.0
### Bug Fixes
- Fixed proxying of websocket requests to avoid possibility of losing initial frames that were sent in the same TCP
packet as response headers [#345](https://github.com/cloudflare/cloudflared/issues/345).
- `proxy-dns` option now works in conjunction with running a named tunnel [#346](https://github.com/cloudflare/cloudflared/issues/346).
## 2021.3.6
### Bug Fixes
- Reverted 2021.3.5 improvement to use HTTP/2 in a best-effort manner between cloudflared and origin services because
it was found to break in some cases.
## 2021.3.5
### Improvements
- HTTP/2 transport is now always chosen if origin server supports it and the service url scheme is HTTPS.
This was previously done in a best attempt manner.
### Bug Fixes
- The MacOS binaries were not successfully released in 2021.3.3 and 2021.3.4. This release is aimed at addressing that.
## 2021.3.3
### Improvements
- Tunnel create command, as well as, running ad-hoc tunnels using `cloudflared tunnel -name NAME`, will not overwrite
existing files when writing tunnel credentials.
### Bug Fixes
- Tunnel create and delete commands no longer use path to credentials from the configuration file.
If you need ot place tunnel credentials file at a specific location, you must use `--credentials-file` flag.
- Access ssh-gen creates properly named keys for SSH short lived certs.
## 2021.3.2
### New Features
- It is now possible to obtain more detailed information about the cloudflared connectors to Cloudflare Edge via
`cloudflared tunnel info <name/uuid>`. It is possible to sort the output as well as output in different formats,
such as: `cloudflared tunnel info --sort-by version --invert-sort --output json <name/uuid>`.
You can obtain more information via `cloudflared tunnel info --help`.
### Bug Fixes
- Don't look for configuration file in default paths when `--config FILE` flag is present after `tunnel` subcommand.
- cloudflared access token command now functions correctly with the new token-per-app change from 2021.3.0.
## 2021.3.0
### New Features
- [Cloudflare One Routing](https://developers.cloudflare.com/cloudflare-one/tutorials/warp-to-tunnel) specific commands
now show up in the `cloudflared tunnel route --help` output.
- There is a new ingress type that allows cloudflared to proxy SOCKS5 as a bastion. You can use it with an ingress
rule by adding `service: socks-proxy`. Traffic is routed to any destination specified by the SOCKS5 packet but only
if allowed by a rule. In the following example we allow proxying to a certain CIDR but explicitly forbid one address
within it:
```
ingress:
- hostname: socks.example.com
service: socks-proxy
originRequest:
ipRules:
- prefix: 192.168.1.8/32
allow: false
- prefix: 192.168.1.0/24
ports: [80, 443]
allow: true
```
### Improvements
- Nested commands, such as `cloudflared tunnel run`, now consider CLI arguments even if they appear earlier on the
command. For instance, `cloudflared --config config.yaml tunnel run` will now behave the same as
`cloudflared tunnel --config config.yaml run`
- Warnings are now shown in the output logs whenever cloudflared is running without the most recent version and
`no-autoupdate` is `true`.
- Access tokens are now stored per Access App instead of per request path. This decreases the number of times that the
user is required to authenticate with an Access policy redundantly.
### Bug Fixes
- GitHub [PR #317](https://github.com/cloudflare/cloudflared/issues/317) was broken in 2021.2.5 and is now fixed again.
## 2021.2.5
### New Features
- We introduce [Cloudflare One Routing](https://developers.cloudflare.com/cloudflare-one/tutorials/warp-to-tunnel) in
beta mode. Cloudflare customer can now connect users and private networks with RFC 1918 IP addresses via the
Cloudflare edge network. Users running Cloudflare WARP client in the same organization can connect to the services
made available by Argo Tunnel IP routes. Please share your feedback in the GitHub issue tracker.
## 2021.2.4
### Bug Fixes
- Reverts the Improvement released in 2021.2.3 for CLI arguments as it introduced a regression where cloudflared failed
to read URLs in configuration files.
- cloudflared now logs the reason for failed connections if the error is recoverable.
## 2021.2.3
### Backward Incompatible Changes
- Removes db-connect. The Cloudflare Workers product will continue to support db-connect implementations with versions
of cloudflared that predate this release and include support for db-connect.
### New Features
- Introduces support for proxy configurations with websockets in arbitrary TCP connections (#318).
### Improvements
- (reverted) Nested command line argument handling.
### Bug Fixes
- The maximum number of upstream connections is now limited by default which should fix reported issues of cloudflared
exhausting CPU usage when faced with connectivity issues.
+29
View File
@@ -0,0 +1,29 @@
# use a builder image for building cloudflare
ARG TARGET_GOOS
ARG TARGET_GOARCH
FROM golang:1.17.1 as builder
ENV GO111MODULE=on \
CGO_ENABLED=0 \
TARGET_GOOS=${TARGET_GOOS} \
TARGET_GOARCH=${TARGET_GOARCH}
WORKDIR /go/src/github.com/cloudflare/cloudflared/
# copy our sources into the builder image
COPY . .
# compile cloudflared
RUN make cloudflared
# use a distroless base image with glibc
FROM gcr.io/distroless/base-debian10:nonroot
# copy our compiled binary
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
# run as non-privileged user
USER nonroot
# command / entrypoint of container
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
CMD ["version"]
Generated
-466
View File
@@ -1,466 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/BurntSushi/toml"
packages = ["."]
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
name = "github.com/certifi/gocertifi"
packages = ["."]
revision = "deb3ae2ef2610fde3330947281941c562861188b"
version = "2018.01.18"
[[projects]]
branch = "master"
name = "github.com/cloudflare/brotli-go"
packages = ["."]
revision = "18c9f6c67e3dfc12e0ddaca748d2887f97a7ac28"
[[projects]]
branch = "master"
name = "github.com/coredns/coredns"
packages = [
"core/dnsserver",
"coremain",
"pb",
"plugin",
"plugin/cache",
"plugin/cache/freq",
"plugin/etcd/msg",
"plugin/metrics",
"plugin/metrics/vars",
"plugin/pkg/cache",
"plugin/pkg/dnstest",
"plugin/pkg/dnsutil",
"plugin/pkg/doh",
"plugin/pkg/edns",
"plugin/pkg/fuzz",
"plugin/pkg/log",
"plugin/pkg/nonwriter",
"plugin/pkg/rcode",
"plugin/pkg/response",
"plugin/pkg/trace",
"plugin/pkg/uniq",
"plugin/pkg/watch",
"plugin/test",
"request"
]
revision = "992e7928c7c258628d2b13b769acc86781b9faea"
[[projects]]
branch = "master"
name = "github.com/coreos/go-oidc"
packages = [
"http",
"jose",
"key",
"oauth2",
"oidc"
]
revision = "a93f71fdfe73d2c0f5413c0565eea0af6523a6df"
[[projects]]
name = "github.com/coreos/go-systemd"
packages = ["daemon"]
revision = "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
version = "v17"
[[projects]]
name = "github.com/coreos/pkg"
packages = [
"health",
"httputil",
"timeutil"
]
revision = "97fdf19511ea361ae1c100dd393cc47f8dcfa1e1"
version = "v4"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/elgs/gosqljson"
packages = ["."]
revision = "027aa4915315a0b2825c0f025cea347829b974fa"
[[projects]]
branch = "master"
name = "github.com/equinox-io/equinox"
packages = [
".",
"internal/go-update",
"internal/go-update/internal/binarydist",
"internal/go-update/internal/osext",
"internal/osext",
"proto"
]
revision = "f24972fa72facf59d05c91c848b65eac38815915"
[[projects]]
branch = "master"
name = "github.com/facebookgo/grace"
packages = ["gracenet"]
revision = "75cf19382434e82df4dd84953f566b8ad23d6e9e"
[[projects]]
branch = "master"
name = "github.com/flynn/go-shlex"
packages = ["."]
revision = "3f9db97f856818214da2e1057f8ad84803971cff"
[[projects]]
branch = "master"
name = "github.com/getsentry/raven-go"
packages = ["."]
revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa"
[[projects]]
branch = "master"
name = "github.com/golang-collections/collections"
packages = ["queue"]
revision = "604e922904d35e97f98a774db7881f049cd8d970"
[[projects]]
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
]
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
name = "github.com/google/uuid"
packages = ["."]
revision = "064e2069ce9c359c118179501254f67d7d37ba24"
version = "0.2"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/grpc-ecosystem/grpc-opentracing"
packages = ["go/otgrpc"]
revision = "8e809c8a86450a29b90dcc9efbf062d0fe6d9746"
[[projects]]
name = "github.com/jonboulle/clockwork"
packages = ["."]
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [
".",
"oid"
]
revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
version = "v1.0.1"
[[projects]]
branch = "master"
name = "github.com/mholt/caddy"
packages = [
".",
"caddyfile",
"telemetry"
]
revision = "d3b731e9255b72d4571a5aac125634cf1b6031dc"
[[projects]]
name = "github.com/miekg/dns"
packages = ["."]
revision = "5a2b9fab83ff0f8bfc99684bd5f43a37abe560f1"
version = "v1.0.8"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66"
[[projects]]
name = "github.com/opentracing/opentracing-go"
packages = [
".",
"ext",
"log"
]
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
version = "v1.0.2"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/promhttp"
]
revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
version = "v0.9.0-pre1"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
]
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs"
]
revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a"
[[projects]]
name = "github.com/rifflock/lfshook"
packages = ["."]
revision = "bf539943797a1f34c1f502d07de419b5238ae6c6"
version = "v2.3"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"internal/subtle",
"nacl/box",
"nacl/secretbox",
"poly1305",
"salsa20/salsa",
"ssh/terminal"
]
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"bpf",
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/iana",
"internal/socket",
"internal/timeseries",
"ipv4",
"ipv6",
"trace",
"websocket"
]
revision = "32a936f46389aa10549d60bd7833e54b01685d09"
[[projects]]
branch = "master"
name = "golang.org/x/sync"
packages = ["errgroup"]
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
"windows/registry",
"windows/svc",
"windows/svc/eventlog",
"windows/svc/mgr"
]
revision = "ce36f3865eeb42541ce3f87f32f8462c5687befa"
[[projects]]
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "ff3583edef7de132f219f0efc00e097cabcc0ec0"
[[projects]]
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/grpcrand",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport"
]
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8"
version = "v1.13.0"
[[projects]]
branch = "altsrc-parse-durations"
name = "gopkg.in/urfave/cli.v2"
packages = [
".",
"altsrc"
]
revision = "d604b6ffeee878fbf084fd2761466b6649989cee"
source = "https://github.com/cbranch/cli"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
name = "zombiezen.com/go/capnproto2"
packages = [
".",
"encoding/text",
"internal/fulfiller",
"internal/nodemap",
"internal/packed",
"internal/queue",
"internal/schema",
"internal/strquote",
"pogs",
"rpc",
"rpc/internal/refcount",
"schemas",
"server",
"std/capnp/rpc"
]
revision = "7cfd211c19c7f5783c695f3654efa46f0df259c3"
source = "https://github.com/zombiezen/go-capnproto2"
version = "v2.17.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ee681bef3527e49801c841e313f98b40116eafe8b60be21273956eeb96487486"
solver-name = "gps-cdcl"
solver-version = 1
-75
View File
@@ -1,75 +0,0 @@
[prune]
go-tests = true
unused-packages = true
[[prune.project]]
name = "github.com/cloudflare/brotli-go"
unused-packages = false
[[constraint]]
name = "github.com/facebookgo/grace"
branch = "master"
[[constraint]]
name = "github.com/getsentry/raven-go"
branch = "master"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.9.0-pre1"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.3"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
[[constraint]]
name = "golang.org/x/net"
branch = "master"
[[constraint]]
name = "golang.org/x/sync"
branch = "master"
[[constraint]]
name = "gopkg.in/urfave/cli.v2"
source = "https://github.com/cbranch/cli"
branch = "altsrc-parse-durations"
[[constraint]]
name = "zombiezen.com/go/capnproto2"
source = "https://github.com/zombiezen/go-capnproto2"
version = "2.17.1"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.2.0"
[[constraint]]
name = "github.com/coredns/coredns"
branch = "master"
[[constraint]]
name = "github.com/cloudflare/brotli-go"
branch = "master"
[[override]]
name = "github.com/mholt/caddy"
branch = "master"
[[constraint]]
branch = "master"
name = "github.com/coreos/go-oidc"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
+249 -28
View File
@@ -1,61 +1,282 @@
VERSION := $(shell git describe --tags --always --dirty="-dev")
VERSION := $(shell git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
MSI_VERSION := $(shell git tag -l --sort=v:refname | grep "w" | tail -1 | cut -c2-)
#MSI_VERSION expects the format of the tag to be: (wX.X.X). Starts with the w character to not break cfsetup.
#e.g. w3.0.1 or w4.2.10. It trims off the w character when creating the MSI.
ifeq ($(ORIGINAL_NAME), true)
# Used for builds that want FIPS compilation but want the artifacts generated to still have the original name.
BINARY_NAME := cloudflared
else ifeq ($(FIPS), true)
# Used for FIPS compliant builds that do not match the case above.
BINARY_NAME := cloudflared-fips
else
# Used for all other (non-FIPS) builds.
BINARY_NAME := cloudflared
endif
ifeq ($(NIGHTLY), true)
DEB_PACKAGE_NAME := $(BINARY_NAME)-nightly
NIGHTLY_FLAGS := --conflicts cloudflared --replaces cloudflared
else
DEB_PACKAGE_NAME := $(BINARY_NAME)
endif
DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC')
VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
VERSION_FLAGS := -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"
LINK_FLAGS :=
ifeq ($(FIPS), true)
LINK_FLAGS := -linkmode=external -extldflags=-static $(LINK_FLAGS)
# Prevent linking with libc regardless of CGO enabled or not.
GO_BUILD_TAGS := $(GO_BUILD_TAGS) osusergo netgo fips
VERSION_FLAGS := $(VERSION_FLAGS) -X "main.BuildType=FIPS"
endif
LDFLAGS := -ldflags='$(VERSION_FLAGS) $(LINK_FLAGS)'
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/local/bin
INSTALL_BINDIR := /usr/bin/
MAN_DIR := /usr/share/man/man1/
EQUINOX_FLAGS = --version="$(VERSION)" \
--platforms="$(EQUINOX_BUILD_PLATFORMS)" \
--app="$(EQUINOX_APP_ID)" \
--token="$(EQUINOX_TOKEN)" \
--channel="$(EQUINOX_CHANNEL)"
LOCAL_ARCH ?= $(shell uname -m)
ifneq ($(GOARCH),)
TARGET_ARCH ?= $(GOARCH)
else ifeq ($(LOCAL_ARCH),x86_64)
TARGET_ARCH ?= amd64
else ifeq ($(LOCAL_ARCH),amd64)
TARGET_ARCH ?= amd64
else ifeq ($(LOCAL_ARCH),i686)
TARGET_ARCH ?= amd64
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
TARGET_ARCH ?= arm64
else ifeq ($(LOCAL_ARCH),aarch64)
TARGET_ARCH ?= arm64
else ifeq ($(LOCAL_ARCH),arm64)
TARGET_ARCH ?= arm64
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
TARGET_ARCH ?= arm
else
$(error This system's architecture $(LOCAL_ARCH) isn't supported)
endif
ifeq ($(EQUINOX_IS_DRAFT), true)
EQUINOX_FLAGS := --draft $(EQUINOX_FLAGS)
LOCAL_OS ?= $(shell go env GOOS)
ifeq ($(LOCAL_OS),linux)
TARGET_OS ?= linux
else ifeq ($(LOCAL_OS),darwin)
TARGET_OS ?= darwin
else ifeq ($(LOCAL_OS),windows)
TARGET_OS ?= windows
else ifeq ($(LOCAL_OS),freebsd)
TARGET_OS ?= freebsd
else
$(error This system's OS $(LOCAL_OS) isn't supported)
endif
ifeq ($(TARGET_OS), windows)
EXECUTABLE_PATH=./$(BINARY_NAME).exe
else
EXECUTABLE_PATH=./$(BINARY_NAME)
endif
ifeq ($(FLAVOR), centos-7)
TARGET_PUBLIC_REPO ?= el7
else
TARGET_PUBLIC_REPO ?= $(FLAVOR)
endif
.PHONY: all
all: cloudflared test
.PHONY: clean
clean:
go clean
.PHONY: cloudflared
cloudflared:
go build -v $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/cloudflared
ifeq ($(FIPS), true)
$(info Building cloudflared with go-fips)
cp -f fips/fips.go.linux-amd64 cmd/cloudflared/fips.go
endif
GOOS=$(TARGET_OS) GOARCH=$(TARGET_ARCH) go build -v -mod=vendor $(GO_BUILD_TAGS) $(LDFLAGS) $(IMPORT_PATH)/cmd/cloudflared
ifeq ($(FIPS), true)
rm -f cmd/cloudflared/fips.go
./check-fips.sh cloudflared
endif
.PHONY: container
container:
docker build --build-arg=TARGET_ARCH=$(TARGET_ARCH) --build-arg=TARGET_OS=$(TARGET_OS) -t cloudflare/cloudflared-$(TARGET_OS)-$(TARGET_ARCH):"$(VERSION)" .
.PHONY: test
test:
go test -v -race $(VERSION_FLAGS) ./...
test: vet
ifndef CI
go test -v -mod=vendor -race $(LDFLAGS) ./...
else
@mkdir -p .cover
go test -v -mod=vendor -race $(LDFLAGS) -coverprofile=".cover/c.out" ./...
go tool cover -html ".cover/c.out" -o .cover/all.html
endif
.PHONY: test-ssh-server
test-ssh-server:
docker-compose -f ssh_server_tests/docker-compose.yml up
define publish_package
chmod 664 $(BINARY_NAME)*.$(1); \
for HOST in $(CF_PKG_HOSTS); do \
ssh-keyscan -t ecdsa $$HOST >> ~/.ssh/known_hosts; \
scp -p -4 $(BINARY_NAME)*.$(1) cfsync@$$HOST:/state/cf-pkg/staging/$(2)/$(TARGET_PUBLIC_REPO)/$(BINARY_NAME)/; \
done
endef
.PHONY: publish-deb
publish-deb: cloudflared-deb
$(call publish_package,deb,apt)
.PHONY: publish-rpm
publish-rpm: cloudflared-rpm
$(call publish_package,rpm,yum)
# 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
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t $(1) \
--description 'Cloudflare Tunnel daemon' \
--vendor 'Cloudflare' \
--license 'Cloudflare Service Agreement' \
--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)
endef
.PHONY: cloudflared-deb
cloudflared-deb: cloudflared
mkdir -p $(PACKAGE_DIR)
cp cloudflared $(PACKAGE_DIR)/cloudflared
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t deb --deb-compression bzip2 \
-a $(GOARCH) -v $(VERSION) -n cloudflared
$(call build_package,deb)
.PHONY: cloudflared-rpm
cloudflared-rpm: cloudflared
$(call build_package,rpm)
.PHONY: cloudflared-pkg
cloudflared-pkg: cloudflared
$(call build_package,osxpkg)
.PHONY: cloudflared-msi
cloudflared-msi: cloudflared
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
.PHONY: cloudflared-darwin-amd64.tgz
cloudflared-darwin-amd64.tgz: cloudflared
tar czf cloudflared-darwin-amd64.tgz cloudflared
rm cloudflared
.PHONY: cloudflared-junos
cloudflared-junos: cloudflared jetez-certificate.pem jetez-key.pem
jetez --source . \
-j jet.yaml \
--key jetez-key.pem \
--cert jetez-certificate.pem \
--version $(VERSION)
rm jetez-*.pem
jetez-certificate.pem:
ifndef JETEZ_CERT
$(error JETEZ_CERT not defined)
endif
@echo "Writing JetEZ certificate"
@echo "$$JETEZ_CERT" > jetez-certificate.pem
jetez-key.pem:
ifndef JETEZ_KEY
$(error JETEZ_KEY not defined)
endif
@echo "Writing JetEZ key"
@echo "$$JETEZ_KEY" > jetez-key.pem
.PHONY: publish-cloudflared-junos
publish-cloudflared-junos: cloudflared-junos cloudflared-x86-64.latest.s3
ifndef S3_ENDPOINT
$(error S3_HOST not defined)
endif
ifndef S3_URI
$(error S3_URI not defined)
endif
ifndef S3_ACCESS_KEY
$(error S3_ACCESS_KEY not defined)
endif
ifndef S3_SECRET_KEY
$(error S3_SECRET_KEY not defined)
endif
sha256sum cloudflared-x86-64-$(VERSION).tgz | awk '{printf $$1}' > cloudflared-x86-64-$(VERSION).tgz.shasum
s4cmd --endpoint-url $(S3_ENDPOINT) --force --API-GrantRead=uri=http://acs.amazonaws.com/groups/global/AllUsers \
put cloudflared-x86-64-$(VERSION).tgz $(S3_URI)/cloudflared-x86-64-$(VERSION).tgz
s4cmd --endpoint-url $(S3_ENDPOINT) --force --API-GrantRead=uri=http://acs.amazonaws.com/groups/global/AllUsers \
put cloudflared-x86-64-$(VERSION).tgz.shasum $(S3_URI)/cloudflared-x86-64-$(VERSION).tgz.shasum
dpkg --compare-versions "$(VERSION)" gt "$(shell cat cloudflared-x86-64.latest.s3)" && \
echo -n "$(VERSION)" > cloudflared-x86-64.latest && \
s4cmd --endpoint-url $(S3_ENDPOINT) --force --API-GrantRead=uri=http://acs.amazonaws.com/groups/global/AllUsers \
put cloudflared-x86-64.latest $(S3_URI)/cloudflared-x86-64.latest || \
echo "Latest version not updated"
cloudflared-x86-64.latest.s3:
s4cmd --endpoint-url $(S3_ENDPOINT) --force \
get $(S3_URI)/cloudflared-x86-64.latest cloudflared-x86-64.latest.s3
.PHONY: homebrew-upload
homebrew-upload: cloudflared-darwin-amd64.tgz
aws s3 --endpoint-url $(S3_ENDPOINT) cp --acl public-read $$^ $(S3_URI)/cloudflared-$$(VERSION)-$1.tgz
aws s3 --endpoint-url $(S3_ENDPOINT) cp --acl public-read $(S3_URI)/cloudflared-$$(VERSION)-$1.tgz $(S3_URI)/cloudflared-stable-$1.tgz
.PHONY: homebrew-release
.PHONY: homebrew-release
homebrew-release: homebrew-upload
./publish-homebrew-formula.sh cloudflared-darwin-amd64.tgz $(VERSION) homebrew-cloudflare
.PHONY: release
release: bin/equinox
bin/equinox release $(EQUINOX_FLAGS) -- $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/cloudflared
.PHONY: github-release
github-release: cloudflared
python3 github_release.py --path $(EXECUTABLE_PATH) --release-version $(VERSION)
bin/equinox:
mkdir -p bin
curl -s https://bin.equinox.io/c/75JtLRTsJ3n/release-tool-beta-$(EQUINOX_PLATFORM).tgz | tar xz -C bin/
.PHONY: github-release-built-pkgs
github-release-built-pkgs:
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
.PHONY: tunnel-deps
tunnel-deps:
capnp compile -ogo -I ./tunnelrpc tunnelrpc/tunnelrpc.capnp
.PHONY: github-message
github-message:
python3 github_message.py --release-version $(VERSION)
.PHONY: github-mac-upload
github-mac-upload:
python3 github_release.py --path artifacts/cloudflared-darwin-amd64.tgz --release-version $(VERSION) --name cloudflared-darwin-amd64.tgz
python3 github_release.py --path artifacts/cloudflared-amd64.pkg --release-version $(VERSION) --name cloudflared-amd64.pkg
.PHONY: tunnelrpc-deps
tunnelrpc-deps:
which capnp # https://capnproto.org/install.html
which capnpc-go # go get zombiezen.com/go/capnproto2/capnpc-go
capnp compile -ogo tunnelrpc/tunnelrpc.capnp
.PHONY: quic-deps
quic-deps:
which capnp
which capnpc-go
capnp compile -ogo quic/schema/quic_metadata_protocol.capnp
.PHONY: vet
vet:
go vet -mod=vendor ./...
# go get github.com/sudarshan-reddy/go-sumtype (don't do this in build directory or this will cause vendor issues)
# Note: If you have github.com/BurntSushi/go-sumtype then you might have to use the repo above instead
# for now because it uses an older version of golang.org/x/tools.
which go-sumtype
go-sumtype $$(go list -mod=vendor ./...)
.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
+56 -5
View File
@@ -1,9 +1,60 @@
# Argo Tunnel client
# Cloudflare Tunnel client
Contains the command-line client and its libraries for Argo Tunnel, a tunneling daemon that proxies any local webserver through the Cloudflare network.
Contains the command-line client for Cloudflare Tunnel, a tunneling daemon that proxies traffic from the Cloudflare network to your origins.
This daemon sits between Cloudflare network and your origin (e.g. a webserver). Cloudflare attracts client requests and sends them to you
via this daemon, without requiring you to poke holes on your firewall --- your origin can remain as closed as possible.
Extensive documentation can be found in the [Cloudflare Tunnel section](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps) of the Cloudflare Docs.
All usages related with proxying to your origins are available under `cloudflared tunnel help`.
## Getting started
You can also use `cloudflared` to access Tunnel origins (that are protected with `cloudflared tunnel`) for TCP traffic
at Layer 4 (i.e., not HTTP/websocket), which is relevant for use cases such as SSH, RDP, etc.
Such usages are available under `cloudflared access help`.
go install github.com/cloudflare/cloudflared/cmd/cloudflared
You can instead use [WARP client](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
to access private origins behind Tunnels for Layer 4 traffic without requiring `cloudflared access` commands on the client side.
User documentation for Argo Tunnel can be found at https://developers.cloudflare.com/argo-tunnel/
## Before you get started
Before you use Cloudflare Tunnel, you'll need to complete a few steps in the Cloudflare dashboard: you need to add a
website to your Cloudflare account. Note that today it is possible to use Tunnel without a website (e.g. for private
routing), but for legacy reasons this requirement is still necessary:
1. [Add a website to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/201720164-Creating-a-Cloudflare-account-and-adding-a-website)
2. [Change your domain nameservers to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/205195708)
## Installing `cloudflared`
Downloads are available as standalone binaries, a Docker image, and Debian, RPM, and Homebrew packages. You can also find releases here on the `cloudflared` GitHub repository.
* You can [install on macOS](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#macos) via Homebrew or by downloading the [latest Darwin amd64 release](https://github.com/cloudflare/cloudflared/releases)
* Binaries, Debian, and RPM packages for Linux [can be found here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#linux)
* A Docker image of `cloudflared` is [available on DockerHub](https://hub.docker.com/r/cloudflare/cloudflared)
* You can install on Windows machines with the [steps here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#windows)
User documentation for Cloudflare Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
## Creating Tunnels and routing traffic
Once installed, you can authenticate `cloudflared` into your Cloudflare account and begin creating Tunnels to serve traffic to your origins.
* Create a Tunnel with [these instructions](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/create-tunnel)
* Route traffic to that Tunnel:
* Via public [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns)
* Or via a public hostname guided by a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb)
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
## TryCloudflare
Want to test Cloudflare Tunnel before adding a website to Cloudflare? You can do so with TryCloudflare using the documentation [available here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare).
## Deprecated versions
Cloudflare currently supports versions of `cloudflared` 2020.5.1 and later. Breaking changes unrelated to feature availability may be introduced that will impact versions released prior to 2020.5.1. You can read more about upgrading `cloudflared` in our [developer documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#updating-cloudflared).
| Version(s) | Deprecation status |
|---|---|
| 2020.5.1 and later | Supported |
| Versions prior to 2020.5.1 | No longer supported |
+1043
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
echo $VERSION
# This controls the directory the built artifacts go into
export ARTIFACT_DIR=built_artifacts/
mkdir -p $ARTIFACT_DIR
arch=("amd64")
export TARGET_ARCH=$arch
export TARGET_OS=linux
export FIPS=true
# For BoringCrypto to link, we need CGO enabled. Otherwise compilation fails.
export CGO_ENABLED=1
make cloudflared-deb
mv cloudflared-fips\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-fips-linux-$arch.deb
# rpm packages invert the - and _ and use x86_64 instead of amd64.
RPMVERSION=$(echo $VERSION|sed -r 's/-/_/g')
RPMARCH="x86_64"
make cloudflared-rpm
mv cloudflared-fips-$RPMVERSION-1.$RPMARCH.rpm $ARTIFACT_DIR/cloudflared-fips-linux-$RPMARCH.rpm
# finally move the linux binary as well.
mv ./cloudflared $ARTIFACT_DIR/cloudflared-fips-linux-$arch
+41
View File
@@ -0,0 +1,41 @@
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
echo $VERSION
# Avoid depending on C code since we don't need it.
export CGO_ENABLED=0
# This controls the directory the built artifacts go into
export ARTIFACT_DIR=built_artifacts/
mkdir -p $ARTIFACT_DIR
windowsArchs=("amd64" "386")
export TARGET_OS=windows
for arch in ${windowsArchs[@]}; do
export TARGET_ARCH=$arch
make cloudflared-msi
mv ./cloudflared.exe $ARTIFACT_DIR/cloudflared-windows-$arch.exe
mv cloudflared-$VERSION-$arch.msi $ARTIFACT_DIR/cloudflared-windows-$arch.msi
done
linuxArchs=("386" "amd64" "arm" "arm64")
export TARGET_OS=linux
for arch in ${linuxArchs[@]}; do
export TARGET_ARCH=$arch
make cloudflared-deb
mv cloudflared\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-linux-$arch.deb
# rpm packages invert the - and _ and use x86_64 instead of amd64.
RPMVERSION=$(echo $VERSION|sed -r 's/-/_/g')
RPMARCH=$arch
if [ $arch == "amd64" ];then
RPMARCH="x86_64"
fi
if [ $arch == "arm64" ]; then
RPMARCH="aarch64"
fi
make cloudflared-rpm
mv cloudflared-$RPMVERSION-1.$RPMARCH.rpm $ARTIFACT_DIR/cloudflared-linux-$RPMARCH.rpm
# finally move the linux binary as well.
mv ./cloudflared $ARTIFACT_DIR/cloudflared-linux-$arch
done
+185
View File
@@ -0,0 +1,185 @@
//Package carrier provides a WebSocket proxy to carry or proxy a connection
//from the local client to the edge. See it as a wrapper around any protocol
//that it packages up in a WebSocket connection to the edge.
package carrier
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/token"
)
const (
LogFieldOriginURL = "originURL"
CFAccessTokenHeader = "Cf-Access-Token"
cfJumpDestinationHeader = "Cf-Access-Jump-Destination"
)
type StartOptions struct {
AppInfo *token.AppInfo
OriginURL string
Headers http.Header
Host string
TLSClientConfig *tls.Config
}
// Connection wraps up all the needed functions to forward over the tunnel
type Connection interface {
// ServeStream is used to forward data from the client to the edge
ServeStream(*StartOptions, io.ReadWriter) error
}
// StdinoutStream is empty struct for wrapping stdin/stdout
// into a single ReadWriter
type StdinoutStream struct{}
// Read will read from Stdin
func (c *StdinoutStream) Read(p []byte) (int, error) {
return os.Stdin.Read(p)
}
// Write will write to Stdout
func (c *StdinoutStream) Write(p []byte) (int, error) {
return os.Stdout.Write(p)
}
// Helper to allow deferring the response close with a check that the resp is not nil
func closeRespBody(resp *http.Response) {
if resp != nil {
_ = resp.Body.Close()
}
}
// StartForwarder will setup a listener on a specified address/port and then
// forward connections to the origin by calling `Serve()`.
func StartForwarder(conn Connection, address string, shutdownC <-chan struct{}, options *StartOptions) error {
listener, err := net.Listen("tcp", address)
if err != nil {
return errors.Wrap(err, "failed to start forwarding server")
}
return Serve(conn, listener, shutdownC, options)
}
// StartClient will copy the data from stdin/stdout over a WebSocket connection
// to the edge (originURL)
func StartClient(conn Connection, stream io.ReadWriter, options *StartOptions) error {
return conn.ServeStream(options, stream)
}
// Serve accepts incoming connections on the specified net.Listener.
// Each connection is handled in a new goroutine: its data is copied over a
// WebSocket connection to the edge (originURL).
// `Serve` always closes `listener`.
func Serve(remoteConn Connection, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
defer listener.Close()
errChan := make(chan error)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
// don't block if parent goroutine quit early
select {
case errChan <- err:
default:
}
return
}
go serveConnection(remoteConn, conn, options)
}
}()
select {
case <-shutdownC:
return nil
case err := <-errChan:
return err
}
}
// serveConnection handles connections for the Serve() call
func serveConnection(remoteConn Connection, c net.Conn, options *StartOptions) {
defer c.Close()
_ = remoteConn.ServeStream(options, c)
}
// IsAccessResponse checks the http Response to see if the url location
// contains the Access structure.
func IsAccessResponse(resp *http.Response) bool {
if resp == nil || resp.StatusCode != http.StatusFound {
return false
}
location, err := resp.Location()
if err != nil || location == nil {
return false
}
if strings.HasPrefix(location.Path, token.AccessLoginWorkerPath) {
return true
}
return false
}
// BuildAccessRequest builds an HTTP request with the Access token set
func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
}
token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, log)
if err != nil {
return nil, err
}
// We need to create a new request as FetchToken will modify req (boo mutable)
// as it has to follow redirect on the API and such, so here we init a new one
originRequest, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
}
originRequest.Header.Set(CFAccessTokenHeader, token)
for k, v := range options.Headers {
if len(v) >= 1 {
originRequest.Header.Set(k, v[0])
}
}
return originRequest, nil
}
func SetBastionDest(header http.Header, destination string) {
if destination != "" {
header.Set(cfJumpDestinationHeader, destination)
}
}
func ResolveBastionDest(r *http.Request) (string, error) {
jumpDestination := r.Header.Get(cfJumpDestinationHeader)
if jumpDestination == "" {
return "", fmt.Errorf("Did not receive final destination from client. The --destination flag is likely not set on the client side")
}
// Strip scheme and path set by client. Without a scheme
// Parsing a hostname and path without scheme might not return an error due to parsing ambiguities
if jumpURL, err := url.Parse(jumpDestination); err == nil && jumpURL.Host != "" {
return removePath(jumpURL.Host), nil
}
return removePath(jumpDestination), nil
}
func removePath(dest string) string {
return strings.SplitN(dest, "/", 2)[0]
}
+254
View File
@@ -0,0 +1,254 @@
package carrier
import (
"bytes"
"io"
"net"
"net/http"
"net/http/httptest"
"sync"
"testing"
ws "github.com/gorilla/websocket"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
)
const (
// example in Sec-Websocket-Key in rfc6455
testSecWebsocketKey = "dGhlIHNhbXBsZSBub25jZQ=="
)
type testStreamer struct {
buf *bytes.Buffer
l sync.RWMutex
}
func newTestStream() *testStreamer {
return &testStreamer{buf: new(bytes.Buffer)}
}
func (s *testStreamer) Read(p []byte) (int, error) {
s.l.RLock()
defer s.l.RUnlock()
return s.buf.Read(p)
}
func (s *testStreamer) Write(p []byte) (int, error) {
s.l.Lock()
defer s.l.Unlock()
return s.buf.Write(p)
}
func TestStartClient(t *testing.T) {
message := "Good morning Austin! Time for another sunny day in the great state of Texas."
log := zerolog.Nop()
wsConn := NewWSConnection(&log)
ts := newTestWebSocketServer()
defer ts.Close()
buf := newTestStream()
options := &StartOptions{
OriginURL: "http://" + ts.Listener.Addr().String(),
Headers: nil,
}
err := StartClient(wsConn, buf, options)
assert.NoError(t, err)
_, _ = buf.Write([]byte(message))
readBuffer := make([]byte, len(message))
_, _ = buf.Read(readBuffer)
assert.Equal(t, message, string(readBuffer))
}
func TestStartServer(t *testing.T) {
listener, err := net.Listen("tcp", "localhost:")
if err != nil {
t.Fatalf("Error starting listener: %v", err)
}
message := "Good morning Austin! Time for another sunny day in the great state of Texas."
log := zerolog.Nop()
shutdownC := make(chan struct{})
wsConn := NewWSConnection(&log)
ts := newTestWebSocketServer()
defer ts.Close()
options := &StartOptions{
OriginURL: "http://" + ts.Listener.Addr().String(),
Headers: nil,
}
go func() {
err := Serve(wsConn, listener, shutdownC, options)
if err != nil {
t.Errorf("Error running server: %v", err)
return
}
}()
conn, err := net.Dial("tcp", listener.Addr().String())
_, _ = conn.Write([]byte(message))
readBuffer := make([]byte, len(message))
_, _ = conn.Read(readBuffer)
assert.Equal(t, string(readBuffer), message)
}
func TestIsAccessResponse(t *testing.T) {
validLocationHeader := http.Header{}
validLocationHeader.Add("location", "https://test.cloudflareaccess.com/cdn-cgi/access/login/blahblah")
invalidLocationHeader := http.Header{}
invalidLocationHeader.Add("location", "https://google.com")
testCases := []struct {
Description string
In *http.Response
ExpectedOut bool
}{
{"nil response", nil, false},
{"redirect with no location", &http.Response{StatusCode: http.StatusFound}, false},
{"200 ok", &http.Response{StatusCode: http.StatusOK}, false},
{"redirect with location", &http.Response{StatusCode: http.StatusFound, Header: validLocationHeader}, true},
{"redirect with invalid location", &http.Response{StatusCode: http.StatusFound, Header: invalidLocationHeader}, false},
}
for i, tc := range testCases {
if IsAccessResponse(tc.In) != tc.ExpectedOut {
t.Fatalf("Failed case %d -- %s", i, tc.Description)
}
}
}
func newTestWebSocketServer() *httptest.Server {
upgrader := ws.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
break
}
if err := conn.WriteMessage(mt, []byte(message)); err != nil {
break
}
}
}))
}
func testRequest(t *testing.T, url string, stream io.ReadWriter) *http.Request {
req, err := http.NewRequest("GET", url, stream)
if err != nil {
t.Fatalf("testRequestHeader error")
}
req.Header.Add("Connection", "Upgrade")
req.Header.Add("Upgrade", "WebSocket")
req.Header.Add("Sec-Websocket-Key", testSecWebsocketKey)
req.Header.Add("Sec-Websocket-Protocol", "tunnel-protocol")
req.Header.Add("Sec-Websocket-Version", "13")
req.Header.Add("User-Agent", "curl/7.59.0")
return req
}
func TestBastionDestination(t *testing.T) {
tests := []struct {
name string
header http.Header
expectedDest string
wantErr bool
}{
{
name: "hostname destination",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost"},
},
expectedDest: "localhost",
},
{
name: "hostname destination with port",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost:9000"},
},
expectedDest: "localhost:9000",
},
{
name: "hostname destination with scheme and port",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://localhost:9000"},
},
expectedDest: "localhost:9000",
},
{
name: "full hostname url",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://localhost:9000/metrics"},
},
expectedDest: "localhost:9000",
},
{
name: "hostname destination with port and path",
header: http.Header{
cfJumpDestinationHeader: []string{"localhost:9000/metrics"},
},
expectedDest: "localhost:9000",
},
{
name: "ip destination",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1"},
},
expectedDest: "127.0.0.1",
},
{
name: "ip destination with port",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1:9000"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "ip destination with port and path",
header: http.Header{
cfJumpDestinationHeader: []string{"127.0.0.1:9000/metrics"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "ip destination with schem and port",
header: http.Header{
cfJumpDestinationHeader: []string{"tcp://127.0.0.1:9000"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "full ip url",
header: http.Header{
cfJumpDestinationHeader: []string{"ssh://127.0.0.1:9000/metrics"},
},
expectedDest: "127.0.0.1:9000",
},
{
name: "no destination",
wantErr: true,
},
}
for _, test := range tests {
r := &http.Request{
Header: test.header,
}
dest, err := ResolveBastionDest(r)
if test.wantErr {
assert.Error(t, err, "Test %s expects error", test.name)
} else {
assert.NoError(t, err, "Test %s expects no error, got error %v", test.name, err)
assert.Equal(t, test.expectedDest, dest, "Test %s expect dest %s, got %s", test.name, test.expectedDest, dest)
}
}
}
+199
View File
@@ -0,0 +1,199 @@
package carrier
import (
"io"
"net/http"
"net/http/httputil"
"net/url"
"github.com/gorilla/websocket"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/token"
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
)
// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
// This implements the functions for glider proxy (sock5) and the carrier interface
type Websocket struct {
log *zerolog.Logger
isSocks bool
}
// NewWSConnection returns a new connection object
func NewWSConnection(log *zerolog.Logger) Connection {
return &Websocket{
log: log,
}
}
// ServeStream will create a Websocket client stream connection to the edge
// it blocks and writes the raw data from conn over the tunnel
func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) error {
wsConn, err := createWebsocketStream(options, ws.log)
if err != nil {
ws.log.Err(err).Str(LogFieldOriginURL, options.OriginURL).Msg("failed to connect to origin")
return err
}
defer wsConn.Close()
cfwebsocket.Stream(wsConn, conn, ws.log)
return nil
}
// createWebsocketStream will create a WebSocket connection to stream data over
// It also handles redirects from Access and will present that flow if
// the token is not present on the request
func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebsocket.GorillaConn, error) {
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
}
req.Header = options.Headers
if options.Host != "" {
req.Host = options.Host
}
dump, err := httputil.DumpRequest(req, false)
log.Debug().Msgf("Websocket request: %s", string(dump))
dialer := &websocket.Dialer{
TLSClientConfig: options.TLSClientConfig,
Proxy: http.ProxyFromEnvironment,
}
wsConn, resp, err := clientConnect(req, dialer)
defer closeRespBody(resp)
if err != nil && IsAccessResponse(resp) {
// Only get Access app info if we know the origin is protected by Access
originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
}
appInfo, err := token.GetAppInfo(originReq.URL)
if err != nil {
return nil, err
}
options.AppInfo = appInfo
wsConn, err = createAccessAuthenticatedStream(options, log)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return &cfwebsocket.GorillaConn{Conn: wsConn}, nil
}
var stripWebsocketHeaders = []string{
"Upgrade",
"Connection",
"Sec-Websocket-Key",
"Sec-Websocket-Version",
"Sec-Websocket-Extensions",
}
// the gorilla websocket library sets its own Upgrade, Connection, Sec-WebSocket-Key,
// Sec-WebSocket-Version and Sec-Websocket-Extensions headers.
// https://github.com/gorilla/websocket/blob/master/client.go#L189-L194.
func websocketHeaders(req *http.Request) http.Header {
wsHeaders := make(http.Header)
for key, val := range req.Header {
wsHeaders[key] = val
}
// Assume the header keys are in canonical format.
for _, header := range stripWebsocketHeaders {
wsHeaders.Del(header)
}
wsHeaders.Set("Host", req.Host) // See TUN-1097
return wsHeaders
}
// clientConnect creates a WebSocket client connection for provided request. Caller is responsible for closing
// the connection. The response body may not contain the entire response and does
// not need to be closed by the application.
func clientConnect(req *http.Request, dialler *websocket.Dialer) (*websocket.Conn, *http.Response, error) {
req.URL.Scheme = changeRequestScheme(req.URL)
wsHeaders := websocketHeaders(req)
if dialler == nil {
dialler = &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
}
}
conn, response, err := dialler.Dial(req.URL.String(), wsHeaders)
if err != nil {
return nil, response, err
}
return conn, response, nil
}
// changeRequestScheme is needed as the gorilla websocket library requires the ws scheme.
// (even though it changes it back to http/https, but ¯\_(ツ)_/¯.)
func changeRequestScheme(reqURL *url.URL) string {
switch reqURL.Scheme {
case "https":
return "wss"
case "http":
return "ws"
case "":
return "ws"
default:
return reqURL.Scheme
}
}
// createAccessAuthenticatedStream will try load a token from storage and make
// a connection with the token set on the request. If it still get redirect,
// this probably means the token in storage is invalid (expired/revoked). If that
// happens it deletes the token and runs the connection again, so the user can
// login again and generate a new one.
func createAccessAuthenticatedStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, error) {
wsConn, resp, err := createAccessWebSocketStream(options, log)
defer closeRespBody(resp)
if err == nil {
return wsConn, nil
}
if !IsAccessResponse(resp) {
return nil, err
}
// Access Token is invalid for some reason. Go through regen flow
if err := token.RemoveTokenIfExists(options.AppInfo); err != nil {
return nil, err
}
wsConn, resp, err = createAccessWebSocketStream(options, log)
defer closeRespBody(resp)
if err != nil {
return nil, err
}
return wsConn, nil
}
// createAccessWebSocketStream builds an Access request and makes a connection
func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, *http.Response, error) {
req, err := BuildAccessRequest(options, log)
if err != nil {
return nil, nil, err
}
dump, err := httputil.DumpRequest(req, false)
log.Debug().Msgf("Access Websocket request: %s", string(dump))
conn, resp, err := clientConnect(req, nil)
if resp != nil {
r, err := httputil.DumpResponse(resp, true)
if r != nil {
log.Debug().Msgf("Websocket response: %q", r)
} else if err != nil {
log.Debug().Msgf("Websocket response error: %v", err)
}
}
return conn, resp, err
}
+123
View File
@@ -0,0 +1,123 @@
package carrier
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"math/rand"
"testing"
"time"
gws "github.com/gorilla/websocket"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/websocket"
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/tlsconfig"
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
)
func websocketClientTLSConfig(t *testing.T) *tls.Config {
certPool := x509.NewCertPool()
helloCert, err := tlsconfig.GetHelloCertificateX509()
assert.NoError(t, err)
certPool.AddCert(helloCert)
assert.NotNil(t, certPool)
return &tls.Config{RootCAs: certPool}
}
func TestWebsocketHeaders(t *testing.T) {
req := testRequest(t, "http://example.com", nil)
wsHeaders := websocketHeaders(req)
for _, header := range stripWebsocketHeaders {
assert.Empty(t, wsHeaders[header])
}
assert.Equal(t, "curl/7.59.0", wsHeaders.Get("User-Agent"))
}
func TestServe(t *testing.T) {
log := zerolog.Nop()
shutdownC := make(chan struct{})
errC := make(chan error)
listener, err := hello.CreateTLSListener("localhost:1111")
assert.NoError(t, err)
defer listener.Close()
go func() {
errC <- hello.StartHelloWorldServer(&log, listener, shutdownC)
}()
req := testRequest(t, "https://localhost:1111/ws", nil)
tlsConfig := websocketClientTLSConfig(t)
assert.NotNil(t, tlsConfig)
d := gws.Dialer{TLSClientConfig: tlsConfig}
conn, resp, err := clientConnect(req, &d)
assert.NoError(t, err)
assert.Equal(t, "websocket", resp.Header.Get("Upgrade"))
for i := 0; i < 1000; i++ {
messageSize := rand.Int()%2048 + 1
clientMessage := make([]byte, messageSize)
// rand.Read always returns len(clientMessage) and a nil error
rand.Read(clientMessage)
err = conn.WriteMessage(websocket.BinaryFrame, clientMessage)
assert.NoError(t, err)
messageType, message, err := conn.ReadMessage()
assert.NoError(t, err)
assert.Equal(t, websocket.BinaryFrame, messageType)
assert.Equal(t, clientMessage, message)
}
_ = conn.Close()
close(shutdownC)
<-errC
}
func TestWebsocketWrapper(t *testing.T) {
listener, err := hello.CreateTLSListener("localhost:0")
require.NoError(t, err)
serverErrorChan := make(chan error)
helloSvrCtx, cancelHelloSvr := context.WithCancel(context.Background())
defer func() { <-serverErrorChan }()
defer cancelHelloSvr()
go func() {
log := zerolog.Nop()
serverErrorChan <- hello.StartHelloWorldServer(&log, listener, helloSvrCtx.Done())
}()
tlsConfig := websocketClientTLSConfig(t)
d := gws.Dialer{TLSClientConfig: tlsConfig, HandshakeTimeout: time.Minute}
testAddr := fmt.Sprintf("https://%s/ws", listener.Addr().String())
req := testRequest(t, testAddr, nil)
conn, resp, err := clientConnect(req, &d)
require.NoError(t, err)
assert.Equal(t, "websocket", resp.Header.Get("Upgrade"))
// Websocket now connected to test server so lets check our wrapper
wrapper := cfwebsocket.GorillaConn{Conn: conn}
buf := make([]byte, 100)
wrapper.Write([]byte("abc"))
n, err := wrapper.Read(buf)
require.NoError(t, err)
require.Equal(t, n, 3)
require.Equal(t, "abc", string(buf[:n]))
// Test partial read, read 1 of 3 bytes in one read and the other 2 in another read
wrapper.Write([]byte("abc"))
buf = buf[:1]
n, err = wrapper.Read(buf)
require.NoError(t, err)
require.Equal(t, n, 1)
require.Equal(t, "a", string(buf[:n]))
buf = buf[:cap(buf)]
n, err = wrapper.Read(buf)
require.NoError(t, err)
require.Equal(t, n, 2)
require.Equal(t, "bc", string(buf[:n]))
}
+94
View File
@@ -0,0 +1,94 @@
package certutil
import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"strings"
)
type namedTunnelToken struct {
ZoneID string `json:"zoneID"`
AccountID string `json:"accountID"`
ServiceKey string `json:"serviceKey"`
}
type OriginCert struct {
PrivateKey interface{}
Cert *x509.Certificate
ZoneID string
ServiceKey string
AccountID string
}
func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
if len(blocks) == 0 {
return nil, fmt.Errorf("Cannot decode empty certificate")
}
originCert := OriginCert{}
block, rest := pem.Decode(blocks)
for {
if block == nil {
break
}
switch block.Type {
case "PRIVATE KEY":
if originCert.PrivateKey != nil {
return nil, fmt.Errorf("Found multiple private key in the certificate")
}
// RSA private key
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Cannot parse private key")
}
originCert.PrivateKey = privateKey
case "CERTIFICATE":
if originCert.Cert != nil {
return nil, fmt.Errorf("Found multiple certificates in the certificate")
}
cert, err := x509.ParseCertificates(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Cannot parse certificate")
} else if len(cert) > 1 {
return nil, fmt.Errorf("Found multiple certificates in the certificate")
}
originCert.Cert = cert[0]
case "WARP TOKEN", "ARGO TUNNEL TOKEN":
if originCert.ZoneID != "" || originCert.ServiceKey != "" {
return nil, fmt.Errorf("Found multiple tokens in the certificate")
}
// The token is a string,
// Try the newer JSON format
ntt := namedTunnelToken{}
if err := json.Unmarshal(block.Bytes, &ntt); err == nil {
originCert.ZoneID = ntt.ZoneID
originCert.ServiceKey = ntt.ServiceKey
originCert.AccountID = ntt.AccountID
} else {
// Try the older format, where the zoneID and service key are separated by
// a new line character
token := string(block.Bytes)
s := strings.Split(token, "\n")
if len(s) != 2 {
return nil, fmt.Errorf("Cannot parse token")
}
originCert.ZoneID = s[0]
originCert.ServiceKey = s[1]
}
default:
return nil, fmt.Errorf("Unknown block %s in the certificate", block.Type)
}
block, rest = pem.Decode(rest)
}
if originCert.PrivateKey == nil {
return nil, fmt.Errorf("Missing private key in the certificate")
} else if originCert.Cert == nil {
return nil, fmt.Errorf("Missing certificate in the certificate")
} else if originCert.ZoneID == "" || originCert.ServiceKey == "" {
return nil, fmt.Errorf("Missing token in the certificate")
}
return &originCert, nil
}
+67
View File
@@ -0,0 +1,67 @@
package certutil
import (
"fmt"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadOriginCert(t *testing.T) {
cert, err := DecodeOriginCert([]byte{})
assert.Equal(t, fmt.Errorf("Cannot decode empty certificate"), err)
assert.Nil(t, cert)
blocks, err := ioutil.ReadFile("test-cert-no-key.pem")
assert.Nil(t, err)
cert, err = DecodeOriginCert(blocks)
assert.Equal(t, fmt.Errorf("Missing private key in the certificate"), err)
assert.Nil(t, cert)
blocks, err = ioutil.ReadFile("test-cert-two-certificates.pem")
assert.Nil(t, err)
cert, err = DecodeOriginCert(blocks)
assert.Equal(t, fmt.Errorf("Found multiple certificates in the certificate"), err)
assert.Nil(t, cert)
blocks, err = ioutil.ReadFile("test-cert-unknown-block.pem")
assert.Nil(t, err)
cert, err = DecodeOriginCert(blocks)
assert.Equal(t, fmt.Errorf("Unknown block RSA PRIVATE KEY in the certificate"), err)
assert.Nil(t, cert)
blocks, err = ioutil.ReadFile("test-cert.pem")
assert.Nil(t, err)
cert, err = DecodeOriginCert(blocks)
assert.Nil(t, err)
assert.NotNil(t, cert)
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
key := "v1.0-58bd4f9e28f7b3c28e05a35ff3e80ab4fd9644ef3fece537eb0d12e2e9258217-183442fbb0bbdb3e571558fec9b5589ebd77aafc87498ee3f09f64a4ad79ffe8791edbae08b36c1d8f1d70a8670de56922dff92b15d214a524f4ebfa1958859e-7ce80f79921312a6022c5d25e2d380f82ceaefe3fbdc43dd13b080e3ef1e26f7"
assert.Equal(t, key, cert.ServiceKey)
}
func TestNewlineArgoTunnelToken(t *testing.T) {
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert.pem")
}
func TestJSONArgoTunnelToken(t *testing.T) {
// The given cert's Argo Tunnel Token was generated by base64 encoding this JSON:
// {
// "zoneID": "7b0a4d77dfb881c1a3b7d61ea9443e19",
// "serviceKey": "test-service-key",
// "accountID": "abcdabcdabcdabcd1234567890abcdef"
// }
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert-json.pem")
}
func ArgoTunnelTokenTest(t *testing.T, path string) {
blocks, err := ioutil.ReadFile(path)
assert.Nil(t, err)
cert, err := DecodeOriginCert(blocks)
assert.Nil(t, err)
assert.NotNil(t, cert)
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
key := "test-service-key"
assert.Equal(t, key, cert.ServiceKey)
}
+57
View File
@@ -0,0 +1,57 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN ARGO TUNNEL TOKEN-----
eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAi
c2VydmljZUtleSI6ICJ0ZXN0LXNlcnZpY2Uta2V5IiwgImFjY291bnRJRCI6ICJh
YmNkYWJjZGFiY2RhYmNkMTIzNDU2Nzg5MGFiY2RlZiJ9
-----END ARGO TUNNEL TOKEN-----
+56
View File
@@ -0,0 +1,56 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN ARGO TUNNEL TOKEN-----
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdGVzdC1zZXJ2aWNlLWtl
eQ==
-----END ARGO TUNNEL TOKEN-----
+33
View File
@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN WARP TOKEN-----
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
ZWYxZTI2Zjc=
-----END WARP TOKEN-----
+85
View File
@@ -0,0 +1,85 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN WARP TOKEN-----
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
ZWYxZTI2Zjc=
-----END WARP TOKEN-----
+89
View File
@@ -0,0 +1,89 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN WARP TOKEN-----
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
ZWYxZTI2Zjc=
-----END WARP TOKEN-----
-----BEGIN RSA PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END RSA PRIVATE KEY-----
+61
View File
@@ -0,0 +1,61 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
uQicoQq3yzeQh20wtrtaXzTNmA==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
x+Yo/cL8fGfVpPt4UM8=
-----END CERTIFICATE-----
-----BEGIN WARP TOKEN-----
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
ZWYxZTI2Zjc=
-----END WARP TOKEN-----
+186
View File
@@ -0,0 +1,186 @@
package cfapi
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/http2"
)
const (
defaultTimeout = 15 * time.Second
jsonContentType = "application/json"
)
var (
ErrUnauthorized = errors.New("unauthorized")
ErrBadRequest = errors.New("incorrect request parameters")
ErrNotFound = errors.New("not found")
ErrAPINoSuccess = errors.New("API call failed")
)
type RESTClient struct {
baseEndpoints *baseEndpoints
authToken string
userAgent string
client http.Client
log *zerolog.Logger
}
type baseEndpoints struct {
accountLevel url.URL
zoneLevel url.URL
accountRoutes url.URL
accountVnets url.URL
}
var _ Client = (*RESTClient)(nil)
func NewRESTClient(baseURL, accountTag, zoneTag, authToken, userAgent string, log *zerolog.Logger) (*RESTClient, error) {
if strings.HasSuffix(baseURL, "/") {
baseURL = baseURL[:len(baseURL)-1]
}
accountLevelEndpoint, err := url.Parse(fmt.Sprintf("%s/accounts/%s/tunnels", baseURL, accountTag))
if err != nil {
return nil, errors.Wrap(err, "failed to create account level endpoint")
}
accountRoutesEndpoint, err := url.Parse(fmt.Sprintf("%s/accounts/%s/teamnet/routes", baseURL, accountTag))
if err != nil {
return nil, errors.Wrap(err, "failed to create route account-level endpoint")
}
accountVnetsEndpoint, err := url.Parse(fmt.Sprintf("%s/accounts/%s/teamnet/virtual_networks", baseURL, accountTag))
if err != nil {
return nil, errors.Wrap(err, "failed to create virtual network account-level endpoint")
}
zoneLevelEndpoint, err := url.Parse(fmt.Sprintf("%s/zones/%s/tunnels", baseURL, zoneTag))
if err != nil {
return nil, errors.Wrap(err, "failed to create account level endpoint")
}
httpTransport := http.Transport{
TLSHandshakeTimeout: defaultTimeout,
ResponseHeaderTimeout: defaultTimeout,
}
http2.ConfigureTransport(&httpTransport)
return &RESTClient{
baseEndpoints: &baseEndpoints{
accountLevel: *accountLevelEndpoint,
zoneLevel: *zoneLevelEndpoint,
accountRoutes: *accountRoutesEndpoint,
accountVnets: *accountVnetsEndpoint,
},
authToken: authToken,
userAgent: userAgent,
client: http.Client{
Transport: &httpTransport,
Timeout: defaultTimeout,
},
log: log,
}, nil
}
func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
if bodyBytes, err := json.Marshal(body); err != nil {
return nil, errors.Wrap(err, "failed to serialize json body")
} else {
bodyReader = bytes.NewBuffer(bodyBytes)
}
}
req, err := http.NewRequest(method, url.String(), bodyReader)
if err != nil {
return nil, errors.Wrapf(err, "can't create %s request", method)
}
req.Header.Set("User-Agent", r.userAgent)
if bodyReader != nil {
req.Header.Set("Content-Type", jsonContentType)
}
req.Header.Add("X-Auth-User-Service-Key", r.authToken)
req.Header.Add("Accept", "application/json;version=1")
return r.client.Do(req)
}
func parseResponse(reader io.Reader, data interface{}) error {
// Schema for Tunnelstore responses in the v1 API.
// Roughly, it's a wrapper around a particular result that adds failures/errors/etc
var result response
// First, parse the wrapper and check the API call succeeded
if err := json.NewDecoder(reader).Decode(&result); err != nil {
return errors.Wrap(err, "failed to decode response")
}
if err := result.checkErrors(); err != nil {
return err
}
if !result.Success {
return ErrAPINoSuccess
}
// At this point we know the API call succeeded, so, parse out the inner
// result into the datatype provided as a parameter.
if err := json.Unmarshal(result.Result, &data); err != nil {
return errors.Wrap(err, "the Cloudflare API response was an unexpected type")
}
return nil
}
type response struct {
Success bool `json:"success,omitempty"`
Errors []apiErr `json:"errors,omitempty"`
Messages []string `json:"messages,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
}
func (r *response) checkErrors() error {
if len(r.Errors) == 0 {
return nil
}
if len(r.Errors) == 1 {
return r.Errors[0]
}
var messages string
for _, e := range r.Errors {
messages += fmt.Sprintf("%s; ", e)
}
return fmt.Errorf("API errors: %s", messages)
}
type apiErr struct {
Code json.Number `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
func (e apiErr) Error() string {
return fmt.Sprintf("code: %v, reason: %s", e.Code, e.Message)
}
func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
if resp.Header.Get("Content-Type") == "application/json" {
var errorsResp response
if json.NewDecoder(resp.Body).Decode(&errorsResp) == nil {
if err := errorsResp.checkErrors(); err != nil {
return errors.Errorf("Failed to %s: %s", op, err)
}
}
}
switch resp.StatusCode {
case http.StatusOK:
return nil
case http.StatusBadRequest:
return ErrBadRequest
case http.StatusUnauthorized, http.StatusForbidden:
return ErrUnauthorized
case http.StatusNotFound:
return ErrNotFound
}
return errors.Errorf("API call to %s failed with status %d: %s", op,
resp.StatusCode, http.StatusText(resp.StatusCode))
}
+39
View File
@@ -0,0 +1,39 @@
package cfapi
import (
"github.com/google/uuid"
)
type TunnelClient interface {
CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error)
GetTunnel(tunnelID uuid.UUID) (*Tunnel, error)
DeleteTunnel(tunnelID uuid.UUID) error
ListTunnels(filter *TunnelFilter) ([]*Tunnel, error)
ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error)
CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error
}
type HostnameClient interface {
RouteTunnel(tunnelID uuid.UUID, route HostnameRoute) (HostnameRouteResult, error)
}
type IPRouteClient interface {
ListRoutes(filter *IpRouteFilter) ([]*DetailedRoute, error)
AddRoute(newRoute NewRoute) (Route, error)
DeleteRoute(params DeleteRouteParams) error
GetByIP(params GetRouteByIpParams) (DetailedRoute, error)
}
type VnetClient interface {
CreateVirtualNetwork(newVnet NewVirtualNetwork) (VirtualNetwork, error)
ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, error)
DeleteVirtualNetwork(id uuid.UUID) error
UpdateVirtualNetwork(id uuid.UUID, updates UpdateVirtualNetwork) error
}
type Client interface {
TunnelClient
HostnameClient
IPRouteClient
VnetClient
}
+192
View File
@@ -0,0 +1,192 @@
package cfapi
import (
"encoding/json"
"fmt"
"io"
"net/http"
"path"
"github.com/google/uuid"
"github.com/pkg/errors"
)
type Change = string
const (
ChangeNew = "new"
ChangeUpdated = "updated"
ChangeUnchanged = "unchanged"
)
// HostnameRoute represents a record type that can route to a tunnel
type HostnameRoute interface {
json.Marshaler
RecordType() string
UnmarshalResult(body io.Reader) (HostnameRouteResult, error)
String() string
}
type HostnameRouteResult interface {
// SuccessSummary explains what will route to this tunnel when it's provisioned successfully
SuccessSummary() string
}
type DNSRoute struct {
userHostname string
overwriteExisting bool
}
type DNSRouteResult struct {
route *DNSRoute
CName Change `json:"cname"`
Name string `json:"name"`
}
func NewDNSRoute(userHostname string, overwriteExisting bool) HostnameRoute {
return &DNSRoute{
userHostname: userHostname,
overwriteExisting: overwriteExisting,
}
}
func (dr *DNSRoute) MarshalJSON() ([]byte, error) {
s := struct {
Type string `json:"type"`
UserHostname string `json:"user_hostname"`
OverwriteExisting bool `json:"overwrite_existing"`
}{
Type: dr.RecordType(),
UserHostname: dr.userHostname,
OverwriteExisting: dr.overwriteExisting,
}
return json.Marshal(&s)
}
func (dr *DNSRoute) UnmarshalResult(body io.Reader) (HostnameRouteResult, error) {
var result DNSRouteResult
err := parseResponse(body, &result)
result.route = dr
return &result, err
}
func (dr *DNSRoute) RecordType() string {
return "dns"
}
func (dr *DNSRoute) String() string {
return fmt.Sprintf("%s %s", dr.RecordType(), dr.userHostname)
}
func (res *DNSRouteResult) SuccessSummary() string {
var msgFmt string
switch res.CName {
case ChangeNew:
msgFmt = "Added CNAME %s which will route to this tunnel"
case ChangeUpdated: // this is not currently returned by tunnelsore
msgFmt = "%s updated to route to your tunnel"
case ChangeUnchanged:
msgFmt = "%s is already configured to route to your tunnel"
}
return fmt.Sprintf(msgFmt, res.hostname())
}
// hostname yields the resulting name for the DNS route; if that is not available from Cloudflare API, then the
// requested name is returned instead (should not be the common path, it is just a fall-back).
func (res *DNSRouteResult) hostname() string {
if res.Name != "" {
return res.Name
}
return res.route.userHostname
}
type LBRoute struct {
lbName string
lbPool string
}
type LBRouteResult struct {
route *LBRoute
LoadBalancer Change `json:"load_balancer"`
Pool Change `json:"pool"`
}
func NewLBRoute(lbName, lbPool string) HostnameRoute {
return &LBRoute{
lbName: lbName,
lbPool: lbPool,
}
}
func (lr *LBRoute) MarshalJSON() ([]byte, error) {
s := struct {
Type string `json:"type"`
LBName string `json:"lb_name"`
LBPool string `json:"lb_pool"`
}{
Type: lr.RecordType(),
LBName: lr.lbName,
LBPool: lr.lbPool,
}
return json.Marshal(&s)
}
func (lr *LBRoute) RecordType() string {
return "lb"
}
func (lb *LBRoute) String() string {
return fmt.Sprintf("%s %s %s", lb.RecordType(), lb.lbName, lb.lbPool)
}
func (lr *LBRoute) UnmarshalResult(body io.Reader) (HostnameRouteResult, error) {
var result LBRouteResult
err := parseResponse(body, &result)
result.route = lr
return &result, err
}
func (res *LBRouteResult) SuccessSummary() string {
var msg string
switch res.LoadBalancer + "," + res.Pool {
case "new,new":
msg = "Created load balancer %s and added a new pool %s with this tunnel as an origin"
case "new,updated":
msg = "Created load balancer %s with an existing pool %s which was updated to use this tunnel as an origin"
case "new,unchanged":
msg = "Created load balancer %s with an existing pool %s which already has this tunnel as an origin"
case "updated,new":
msg = "Added new pool %[2]s with this tunnel as an origin to load balancer %[1]s"
case "updated,updated":
msg = "Updated pool %[2]s to use this tunnel as an origin and added it to load balancer %[1]s"
case "updated,unchanged":
msg = "Added pool %[2]s, which already has this tunnel as an origin, to load balancer %[1]s"
case "unchanged,updated":
msg = "Added this tunnel as an origin in pool %[2]s which is already used by load balancer %[1]s"
case "unchanged,unchanged":
msg = "Load balancer %s already uses pool %s which has this tunnel as an origin"
case "unchanged,new":
// this state is not possible
fallthrough
default:
msg = "Something went wrong: failed to modify load balancer %s with pool %s; please check traffic manager configuration in the dashboard"
}
return fmt.Sprintf(msg, res.route.lbName, res.route.lbPool)
}
func (r *RESTClient) RouteTunnel(tunnelID uuid.UUID, route HostnameRoute) (HostnameRouteResult, error) {
endpoint := r.baseEndpoints.zoneLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/routes", tunnelID))
resp, err := r.sendRequest("PUT", endpoint, route)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return route.UnmarshalResult(resp.Body)
}
return nil, r.statusCodeToError("add route", resp)
}
+99
View File
@@ -0,0 +1,99 @@
package cfapi
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDNSRouteUnmarshalResult(t *testing.T) {
route := &DNSRoute{
userHostname: "example.com",
}
result, err := route.UnmarshalResult(strings.NewReader(`{"success": true, "result": {"cname": "new"}}`))
assert.NoError(t, err)
assert.Equal(t, &DNSRouteResult{
route: route,
CName: ChangeNew,
}, result)
badJSON := []string{
`abc`,
`{"success": false, "result": {"cname": "new"}}`,
`{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}], "result": {"cname": "new"}}`,
`{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}, {"code": 1004, "message":"Cannot use tunnel as origin for non-proxied load balancer"}], "result": {"cname": "new"}}`,
`{"result": {"cname": "new"}}`,
`{"result": {"cname": "new"}}`,
}
for _, j := range badJSON {
_, err = route.UnmarshalResult(strings.NewReader(j))
assert.NotNil(t, err)
}
}
func TestLBRouteUnmarshalResult(t *testing.T) {
route := &LBRoute{
lbName: "lb.example.com",
lbPool: "pool",
}
result, err := route.UnmarshalResult(strings.NewReader(`{"success": true, "result": {"pool": "unchanged", "load_balancer": "updated"}}`))
assert.NoError(t, err)
assert.Equal(t, &LBRouteResult{
route: route,
LoadBalancer: ChangeUpdated,
Pool: ChangeUnchanged,
}, result)
badJSON := []string{
`abc`,
`{"success": false, "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
`{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}], "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
`{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}, {"code": 1004, "message":"Cannot use tunnel as origin for non-proxied load balancer"}], "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
`{"result": {"pool": "unchanged", "load_balancer": "updated"}}`,
}
for _, j := range badJSON {
_, err = route.UnmarshalResult(strings.NewReader(j))
assert.NotNil(t, err)
}
}
func TestLBRouteResultSuccessSummary(t *testing.T) {
route := &LBRoute{
lbName: "lb.example.com",
lbPool: "POOL",
}
tests := []struct {
lb Change
pool Change
expected string
}{
{ChangeNew, ChangeNew, "Created load balancer lb.example.com and added a new pool POOL with this tunnel as an origin"},
{ChangeNew, ChangeUpdated, "Created load balancer lb.example.com with an existing pool POOL which was updated to use this tunnel as an origin"},
{ChangeNew, ChangeUnchanged, "Created load balancer lb.example.com with an existing pool POOL which already has this tunnel as an origin"},
{ChangeUpdated, ChangeNew, "Added new pool POOL with this tunnel as an origin to load balancer lb.example.com"},
{ChangeUpdated, ChangeUpdated, "Updated pool POOL to use this tunnel as an origin and added it to load balancer lb.example.com"},
{ChangeUpdated, ChangeUnchanged, "Added pool POOL, which already has this tunnel as an origin, to load balancer lb.example.com"},
{ChangeUnchanged, ChangeNew, "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard"},
{ChangeUnchanged, ChangeUpdated, "Added this tunnel as an origin in pool POOL which is already used by load balancer lb.example.com"},
{ChangeUnchanged, ChangeUnchanged, "Load balancer lb.example.com already uses pool POOL which has this tunnel as an origin"},
{"", "", "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard"},
{"a", "b", "Something went wrong: failed to modify load balancer lb.example.com with pool POOL; please check traffic manager configuration in the dashboard"},
}
for i, tt := range tests {
res := &LBRouteResult{
route: route,
LoadBalancer: tt.lb,
Pool: tt.pool,
}
actual := res.SuccessSummary()
assert.Equal(t, tt.expected, actual, "case %d", i+1)
}
}
+240
View File
@@ -0,0 +1,240 @@
package cfapi
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"path"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
// Route is a mapping from customer's IP space to a tunnel.
// Each route allows the customer to route eyeballs in their corporate network
// to certain private IP ranges. Each Route represents an IP range in their
// network, and says that eyeballs can reach that route using the corresponding
// tunnel.
type Route struct {
Network CIDR `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"`
// Optional field. When unset, it means the Route belongs to the default virtual network.
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
}
// CIDR is just a newtype wrapper around net.IPNet. It adds JSON unmarshalling.
type CIDR net.IPNet
func (c CIDR) String() string {
n := net.IPNet(c)
return n.String()
}
func (c CIDR) MarshalJSON() ([]byte, error) {
str := c.String()
json, err := json.Marshal(str)
if err != nil {
return nil, errors.Wrap(err, "error serializing CIDR into JSON")
}
return json, nil
}
// UnmarshalJSON parses a JSON string into net.IPNet
func (c *CIDR) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, "error parsing cidr string")
}
_, network, err := net.ParseCIDR(s)
if err != nil {
return errors.Wrap(err, "error parsing invalid network from backend")
}
if network == nil {
return fmt.Errorf("backend returned invalid network %s", s)
}
*c = CIDR(*network)
return nil
}
// NewRoute has all the parameters necessary to add a new route to the table.
type NewRoute struct {
Network net.IPNet
TunnelID uuid.UUID
Comment string
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
}
// MarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
func (r NewRoute) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
TunnelID uuid.UUID `json:"tunnel_id"`
Comment string `json:"comment"`
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
}{
TunnelID: r.TunnelID,
Comment: r.Comment,
VNetID: r.VNetID,
})
}
// DetailedRoute is just a Route with some extra fields, e.g. TunnelName.
type DetailedRoute struct {
Network CIDR `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"`
// Optional field. When unset, it means the DetailedRoute belongs to the default virtual network.
VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
TunnelName string `json:"tunnel_name"`
}
// IsZero checks if DetailedRoute is the zero value.
func (r *DetailedRoute) IsZero() bool {
return r.TunnelID == uuid.Nil
}
// TableString outputs a table row summarizing the route, to be used
// when showing the user their routing table.
func (r DetailedRoute) TableString() string {
deletedColumn := "-"
if !r.DeletedAt.IsZero() {
deletedColumn = r.DeletedAt.Format(time.RFC3339)
}
vnetColumn := "default"
if r.VNetID != nil {
vnetColumn = r.VNetID.String()
}
return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%s\t%s\t",
r.Network.String(),
vnetColumn,
r.Comment,
r.TunnelID,
r.TunnelName,
r.CreatedAt.Format(time.RFC3339),
deletedColumn,
)
}
type DeleteRouteParams struct {
Network net.IPNet
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
}
type GetRouteByIpParams struct {
Ip net.IP
// Optional field. If unset, backend will assume the default vnet for the account.
VNetID *uuid.UUID
}
// ListRoutes calls the Tunnelstore GET endpoint for all routes under an account.
func (r *RESTClient) ListRoutes(filter *IpRouteFilter) ([]*DetailedRoute, error) {
endpoint := r.baseEndpoints.accountRoutes
endpoint.RawQuery = filter.Encode()
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseListDetailedRoutes(resp.Body)
}
return nil, r.statusCodeToError("list routes", resp)
}
// AddRoute calls the Tunnelstore POST endpoint for a given route.
func (r *RESTClient) AddRoute(newRoute NewRoute) (Route, error) {
endpoint := r.baseEndpoints.accountRoutes
endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(newRoute.Network.String()))
resp, err := r.sendRequest("POST", endpoint, newRoute)
if err != nil {
return Route{}, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseRoute(resp.Body)
}
return Route{}, r.statusCodeToError("add route", resp)
}
// DeleteRoute calls the Tunnelstore DELETE endpoint for a given route.
func (r *RESTClient) DeleteRoute(params DeleteRouteParams) error {
endpoint := r.baseEndpoints.accountRoutes
endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(params.Network.String()))
setVnetParam(&endpoint, params.VNetID)
resp, err := r.sendRequest("DELETE", endpoint, nil)
if err != nil {
return errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
_, err := parseRoute(resp.Body)
return err
}
return r.statusCodeToError("delete route", resp)
}
// GetByIP checks which route will proxy a given IP.
func (r *RESTClient) GetByIP(params GetRouteByIpParams) (DetailedRoute, error) {
endpoint := r.baseEndpoints.accountRoutes
endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(params.Ip.String()))
setVnetParam(&endpoint, params.VNetID)
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return DetailedRoute{}, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseDetailedRoute(resp.Body)
}
return DetailedRoute{}, r.statusCodeToError("get route by IP", resp)
}
func parseListDetailedRoutes(body io.ReadCloser) ([]*DetailedRoute, error) {
var routes []*DetailedRoute
err := parseResponse(body, &routes)
return routes, err
}
func parseRoute(body io.ReadCloser) (Route, error) {
var route Route
err := parseResponse(body, &route)
return route, err
}
func parseDetailedRoute(body io.ReadCloser) (DetailedRoute, error) {
var route DetailedRoute
err := parseResponse(body, &route)
return route, err
}
// setVnetParam overwrites the URL's query parameters with a query param to scope the HostnameRoute action to a certain
// virtual network (if one is provided).
func setVnetParam(endpoint *url.URL, vnetID *uuid.UUID) {
queryParams := url.Values{}
if vnetID != nil {
queryParams.Set("virtual_network_id", vnetID.String())
}
endpoint.RawQuery = queryParams.Encode()
}
+165
View File
@@ -0,0 +1,165 @@
package cfapi
import (
"fmt"
"net"
"net/url"
"strconv"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
var (
filterIpRouteDeleted = cli.BoolFlag{
Name: "filter-is-deleted",
Usage: "If false (default), only show non-deleted routes. If true, only show deleted routes.",
}
filterIpRouteTunnelID = cli.StringFlag{
Name: "filter-tunnel-id",
Usage: "Show only routes with the given tunnel ID.",
}
filterSubsetIpRoute = cli.StringFlag{
Name: "filter-network-is-subset-of",
Aliases: []string{"nsub"},
Usage: "Show only routes whose network is a subset of the given network.",
}
filterSupersetIpRoute = cli.StringFlag{
Name: "filter-network-is-superset-of",
Aliases: []string{"nsup"},
Usage: "Show only routes whose network is a superset of the given network.",
}
filterIpRouteComment = cli.StringFlag{
Name: "filter-comment-is",
Usage: "Show only routes with this comment.",
}
filterIpRouteByVnet = cli.StringFlag{
Name: "filter-virtual-network-id",
Usage: "Show only routes that are attached to the given virtual network ID.",
}
// Flags contains all filter flags.
IpRouteFilterFlags = []cli.Flag{
&filterIpRouteDeleted,
&filterIpRouteTunnelID,
&filterSubsetIpRoute,
&filterSupersetIpRoute,
&filterIpRouteComment,
&filterIpRouteByVnet,
}
)
// IpRouteFilter which routes get queried.
type IpRouteFilter struct {
queryParams url.Values
}
// NewIpRouteFilterFromCLI parses CLI flags to discover which filters should get applied.
func NewIpRouteFilterFromCLI(c *cli.Context) (*IpRouteFilter, error) {
f := &IpRouteFilter{
queryParams: url.Values{},
}
// Set deletion filter
if flag := filterIpRouteDeleted.Name; c.IsSet(flag) && c.Bool(flag) {
f.deleted()
} else {
f.notDeleted()
}
if subset, err := cidrFromFlag(c, filterSubsetIpRoute); err != nil {
return nil, err
} else if subset != nil {
f.networkIsSupersetOf(*subset)
}
if superset, err := cidrFromFlag(c, filterSupersetIpRoute); err != nil {
return nil, err
} else if superset != nil {
f.networkIsSupersetOf(*superset)
}
if comment := c.String(filterIpRouteComment.Name); comment != "" {
f.commentIs(comment)
}
if tunnelID := c.String(filterIpRouteTunnelID.Name); tunnelID != "" {
u, err := uuid.Parse(tunnelID)
if err != nil {
return nil, errors.Wrapf(err, "Couldn't parse UUID from %s", filterIpRouteTunnelID.Name)
}
f.tunnelID(u)
}
if vnetId := c.String(filterIpRouteByVnet.Name); vnetId != "" {
u, err := uuid.Parse(vnetId)
if err != nil {
return nil, errors.Wrapf(err, "Couldn't parse UUID from %s", filterIpRouteByVnet.Name)
}
f.vnetID(u)
}
if maxFetch := c.Int("max-fetch-size"); maxFetch > 0 {
f.MaxFetchSize(uint(maxFetch))
}
return f, nil
}
// Parses a CIDR from the flag. If the flag was unset, returns (nil, nil).
func cidrFromFlag(c *cli.Context, flag cli.StringFlag) (*net.IPNet, error) {
if !c.IsSet(flag.Name) {
return nil, nil
}
_, subset, err := net.ParseCIDR(c.String(flag.Name))
if err != nil {
return nil, err
} else if subset == nil {
return nil, fmt.Errorf("Invalid CIDR supplied for %s", flag.Name)
}
return subset, nil
}
func (f *IpRouteFilter) commentIs(comment string) {
f.queryParams.Set("comment", comment)
}
func (f *IpRouteFilter) notDeleted() {
f.queryParams.Set("is_deleted", "false")
}
func (f *IpRouteFilter) deleted() {
f.queryParams.Set("is_deleted", "true")
}
func (f *IpRouteFilter) networkIsSubsetOf(superset net.IPNet) {
f.queryParams.Set("network_subset", superset.String())
}
func (f *IpRouteFilter) networkIsSupersetOf(subset net.IPNet) {
f.queryParams.Set("network_superset", subset.String())
}
func (f *IpRouteFilter) existedAt(existedAt time.Time) {
f.queryParams.Set("existed_at", existedAt.Format(time.RFC3339))
}
func (f *IpRouteFilter) tunnelID(id uuid.UUID) {
f.queryParams.Set("tunnel_id", id.String())
}
func (f *IpRouteFilter) vnetID(id uuid.UUID) {
f.queryParams.Set("virtual_network_id", id.String())
}
func (f *IpRouteFilter) MaxFetchSize(max uint) {
f.queryParams.Set("per_page", strconv.Itoa(int(max)))
}
func (f IpRouteFilter) Encode() string {
return f.queryParams.Encode()
}
+175
View File
@@ -0,0 +1,175 @@
package cfapi
import (
"encoding/json"
"fmt"
"net"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestUnmarshalRoute(t *testing.T) {
testCases := []struct {
Json string
HasVnet bool
}{
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":null
}`,
false,
},
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":null,
"virtual_network_id":"38c95083-8191-4110-8339-3f438d44fdb9"
}`,
true,
},
}
for _, testCase := range testCases {
data := testCase.Json
var r Route
err := json.Unmarshal([]byte(data), &r)
// Check everything worked
require.NoError(t, err)
require.Equal(t, uuid.MustParse("fba6ffea-807f-4e7a-a740-4184ee1b82c8"), r.TunnelID)
require.Equal(t, "test", r.Comment)
_, cidr, err := net.ParseCIDR("10.1.2.40/29")
require.NoError(t, err)
require.Equal(t, CIDR(*cidr), r.Network)
require.Equal(t, "test", r.Comment)
if testCase.HasVnet {
require.Equal(t, uuid.MustParse("38c95083-8191-4110-8339-3f438d44fdb9"), *r.VNetID)
} else {
require.Nil(t, r.VNetID)
}
}
}
func TestDetailedRouteJsonRoundtrip(t *testing.T) {
testCases := []struct {
Json string
HasVnet bool
}{
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":"2021-01-14T05:01:42.183002Z",
"tunnel_name":"Mr. Tun"
}`,
false,
},
{
`{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"virtual_network_id":"38c95083-8191-4110-8339-3f438d44fdb9",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":"2021-01-14T05:01:42.183002Z",
"tunnel_name":"Mr. Tun"
}`,
true,
},
}
for _, testCase := range testCases {
data := testCase.Json
var r DetailedRoute
err := json.Unmarshal([]byte(data), &r)
// Check everything worked
require.NoError(t, err)
require.Equal(t, uuid.MustParse("fba6ffea-807f-4e7a-a740-4184ee1b82c8"), r.TunnelID)
require.Equal(t, "test", r.Comment)
_, cidr, err := net.ParseCIDR("10.1.2.40/29")
require.NoError(t, err)
require.Equal(t, CIDR(*cidr), r.Network)
require.Equal(t, "test", r.Comment)
require.Equal(t, "Mr. Tun", r.TunnelName)
if testCase.HasVnet {
require.Equal(t, uuid.MustParse("38c95083-8191-4110-8339-3f438d44fdb9"), *r.VNetID)
} else {
require.Nil(t, r.VNetID)
}
bytes, err := json.Marshal(r)
require.NoError(t, err)
obtainedJson := string(bytes)
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\n", "", -1)
require.Equal(t, data, obtainedJson)
}
}
func TestMarshalNewRoute(t *testing.T) {
_, network, err := net.ParseCIDR("1.2.3.4/32")
require.NoError(t, err)
require.NotNil(t, network)
vnetId := uuid.New()
newRoutes := []NewRoute{
{
Network: *network,
TunnelID: uuid.New(),
Comment: "hi",
},
{
Network: *network,
TunnelID: uuid.New(),
Comment: "hi",
VNetID: &vnetId,
},
}
for _, newRoute := range newRoutes {
// Test where receiver is struct
serialized, err := json.Marshal(newRoute)
require.NoError(t, err)
require.True(t, strings.Contains(string(serialized), "tunnel_id"))
// Test where receiver is pointer to struct
serialized, err = json.Marshal(&newRoute)
require.NoError(t, err)
require.True(t, strings.Contains(string(serialized), "tunnel_id"))
if newRoute.VNetID == nil {
require.False(t, strings.Contains(string(serialized), "virtual_network_id"))
} else {
require.True(t, strings.Contains(string(serialized), "virtual_network_id"))
}
}
}
func TestRouteTableString(t *testing.T) {
_, network, err := net.ParseCIDR("1.2.3.4/32")
require.NoError(t, err)
require.NotNil(t, network)
r := DetailedRoute{
Network: CIDR(*network),
}
row := r.TableString()
fmt.Println(row)
require.True(t, strings.HasPrefix(row, "1.2.3.4/32"))
}
+183
View File
@@ -0,0 +1,183 @@
package cfapi
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
"path"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
var ErrTunnelNameConflict = errors.New("tunnel with name already exists")
type Tunnel struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
Connections []Connection `json:"connections"`
}
type Connection struct {
ColoName string `json:"colo_name"`
ID uuid.UUID `json:"id"`
IsPendingReconnect bool `json:"is_pending_reconnect"`
OriginIP net.IP `json:"origin_ip"`
OpenedAt time.Time `json:"opened_at"`
}
type ActiveClient struct {
ID uuid.UUID `json:"id"`
Features []string `json:"features"`
Version string `json:"version"`
Arch string `json:"arch"`
RunAt time.Time `json:"run_at"`
Connections []Connection `json:"conns"`
}
type newTunnel struct {
Name string `json:"name"`
TunnelSecret []byte `json:"tunnel_secret"`
}
type CleanupParams struct {
queryParams url.Values
}
func NewCleanupParams() *CleanupParams {
return &CleanupParams{
queryParams: url.Values{},
}
}
func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
cp.queryParams.Set("client_id", clientID.String())
}
func (cp CleanupParams) encode() string {
return cp.queryParams.Encode()
}
func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error) {
if name == "" {
return nil, errors.New("tunnel name required")
}
if _, err := uuid.Parse(name); err == nil {
return nil, errors.New("you cannot use UUIDs as tunnel names")
}
body := &newTunnel{
Name: name,
TunnelSecret: tunnelSecret,
}
resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return unmarshalTunnel(resp.Body)
case http.StatusConflict:
return nil, ErrTunnelNameConflict
}
return nil, r.statusCodeToError("create tunnel", resp)
}
func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
endpoint := r.baseEndpoints.accountLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return unmarshalTunnel(resp.Body)
}
return nil, r.statusCodeToError("get tunnel", resp)
}
func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
endpoint := r.baseEndpoints.accountLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
resp, err := r.sendRequest("DELETE", endpoint, nil)
if err != nil {
return errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
return r.statusCodeToError("delete tunnel", resp)
}
func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
endpoint := r.baseEndpoints.accountLevel
endpoint.RawQuery = filter.encode()
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseListTunnels(resp.Body)
}
return nil, r.statusCodeToError("list tunnels", resp)
}
func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
var tunnels []*Tunnel
err := parseResponse(body, &tunnels)
return tunnels, err
}
func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
endpoint := r.baseEndpoints.accountLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseConnectionsDetails(resp.Body)
}
return nil, r.statusCodeToError("list connection details", resp)
}
func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
var clients []*ActiveClient
err := parseResponse(reader, &clients)
return clients, err
}
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
endpoint := r.baseEndpoints.accountLevel
endpoint.RawQuery = params.encode()
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
resp, err := r.sendRequest("DELETE", endpoint, nil)
if err != nil {
return errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
return r.statusCodeToError("cleanup connections", resp)
}
func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
var tunnel Tunnel
err := parseResponse(reader, &tunnel)
return &tunnel, err
}
+55
View File
@@ -0,0 +1,55 @@
package cfapi
import (
"net/url"
"strconv"
"time"
"github.com/google/uuid"
)
const (
TimeLayout = time.RFC3339
)
type TunnelFilter struct {
queryParams url.Values
}
func NewTunnelFilter() *TunnelFilter {
return &TunnelFilter{
queryParams: url.Values{},
}
}
func (f *TunnelFilter) ByName(name string) {
f.queryParams.Set("name", name)
}
func (f *TunnelFilter) ByNamePrefix(namePrefix string) {
f.queryParams.Set("name_prefix", namePrefix)
}
func (f *TunnelFilter) ExcludeNameWithPrefix(excludePrefix string) {
f.queryParams.Set("exclude_prefix", excludePrefix)
}
func (f *TunnelFilter) NoDeleted() {
f.queryParams.Set("is_deleted", "false")
}
func (f *TunnelFilter) ByExistedAt(existedAt time.Time) {
f.queryParams.Set("existed_at", existedAt.Format(TimeLayout))
}
func (f *TunnelFilter) ByTunnelID(tunnelID uuid.UUID) {
f.queryParams.Set("uuid", tunnelID.String())
}
func (f *TunnelFilter) MaxFetchSize(max uint) {
f.queryParams.Set("per_page", strconv.Itoa(int(max)))
}
func (f TunnelFilter) encode() string {
return f.queryParams.Encode()
}
+149
View File
@@ -0,0 +1,149 @@
package cfapi
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"reflect"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
var loc, _ = time.LoadLocation("UTC")
func Test_parseListTunnels(t *testing.T) {
type args struct {
body string
}
tests := []struct {
name string
args args
want []*Tunnel
wantErr bool
}{
{
name: "empty list",
args: args{body: `{"success": true, "result": []}`},
want: []*Tunnel{},
},
{
name: "success is false",
args: args{body: `{"success": false, "result": []}`},
wantErr: true,
},
{
name: "errors are present",
args: args{body: `{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}], "result": []}`},
wantErr: true,
},
{
name: "invalid response",
args: args{body: `abc`},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
body := ioutil.NopCloser(bytes.NewReader([]byte(tt.args.body)))
got, err := parseListTunnels(body)
if (err != nil) != tt.wantErr {
t.Errorf("parseListTunnels() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseListTunnels() = %v, want %v", got, tt.want)
}
})
}
}
func Test_unmarshalTunnel(t *testing.T) {
type args struct {
body string
}
tests := []struct {
name string
args args
want *Tunnel
wantErr bool
}{
{
name: "empty list",
args: args{body: `{"success": true, "result": {"id":"b34cc7ce-925b-46ee-bc23-4cb5c18d8292","created_at":"2021-07-29T13:46:14.090955Z","deleted_at":"2021-07-29T14:07:27.559047Z","name":"qt-bIWWN7D662ogh61pCPfu5s2XgqFY1OyV","account_id":6946212,"account_tag":"5ab4e9dfbd435d24068829fda0077963","conns_active_at":null,"conns_inactive_at":"2021-07-29T13:47:22.548482Z","tun_type":"cfd_tunnel","metadata":{"qtid":"a6fJROgkXutNruBGaJjD"}}}`},
want: &Tunnel{
ID: uuid.MustParse("b34cc7ce-925b-46ee-bc23-4cb5c18d8292"),
Name: "qt-bIWWN7D662ogh61pCPfu5s2XgqFY1OyV",
CreatedAt: time.Date(2021, 07, 29, 13, 46, 14, 90955000, loc),
DeletedAt: time.Date(2021, 07, 29, 14, 7, 27, 559047000, loc),
Connections: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := unmarshalTunnel(strings.NewReader(tt.args.body))
if (err != nil) != tt.wantErr {
t.Errorf("unmarshalTunnel() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unmarshalTunnel() = %v, want %v", got, tt.want)
}
})
}
}
func TestUnmarshalTunnelOk(t *testing.T) {
jsonBody := `{"success": true, "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}`
expected := Tunnel{
ID: uuid.Nil,
Name: "test",
CreatedAt: time.Time{},
Connections: []Connection{},
}
actual, err := unmarshalTunnel(bytes.NewReader([]byte(jsonBody)))
assert.NoError(t, err)
assert.Equal(t, &expected, actual)
}
func TestUnmarshalTunnelErr(t *testing.T) {
tests := []string{
`abc`,
`{"success": true, "result": abc}`,
`{"success": false, "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}}`,
`{"errors": [{"code": 1003, "message":"An A, AAAA or CNAME record already exists with that host"}], "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}}`,
}
for i, test := range tests {
_, err := unmarshalTunnel(bytes.NewReader([]byte(test)))
assert.Error(t, err, fmt.Sprintf("Test #%v failed", i))
}
}
func TestUnmarshalConnections(t *testing.T) {
jsonBody := `{"success":true,"messages":[],"errors":[],"result":[{"id":"d4041254-91e3-4deb-bd94-b46e11680b1e","features":["ha-origin"],"version":"2021.2.5","arch":"darwin_amd64","conns":[{"colo_name":"LIS","id":"ac2286e5-c708-4588-a6a0-ba6b51940019","is_pending_reconnect":false,"origin_ip":"148.38.28.2","opened_at":"0001-01-01T00:00:00Z"}],"run_at":"0001-01-01T00:00:00Z"}]}`
expected := ActiveClient{
ID: uuid.MustParse("d4041254-91e3-4deb-bd94-b46e11680b1e"),
Features: []string{"ha-origin"},
Version: "2021.2.5",
Arch: "darwin_amd64",
RunAt: time.Time{},
Connections: []Connection{{
ID: uuid.MustParse("ac2286e5-c708-4588-a6a0-ba6b51940019"),
ColoName: "LIS",
IsPendingReconnect: false,
OriginIP: net.ParseIP("148.38.28.2"),
OpenedAt: time.Time{},
}},
}
actual, err := parseConnectionsDetails(bytes.NewReader([]byte(jsonBody)))
assert.NoError(t, err)
assert.Equal(t, []*ActiveClient{&expected}, actual)
}
+127
View File
@@ -0,0 +1,127 @@
package cfapi
import (
"fmt"
"io"
"net/http"
"net/url"
"path"
"strconv"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
type NewVirtualNetwork struct {
Name string `json:"name"`
Comment string `json:"comment"`
IsDefault bool `json:"is_default"`
}
type VirtualNetwork struct {
ID uuid.UUID `json:"id"`
Comment string `json:"comment"`
Name string `json:"name"`
IsDefault bool `json:"is_default_network"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
}
type UpdateVirtualNetwork struct {
Name *string `json:"name,omitempty"`
Comment *string `json:"comment,omitempty"`
IsDefault *bool `json:"is_default_network,omitempty"`
}
func (virtualNetwork VirtualNetwork) TableString() string {
deletedColumn := "-"
if !virtualNetwork.DeletedAt.IsZero() {
deletedColumn = virtualNetwork.DeletedAt.Format(time.RFC3339)
}
return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%s\t",
virtualNetwork.ID,
virtualNetwork.Name,
strconv.FormatBool(virtualNetwork.IsDefault),
virtualNetwork.Comment,
virtualNetwork.CreatedAt.Format(time.RFC3339),
deletedColumn,
)
}
func (r *RESTClient) CreateVirtualNetwork(newVnet NewVirtualNetwork) (VirtualNetwork, error) {
resp, err := r.sendRequest("POST", r.baseEndpoints.accountVnets, newVnet)
if err != nil {
return VirtualNetwork{}, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseVnet(resp.Body)
}
return VirtualNetwork{}, r.statusCodeToError("add virtual network", resp)
}
func (r *RESTClient) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, error) {
endpoint := r.baseEndpoints.accountVnets
endpoint.RawQuery = filter.Encode()
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return nil, errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return parseListVnets(resp.Body)
}
return nil, r.statusCodeToError("list virtual networks", resp)
}
func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID) error {
endpoint := r.baseEndpoints.accountVnets
endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String()))
resp, err := r.sendRequest("DELETE", endpoint, nil)
if err != nil {
return errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
_, err := parseVnet(resp.Body)
return err
}
return r.statusCodeToError("delete virtual network", resp)
}
func (r *RESTClient) UpdateVirtualNetwork(id uuid.UUID, updates UpdateVirtualNetwork) error {
endpoint := r.baseEndpoints.accountVnets
endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String()))
resp, err := r.sendRequest("PATCH", endpoint, updates)
if err != nil {
return errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
_, err := parseVnet(resp.Body)
return err
}
return r.statusCodeToError("update virtual network", resp)
}
func parseListVnets(body io.ReadCloser) ([]*VirtualNetwork, error) {
var vnets []*VirtualNetwork
err := parseResponse(body, &vnets)
return vnets, err
}
func parseVnet(body io.ReadCloser) (VirtualNetwork, error) {
var vnet VirtualNetwork
err := parseResponse(body, &vnet)
return vnet, err
}
+99
View File
@@ -0,0 +1,99 @@
package cfapi
import (
"net/url"
"strconv"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
var (
filterVnetId = cli.StringFlag{
Name: "id",
Usage: "List virtual networks with the given `ID`",
}
filterVnetByName = cli.StringFlag{
Name: "name",
Usage: "List virtual networks with the given `NAME`",
}
filterDefaultVnet = cli.BoolFlag{
Name: "is-default",
Usage: "If true, lists the virtual network that is the default one. If false, lists all non-default virtual networks for the account. If absent, all are included in the results regardless of their default status.",
}
filterDeletedVnet = cli.BoolFlag{
Name: "show-deleted",
Usage: "If false (default), only show non-deleted virtual networks. If true, only show deleted virtual networks.",
}
VnetFilterFlags = []cli.Flag{
&filterVnetId,
&filterVnetByName,
&filterDefaultVnet,
&filterDeletedVnet,
}
)
// VnetFilter which virtual networks get queried.
type VnetFilter struct {
queryParams url.Values
}
func NewVnetFilter() *VnetFilter {
return &VnetFilter{
queryParams: url.Values{},
}
}
func (f *VnetFilter) ById(vnetId uuid.UUID) {
f.queryParams.Set("id", vnetId.String())
}
func (f *VnetFilter) ByName(name string) {
f.queryParams.Set("name", name)
}
func (f *VnetFilter) ByDefaultStatus(isDefault bool) {
f.queryParams.Set("is_default", strconv.FormatBool(isDefault))
}
func (f *VnetFilter) WithDeleted(isDeleted bool) {
f.queryParams.Set("is_deleted", strconv.FormatBool(isDeleted))
}
func (f *VnetFilter) MaxFetchSize(max uint) {
f.queryParams.Set("per_page", strconv.Itoa(int(max)))
}
func (f VnetFilter) Encode() string {
return f.queryParams.Encode()
}
// NewFromCLI parses CLI flags to discover which filters should get applied to list virtual networks.
func NewFromCLI(c *cli.Context) (*VnetFilter, error) {
f := NewVnetFilter()
if id := c.String("id"); id != "" {
vnetId, err := uuid.Parse(id)
if err != nil {
return nil, errors.Wrapf(err, "%s is not a valid virtual network ID", id)
}
f.ById(vnetId)
}
if name := c.String("name"); name != "" {
f.ByName(name)
}
if c.IsSet("is-default") {
f.ByDefaultStatus(c.Bool("is-default"))
}
f.WithDeleted(c.Bool("show-deleted"))
if maxFetch := c.Int("max-fetch-size"); maxFetch > 0 {
f.MaxFetchSize(uint(maxFetch))
}
return f, nil
}
+79
View File
@@ -0,0 +1,79 @@
package cfapi
import (
"encoding/json"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestVirtualNetworkJsonRoundtrip(t *testing.T) {
data := `{
"id":"74fce949-351b-4752-b261-81a56cfd3130",
"comment":"New York DC1",
"name":"us-east-1",
"is_default_network":true,
"created_at":"2021-11-26T14:40:02.600673Z",
"deleted_at":"2021-12-01T10:23:13.102645Z"
}`
var v VirtualNetwork
err := json.Unmarshal([]byte(data), &v)
require.NoError(t, err)
require.Equal(t, uuid.MustParse("74fce949-351b-4752-b261-81a56cfd3130"), v.ID)
require.Equal(t, "us-east-1", v.Name)
require.Equal(t, "New York DC1", v.Comment)
require.Equal(t, true, v.IsDefault)
bytes, err := json.Marshal(v)
require.NoError(t, err)
obtainedJson := string(bytes)
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\n", "", -1)
require.Equal(t, data, obtainedJson)
}
func TestMarshalNewVnet(t *testing.T) {
newVnet := NewVirtualNetwork{
Name: "eu-west-1",
Comment: "London office",
IsDefault: true,
}
serialized, err := json.Marshal(newVnet)
require.NoError(t, err)
require.True(t, strings.Contains(string(serialized), newVnet.Name))
}
func TestMarshalUpdateVnet(t *testing.T) {
newName := "bulgaria-1"
updates := UpdateVirtualNetwork{
Name: &newName,
}
// Test where receiver is struct
serialized, err := json.Marshal(updates)
require.NoError(t, err)
require.True(t, strings.Contains(string(serialized), newName))
}
func TestVnetTableString(t *testing.T) {
virtualNet := VirtualNetwork{
ID: uuid.New(),
Name: "us-east-1",
Comment: "New York DC1",
IsDefault: true,
CreatedAt: time.Now(),
DeletedAt: time.Time{},
}
row := virtualNet.TableString()
require.True(t, strings.HasPrefix(row, virtualNet.ID.String()))
require.True(t, strings.Contains(row, virtualNet.Name))
require.True(t, strings.Contains(row, virtualNet.Comment))
require.True(t, strings.Contains(row, "true"))
require.True(t, strings.HasSuffix(row, "-\t"))
}
+228 -51
View File
@@ -1,89 +1,266 @@
pinned_go: &pinned_go go=1.9.3-1
build_dir: &build_dir /cfsetup_build/src/github.com/cloudflare/cloudflared/
stretch:
pinned_go: &pinned_go go=1.17-1
pinned_go_fips: &pinned_go_fips go-boring=1.16.6-7
build_dir: &build_dir /cfsetup_build
default-flavor: buster
stretch: &stretch
build:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=amd64
- make cloudflared
build-deb:
build-fips:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- make cloudflared
# except FIPS (handled in github-fips-release-pkgs) and macos (handled in github-release-macos-amd64)
github-release-pkgs:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
- rpm
- wget
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
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 pygithub
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
# handle FIPS separately so that we built with gofips compiler
github-fips-release-pkgs:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
- fakeroot
- rubygem-fpm
- rpm
- wget
# libmsi and libgcab are libraries the wixl binary depends on.
- libmsi-dev
- libgcab-dev
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache: *github_release_pkgs_pre_cache
post-cache:
# same logic as above, but for FIPS packages only
- ./build-packages-fips.sh
- make github-release-built-pkgs
build-deb:
build_dir: *build_dir
builddeps: &build_deb_deps
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=amd64
- make cloudflared-deb
release-linux-amd64:
build-fips-internal-deb:
build_dir: *build_dir
builddeps: &build_fips_deb_deps
- *pinned_go_fips
- build-essential
- fakeroot
- rubygem-fpm
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- export ORIGINAL_NAME=true
- make cloudflared-deb
build-fips-internal-deb-nightly:
build_dir: *build_dir
builddeps: *build_fips_deb_deps
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export NIGHTLY=true
- export FIPS=true
- export ORIGINAL_NAME=true
- make cloudflared-deb
build-deb-arm64:
build_dir: *build_dir
builddeps: *build_deb_deps
post-cache:
- export GOOS=linux
- export GOARCH=arm64
- make cloudflared-deb
publish-deb:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
- openssh-client
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=amd64
- make release
release-linux-armv6:
- make publish-deb
github-release-macos-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- crossbuild-essential-armhf
- gcc-arm-linux-gnueabihf
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache: &install_pygithub
- pip3 install pygithub
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=arm
- export CC=arm-linux-gnueabihf-gcc
- make release
release-linux-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-multilib
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=386
- make release
release-windows-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=windows
- export GOARCH=amd64
- export CC=x86_64-w64-mingw32-gcc
- make release
release-windows-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=windows
- export GOARCH=386
- export CC=i686-w64-mingw32-gcc-win32
- make release
- make github-mac-upload
test:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- gotest-to-teamcity
pre-cache: &test_pre_cache
- go get golang.org/x/tools/cmd/goimports
- go get github.com/sudarshan-reddy/go-sumtype@v0.0.0-20210827105221-82eca7e5abb1
post-cache:
- export GOPATH=/cfsetup_build/
- export GOOS=linux
- export GOARCH=amd64
- make test
- export PATH="$HOME/go/bin:$PATH"
- ./fmt-check.sh
- make test | gotest-to-teamcity
test-fips:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- build-essential
- gotest-to-teamcity
pre-cache: *test_pre_cache
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- export FIPS=true
- export PATH="$HOME/go/bin:$PATH"
- ./fmt-check.sh
- make test | gotest-to-teamcity
component-test:
build_dir: *build_dir
builddeps:
- *pinned_go_fips
- python3.7
- python3-pip
- python3-setuptools
# procps installs the ps command which is needed in test_sysv_service because the init script
# uses ps pid to determine if the agent is running
- procps
pre-cache-copy-paths:
- component-tests/requirements.txt
pre-cache:
- sudo pip3 install --upgrade -r component-tests/requirements.txt
post-cache:
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
- python3 component-tests/setup.py --type create
- pytest component-tests -o log_cli=true --log-cli-level=INFO
# The Named Tunnel is deleted and its route unprovisioned here.
- python3 component-tests/setup.py --type cleanup
update-homebrew:
builddeps:
- openssh-client
- s3cmd
post-cache:
- .teamcity/update-homebrew.sh
github-message-release:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-dev
- libffi-dev
- python3-setuptools
- python3-pip
pre-cache: *install_pygithub
post-cache:
- make github-message
build-junos:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3
- genisoimage
- jetez
pre-cache:
- ln -s /usr/bin/genisoimage /usr/bin/mkisofs
post-cache:
- export GOOS=freebsd
- export GOARCH=amd64
- make cloudflared-junos
publish-junos:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3
- genisoimage
- jetez
- s4cmd
pre-cache:
- ln -s /usr/bin/genisoimage /usr/bin/mkisofs
post-cache:
- export GOOS=freebsd
- export GOARCH=amd64
- make publish-cloudflared-junos
buster: *stretch
bullseye: *stretch
centos-7:
publish-rpm:
build_dir: *build_dir
builddeps: &el7_builddeps
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://golang.org/dl/go1.16.3.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.16.3.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make publish-rpm
build-rpm:
build_dir: *build_dir
builddeps: *el7_builddeps
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://golang.org/dl/go1.16.3.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.16.3.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make cloudflared-rpm
Executable
+15
View File
@@ -0,0 +1,15 @@
# Pass the path to the executable to check for FIPS compliance
exe=$1
if [ "$(go tool nm "${exe}" | grep -c '_Cfunc__goboringcrypto_')" -eq 0 ]; then
# Asserts that executable is using FIPS-compliant boringcrypto
echo "${exe}: missing goboring symbols" >&2
exit 1
fi
if [ "$(go tool nm "${exe}" | grep -c 'crypto/internal/boring/sig.FIPSOnly')" -eq 0 ]; then
# Asserts that executable is using FIPS-only schemes
echo "${exe}: missing fipsonly symbols" >&2
exit 1
fi
echo "${exe} is FIPS-compliant"
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<?if $(var.Platform)="x86"?>
<?define Program_Files="ProgramFilesFolder"?>
<?else?>
<?define Program_Files="ProgramFiles64Folder"?>
<?endif?>
<?ifndef var.Version?>
<?error Undefined Version variable?>
<?endif?>
<?ifndef var.Path?>
<?error Undefined Path variable?>
<?endif?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="35e5e858-9372-4449-bf73-1cd6f7267128"
UpgradeCode="23f90fdd-9328-47ea-ab52-5380855a4b12"
Name="cloudflared"
Version="$(var.Version)"
Manufacturer="cloudflare"
Language="1033">
<Package InstallerVersion="200" Compressed="yes" Comments="Windows Installer Package" InstallScope="perMachine"/>
<Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
<Upgrade Id="23f90fdd-9328-47ea-ab52-5380855a4b12">
<UpgradeVersion Minimum="$(var.Version)" OnlyDetect="yes" Property="NEWERVERSIONDETECTED"/>
<UpgradeVersion Minimum="2020.8.0" Maximum="$(var.Version)" IncludeMinimum="yes" IncludeMaximum="no"
Property="OLDERVERSIONBEINGUPGRADED"/>
</Upgrade>
<Condition Message="A newer version of this software is already installed.">NOT NEWERVERSIONDETECTED</Condition>
<Directory Id="TARGETDIR" Name="SourceDir">
<!--This specifies where the cloudflared.exe is moved to in the windows Operation System-->
<Directory Id="$(var.Program_Files)">
<Directory Id="INSTALLDIR" Name="cloudflared">
<Component Id="ApplicationFiles" Guid="35e5e858-9372-4449-bf73-1cd6f7267128">
<File Id="ApplicationFile0" Source="$(var.Path)"/>
</Component>
</Directory>
</Directory>
<Component Id="ENVS" Guid="6bb74449-d10d-4f4a-933e-6fc9fa006eae">
<!--Set the cloudflared bin location to the Path Environment Variable-->
<Environment Id="ENV0"
Name="PATH"
Value="[INSTALLDIR]."
Permanent="no"
Part="last"
Action="create"
System="yes" />
</Component>
</Directory>
<Feature Id='Complete' Level='1'>
<ComponentRef Id="ENVS"/>
<ComponentRef Id='ApplicationFiles' />
</Feature>
</Product>
</Wix>
+6
View File
@@ -0,0 +1,6 @@
.\" Manpage for cloudflared.
.TH man 1 ${DATE} "${VERSION}" "cloudflared man page"
.SH NAME
cloudflared \- creates a connection to the cloudflare edge network
.SH DESCRIPTION
cloudflared creates a persistent connection between a local service and the Cloudflare network. Once the daemon is running and the Tunnel has been configured, the local service can be locked down to only allow connections from Cloudflare.
+136
View File
@@ -0,0 +1,136 @@
package access
import (
"crypto/tls"
"fmt"
"net/http"
"strings"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/validation"
)
const (
LogFieldHost = "host"
cfAccessClientIDHeader = "Cf-Access-Client-Id"
cfAccessClientSecretHeader = "Cf-Access-Client-Secret"
)
// StartForwarder starts a client side websocket forward
func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *zerolog.Logger) error {
validURL, err := validation.ValidateUrl(forwarder.Listener)
if err != nil {
return errors.Wrap(err, "error validating origin URL")
}
// get the headers from the config file and add to the request
headers := make(http.Header)
if forwarder.TokenClientID != "" {
headers.Set(cfAccessClientIDHeader, forwarder.TokenClientID)
}
if forwarder.TokenSecret != "" {
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
}
carrier.SetBastionDest(headers, forwarder.Destination)
options := &carrier.StartOptions{
OriginURL: forwarder.URL,
Headers: headers, //TODO: TUN-2688 support custom headers from config file
}
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
wsConn := carrier.NewWSConnection(log)
log.Info().Str(LogFieldHost, validURL.Host).Msg("Start Websocket listener")
return carrier.StartForwarder(wsConn, validURL.Host, shutdown, options)
}
// ssh will start a WS proxy server for server mode
// or copy from stdin/stdout for client mode
// useful for proxying other protocols (like ssh) over websockets
// (which you can put Access in front of)
func ssh(c *cli.Context) error {
log := logger.CreateSSHLoggerFromContext(c, logger.EnableTerminalLog)
// get the hostname from the cmdline and error out if its not provided
rawHostName := c.String(sshHostnameFlag)
hostname, err := validation.ValidateHostname(rawHostName)
if err != nil || rawHostName == "" {
return cli.ShowCommandHelp(c, "ssh")
}
originURL := ensureURLScheme(hostname)
// get the headers from the cmdline and add them
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Set(cfAccessClientIDHeader, c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
options := &carrier.StartOptions{
OriginURL: originURL,
Headers: headers,
Host: hostname,
}
if connectTo := c.String(sshConnectTo); connectTo != "" {
parts := strings.Split(connectTo, ":")
switch len(parts) {
case 1:
options.OriginURL = fmt.Sprintf("https://%s", parts[0])
case 2:
options.OriginURL = fmt.Sprintf("https://%s:%s", parts[0], parts[1])
case 3:
options.OriginURL = fmt.Sprintf("https://%s:%s", parts[2], parts[1])
options.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
ServerName: parts[0],
}
log.Warn().Msgf("Using insecure SSL connection because SNI overridden to %s", parts[0])
default:
return fmt.Errorf("invalid connection override: %s", connectTo)
}
}
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
wsConn := carrier.NewWSConnection(log)
if c.NArg() > 0 || c.IsSet(sshURLFlag) {
forwarder, err := config.ValidateUrl(c, true)
if err != nil {
log.Err(err).Msg("Error validating origin URL")
return errors.Wrap(err, "error validating origin URL")
}
log.Info().Str(LogFieldHost, forwarder.Host).Msg("Start Websocket listener")
err = carrier.StartForwarder(wsConn, forwarder.Host, shutdownC, options)
if err != nil {
log.Err(err).Msg("Error on Websocket listener")
}
return err
}
return carrier.StartClient(wsConn, &carrier.StdinoutStream{}, options)
}
func buildRequestHeaders(values []string) http.Header {
headers := make(http.Header)
for _, valuePair := range values {
split := strings.Split(valuePair, ":")
if len(split) > 1 {
headers.Add(strings.TrimSpace(split[0]), strings.TrimSpace(split[1]))
}
}
return headers
}
+18
View File
@@ -0,0 +1,18 @@
package access
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBuildRequestHeaders(t *testing.T) {
headers := make(http.Header)
headers.Add("client", "value")
headers.Add("secret", "safe-value")
values := buildRequestHeaders([]string{"client: value", "secret: safe-value", "trash"})
assert.Equal(t, headers.Get("client"), values.Get("client"))
assert.Equal(t, headers.Get("secret"), values.Get("secret"))
}
+411 -86
View File
@@ -1,21 +1,68 @@
package access
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"text/template"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
"github.com/getsentry/raven-go"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"golang.org/x/net/idna"
"github.com/cloudflare/cloudflared/log"
raven "github.com/getsentry/raven-go"
cli "gopkg.in/urfave/cli.v2"
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/sshgen"
"github.com/cloudflare/cloudflared/token"
"github.com/cloudflare/cloudflared/validation"
)
const (
sshHostnameFlag = "hostname"
sshDestinationFlag = "destination"
sshURLFlag = "url"
sshHeaderFlag = "header"
sshTokenIDFlag = "service-token-id"
sshTokenSecretFlag = "service-token-secret"
sshGenCertFlag = "short-lived-cert"
sshConnectTo = "connect-to"
sshConfigTemplate = `
Add to your {{.Home}}/.ssh/config:
Host {{.Hostname}}
{{- if .ShortLivedCerts}}
ProxyCommand bash -c '{{.Cloudflared}} access ssh-gen --hostname %h; ssh -tt %r@cfpipe-{{.Hostname}} >&2 <&1'
Host cfpipe-{{.Hostname}}
HostName {{.Hostname}}
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
IdentityFile ~/.cloudflared/{{.Hostname}}-cf_key
CertificateFile ~/.cloudflared/{{.Hostname}}-cf_key-cert.pub
{{- else}}
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
{{end}}
`
)
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
var (
shutdownC chan struct{}
)
// Init will initialize and store vars from the main program
func Init(shutdown chan struct{}) {
shutdownC = shutdown
}
// Flags return the global flags for Access related commands (hopefully none)
func Flags() []cli.Flag {
return []cli.Flag{} // no flags yet.
@@ -26,34 +73,28 @@ func Commands() []*cli.Command {
return []*cli.Command{
{
Name: "access",
Category: "Access (BETA)",
Aliases: []string{"forward"},
Category: "Access",
Usage: "access <subcommand>",
Description: `(BETA) Cloudflare Access protects internal resources by securing, authenticating and monitoring access
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
applications from the command line. This feature is considered beta. Your feedback is greatly appreciated!
https://cfl.re/CLIAuthBeta`,
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
applications from the command line.`,
Subcommands: []*cli.Command{
{
Name: "login",
Action: login,
Action: cliutil.Action(login),
Usage: "login <url of access application>",
Description: `The login subcommand initiates an authentication flow with your identity provider.
The subcommand will launch a browser. For headless systems, a url is provided.
Once authenticated with your identity provider, the login command will generate a JSON Web Token (JWT)
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
administrator. cloudflared stores the token in local storage.`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Hidden: true,
},
},
},
{
Name: "curl",
Action: curl,
Usage: "curl <args>",
Action: cliutil.Action(curl),
Usage: "curl [--allow-request, -ar] <url> [<curl args>...]",
Description: `The curl subcommand wraps curl and automatically injects the JWT into a cf-access-token
header when using curl to reach an application behind Access.`,
ArgsUsage: "allow-request will allow the curl request to continue even if the jwt is not present.",
@@ -61,7 +102,7 @@ func Commands() []*cli.Command {
},
{
Name: "token",
Action: token,
Action: cliutil.Action(generateToken),
Usage: "token -app=<url of access application>",
ArgsUsage: "url of Access application",
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
@@ -71,6 +112,90 @@ func Commands() []*cli.Command {
},
},
},
{
Name: "tcp",
Action: cliutil.Action(ssh),
Aliases: []string{"rdp", "ssh", "smb"},
Usage: "",
ArgsUsage: "",
Description: `The tcp subcommand sends data over a proxy to the Cloudflare edge.`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: sshHostnameFlag,
Aliases: []string{"tunnel-host", "T"},
Usage: "specify the hostname of your application.",
},
&cli.StringFlag{
Name: sshDestinationFlag,
Usage: "specify the destination address of your SSH server.",
},
&cli.StringFlag{
Name: sshURLFlag,
Aliases: []string{"listener", "L"},
Usage: "specify the host:port to forward data to Cloudflare edge.",
},
&cli.StringSliceFlag{
Name: sshHeaderFlag,
Aliases: []string{"H"},
Usage: "specify additional headers you wish to send.",
},
&cli.StringFlag{
Name: sshTokenIDFlag,
Aliases: []string{"id"},
Usage: "specify an Access service token ID you wish to use.",
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_ID"},
},
&cli.StringFlag{
Name: sshTokenSecretFlag,
Aliases: []string{"secret"},
Usage: "specify an Access service token secret you wish to use.",
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_SECRET"},
},
&cli.StringFlag{
Name: logger.LogSSHDirectoryFlag,
Aliases: []string{"logfile"}, //added to match the tunnel side
Usage: "Save application log to this directory for reporting issues.",
},
&cli.StringFlag{
Name: logger.LogSSHLevelFlag,
Aliases: []string{"loglevel"}, //added to match the tunnel side
Usage: "Application logging level {debug, info, warn, error, fatal}. ",
},
&cli.StringFlag{
Name: sshConnectTo,
Hidden: true,
Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host",
},
},
},
{
Name: "ssh-config",
Action: cliutil.Action(sshConfig),
Usage: "",
Description: `Prints an example configuration ~/.ssh/config`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: sshHostnameFlag,
Usage: "specify the hostname of your application.",
},
&cli.BoolFlag{
Name: sshGenCertFlag,
Usage: "specify if you wish to generate short lived certs.",
},
},
},
{
Name: "ssh-gen",
Action: cliutil.Action(sshGen),
Usage: "",
Description: `Generates a short lived certificate for given hostname`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: sshHostnameFlag,
Usage: "specify the hostname of your application.",
},
},
},
},
},
}
@@ -78,82 +203,237 @@ func Commands() []*cli.Command {
// login pops up the browser window to do the actual login and JWT generation
func login(c *cli.Context) error {
raven.SetDSN(sentryDSN)
logger := log.CreateLogger()
if err := raven.SetDSN(sentryDSN); err != nil {
return err
}
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
args := c.Args()
appURL, err := url.Parse(args.First())
rawURL := ensureURLScheme(args.First())
appURL, err := url.Parse(rawURL)
if args.Len() < 1 || err != nil {
logger.Errorf("Please provide the url of the Access application\n")
return err
}
if _, err := fetchToken(c, appURL); err != nil {
logger.Errorf("Failed to fetch token: %s\n", err)
log.Error().Msg("Please provide the url of the Access application")
return err
}
return nil
}
// curl provides a wrapper around curl, passing Access JWT along in request
func curl(c *cli.Context) error {
raven.SetDSN(sentryDSN)
logger := log.CreateLogger()
args := c.Args()
if args.Len() < 1 {
logger.Error("Please provide the access app and command you wish to run.")
return errors.New("incorrect args")
}
cmdArgs, appURL, allowRequest, err := buildCurlCmdArgs(args.Slice())
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
token, err := getTokenIfExists(appURL)
if err != nil || token == "" {
if err := verifyTokenAtEdge(appURL, appInfo, c, log); err != nil {
log.Err(err).Msg("Could not verify token")
return err
}
cfdToken, err := token.GetAppTokenIfExists(appInfo)
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application.")
return err
} else if cfdToken == "" {
fmt.Fprintln(os.Stderr, "token for provided application was empty.")
return errors.New("empty application token")
}
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", cfdToken)
return nil
}
// ensureURLScheme prepends a URL with https:// if it doesn't have a scheme. http:// URLs will not be converted.
func ensureURLScheme(url string) string {
url = strings.Replace(strings.ToLower(url), "http://", "https://", 1)
if !strings.HasPrefix(url, "https://") {
url = fmt.Sprintf("https://%s", url)
}
return url
}
// curl provides a wrapper around curl, passing Access JWT along in request
func curl(c *cli.Context) error {
if err := raven.SetDSN(sentryDSN); err != nil {
return err
}
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
args := c.Args()
if args.Len() < 1 {
log.Error().Msg("Please provide the access app and command you wish to run.")
return errors.New("incorrect args")
}
cmdArgs, allowRequest := parseAllowRequest(args.Slice())
appURL, err := getAppURL(cmdArgs, log)
if err != nil {
return err
}
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
if err != nil || tok == "" {
if allowRequest {
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
return shell.Run("curl", cmdArgs...)
log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.")
return run("curl", cmdArgs...)
}
token, err = fetchToken(c, appURL)
tok, err = token.FetchToken(appURL, appInfo, log)
if err != nil {
logger.Error("Failed to refresh token: ", err)
log.Err(err).Msg("Failed to refresh token")
return err
}
}
cmdArgs = append(cmdArgs, "-H")
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", token))
return shell.Run("curl", cmdArgs...)
cmdArgs = append(cmdArgs, fmt.Sprintf("%s: %s", carrier.CFAccessTokenHeader, tok))
return run("curl", cmdArgs...)
}
// run kicks off a shell task and pipe the results to the respective std pipes
func run(cmd string, args ...string) error {
c := exec.Command(cmd, args...)
stderr, err := c.StderrPipe()
if err != nil {
return err
}
go func() {
io.Copy(os.Stderr, stderr)
}()
stdout, err := c.StdoutPipe()
if err != nil {
return err
}
go func() {
io.Copy(os.Stdout, stdout)
}()
return c.Run()
}
// token dumps provided token to stdout
func token(c *cli.Context) error {
raven.SetDSN(sentryDSN)
appURL, err := url.Parse(c.String("app"))
func generateToken(c *cli.Context) error {
if err := raven.SetDSN(sentryDSN); err != nil {
return err
}
appURL, err := url.Parse(ensureURLScheme(c.String("app")))
if err != nil || c.NumFlags() < 1 {
fmt.Fprintln(os.Stderr, "Please provide a url.")
return err
}
token, err := getTokenIfExists(appURL)
if err != nil || token == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run token command to generate token.")
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
if err != nil || tok == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run login command to generate token.")
return err
}
if _, err := fmt.Fprint(os.Stdout, token); err != nil {
if _, err := fmt.Fprint(os.Stdout, tok); err != nil {
fmt.Fprintln(os.Stderr, "Failed to write token to stdout.")
return err
}
return nil
}
// sshConfig prints an example SSH config to stdout
func sshConfig(c *cli.Context) error {
genCertBool := c.Bool(sshGenCertFlag)
hostname := c.String(sshHostnameFlag)
if hostname == "" {
hostname = "[your hostname]"
}
type config struct {
Home string
ShortLivedCerts bool
Hostname string
Cloudflared string
}
t := template.Must(template.New("sshConfig").Parse(sshConfigTemplate))
return t.Execute(os.Stdout, config{Home: os.Getenv("HOME"), ShortLivedCerts: genCertBool, Hostname: hostname, Cloudflared: cloudflaredPath()})
}
// sshGen generates a short lived certificate for provided hostname
func sshGen(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
// get the hostname from the cmdline and error out if its not provided
rawHostName := c.String(sshHostnameFlag)
hostname, err := validation.ValidateHostname(rawHostName)
if err != nil || rawHostName == "" {
return cli.ShowCommandHelp(c, "ssh-gen")
}
originURL, err := url.Parse(ensureURLScheme(hostname))
if err != nil {
return err
}
// this fetchToken function mutates the appURL param. We should refactor that
fetchTokenURL := &url.URL{}
*fetchTokenURL = *originURL
appInfo, err := token.GetAppInfo(fetchTokenURL)
if err != nil {
return err
}
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log)
if err != nil {
return err
}
if err := sshgen.GenerateShortLivedCertificate(originURL, cfdToken); err != nil {
return err
}
return nil
}
// getAppURL will pull the request URL needed for fetching a user's Access token
func getAppURL(cmdArgs []string, log *zerolog.Logger) (*url.URL, error) {
if len(cmdArgs) < 1 {
log.Error().Msg("Please provide a valid URL as the first argument to curl.")
return nil, errors.New("not a valid url")
}
u, err := processURL(cmdArgs[0])
if err != nil {
log.Error().Msg("Please provide a valid URL as the first argument to curl.")
return nil, err
}
return u, err
}
// parseAllowRequest will parse cmdArgs and return a copy of the args and result
// of the allow request was present
func parseAllowRequest(cmdArgs []string) ([]string, bool) {
if len(cmdArgs) > 1 {
if cmdArgs[0] == "--allow-request" || cmdArgs[0] == "-ar" {
return cmdArgs[1:], true
}
}
return cmdArgs, false
}
// processURL will preprocess the string (parse to a url, convert to punycode, etc).
func processURL(s string) (*url.URL, error) {
u, err := url.ParseRequestURI(s)
if err != nil {
return nil, err
}
if u.Host == "" {
return nil, errors.New("not a valid host")
}
host, err := idna.ToASCII(u.Hostname())
if err != nil { // we fail to convert to punycode, just return the url we parsed.
return u, nil
@@ -167,33 +447,78 @@ func processURL(s string) (*url.URL, error) {
return u, nil
}
// buildCurlCmdArgs will build the curl cmd args
func buildCurlCmdArgs(cmdArgs []string) ([]string, *url.URL, bool, error) {
allowRequest, iAllowRequest := false, 0
var appURL *url.URL
for i, arg := range cmdArgs {
if arg == "-allow-request" || arg == "-ar" {
iAllowRequest = i
allowRequest = true
}
u, err := processURL(arg)
if err == nil {
appURL = u
cmdArgs[i] = appURL.String()
// cloudflaredPath pulls the full path of cloudflared on disk
func cloudflaredPath() string {
for _, p := range strings.Split(os.Getenv("PATH"), ":") {
path := fmt.Sprintf("%s/%s", p, "cloudflared")
if isFileThere(path) {
return path
}
}
if appURL == nil {
logger.Error("Please provide a valid URL.")
return cmdArgs, appURL, allowRequest, errors.New("invalid url")
}
if allowRequest {
// remove from cmdArgs
cmdArgs[iAllowRequest] = cmdArgs[len(cmdArgs)-1]
cmdArgs = cmdArgs[:len(cmdArgs)-1]
}
return cmdArgs, appURL, allowRequest, nil
return "cloudflared"
}
// isFileThere will check for the presence of candidate path
func isFileThere(candidate string) bool {
fi, err := os.Stat(candidate)
if err != nil || fi.IsDir() || !fi.Mode().IsRegular() {
return false
}
return true
}
// verifyTokenAtEdge checks for a token on disk, or generates a new one.
// Then makes a request to to the origin with the token to ensure it is valid.
// Returns nil if token is valid.
func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context, log *zerolog.Logger) error {
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Add(cfAccessClientIDHeader, c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Add(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers}
if valid, err := isTokenValid(options, log); err != nil {
return err
} else if valid {
return nil
}
if err := token.RemoveTokenIfExists(appInfo); err != nil {
return err
}
if valid, err := isTokenValid(options, log); err != nil {
return err
} else if !valid {
return errors.New("failed to verify token")
}
return nil
}
// isTokenValid makes a request to the origin and returns true if the response was not a 302.
func isTokenValid(options *carrier.StartOptions, log *zerolog.Logger) (bool, error) {
req, err := carrier.BuildAccessRequest(options, log)
if err != nil {
return false, errors.Wrap(err, "Could not create access request")
}
// Do not follow redirects
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: time.Second * 5,
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
// A redirect to login means the token was invalid.
return !carrier.IsAccessResponse(resp), nil
}
+25
View File
@@ -0,0 +1,25 @@
package access
import "testing"
func Test_ensureURLScheme(t *testing.T) {
type args struct {
url string
}
tests := []struct {
name string
args args
want string
}{
{"no scheme", args{"localhost:123"}, "https://localhost:123"},
{"http scheme", args{"http://test"}, "https://test"},
{"https scheme", args{"https://test"}, "https://test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ensureURLScheme(tt.args.url); got != tt.want {
t.Errorf("ensureURLScheme() = %v, want %v", got, tt.want)
}
})
}
}
-90
View File
@@ -1,90 +0,0 @@
package access
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
"github.com/cloudflare/cloudflared/log"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/oidc"
homedir "github.com/mitchellh/go-homedir"
cli "gopkg.in/urfave/cli.v2"
)
var logger = log.CreateLogger()
// fetchToken will either load a stored token or generate a new one
func fetchToken(c *cli.Context, appURL *url.URL) (string, error) {
if token, err := getTokenIfExists(appURL); token != "" && err == nil {
fmt.Fprintf(os.Stdout, "You have an existing token:\n\n%s\n\n", token)
return token, nil
}
path, err := generateFilePathForTokenURL(appURL)
if err != nil {
return "", err
}
// this weird parameter is the resource name (token) and the key/value
// we want to send to the transfer service. the key is token and the value
// is blank (basically just the id generated in the transfer service)
const resourceName, key, value = "token", "token", ""
token, err := transfer.Run(c, appURL, resourceName, key, value, path, true)
if err != nil {
return "", err
}
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", string(token))
return string(token), nil
}
// getTokenIfExists will return the token from local storage if it exists
func getTokenIfExists(url *url.URL) (string, error) {
path, err := generateFilePathForTokenURL(url)
if err != nil {
return "", err
}
content, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
token, err := jose.ParseJWT(string(content))
if err != nil {
return "", err
}
claims, err := token.Claims()
if err != nil {
return "", err
}
ident, err := oidc.IdentityFromClaims(claims)
if err == nil && ident.ExpiresAt.After(time.Now()) {
return token.Encode(), nil
}
return "", err
}
// generateFilePathForTokenURL will return a filepath for given access application url
func generateFilePathForTokenURL(url *url.URL) (string, error) {
configPath, err := homedir.Expand(config.DefaultConfigDirs[0])
if err != nil {
return "", err
}
ok, err := config.FileExists(configPath)
if !ok && err == nil {
// create config directory if doesn't already exist
err = os.Mkdir(configPath, 0700)
}
if err != nil {
return "", err
}
name := strings.Replace(fmt.Sprintf("%s%s-token", url.Hostname(), url.EscapedPath()), "/", "-", -1)
return filepath.Join(configPath, name), nil
}
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
"github.com/cloudflare/cloudflared/config"
)
// ForwardServiceType is used to identify what kind of overwatch service this is
const ForwardServiceType = "forward"
// ForwarderService is used to wrap the access package websocket forwarders
// into a service model for the overwatch package.
// it also holds a reference to the config object that represents its state
type ForwarderService struct {
forwarder config.Forwarder
shutdown chan struct{}
log *zerolog.Logger
}
// NewForwardService creates a new forwarder service
func NewForwardService(f config.Forwarder, log *zerolog.Logger) *ForwarderService {
return &ForwarderService{forwarder: f, shutdown: make(chan struct{}, 1), log: log}
}
// Name is used to figure out this service is related to the others (normally the addr it binds to)
// e.g. localhost:78641 or 127.0.0.1:2222 since this is a websocket forwarder
func (s *ForwarderService) Name() string {
return s.forwarder.Listener
}
// Type is used to identify what kind of overwatch service this is
func (s *ForwarderService) Type() string {
return ForwardServiceType
}
// Hash is used to figure out if this forwarder is the unchanged or not from the config file updates
func (s *ForwarderService) Hash() string {
return s.forwarder.Hash()
}
// Shutdown stops the websocket listener
func (s *ForwarderService) Shutdown() {
s.shutdown <- struct{}{}
}
// Run is the run loop that is started by the overwatch service
func (s *ForwarderService) Run() error {
return access.StartForwarder(s.forwarder, s.shutdown, s.log)
}
+87
View File
@@ -0,0 +1,87 @@
package main
import (
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/tunneldns"
)
const (
// ResolverServiceType is used to identify what kind of overwatch service this is
ResolverServiceType = "resolver"
LogFieldResolverAddress = "resolverAddress"
LogFieldResolverPort = "resolverPort"
LogFieldResolverMaxUpstreamConns = "resolverMaxUpstreamConns"
)
// ResolverService is used to wrap the tunneldns package's DNS over HTTP
// into a service model for the overwatch package.
// it also holds a reference to the config object that represents its state
type ResolverService struct {
resolver config.DNSResolver
shutdown chan struct{}
log *zerolog.Logger
}
// NewResolverService creates a new resolver service
func NewResolverService(r config.DNSResolver, log *zerolog.Logger) *ResolverService {
return &ResolverService{resolver: r,
shutdown: make(chan struct{}),
log: log,
}
}
// Name is used to figure out this service is related to the others (normally the addr it binds to)
// this is just "resolver" since there can only be one DNS resolver running
func (s *ResolverService) Name() string {
return ResolverServiceType
}
// Type is used to identify what kind of overwatch service this is
func (s *ResolverService) Type() string {
return ResolverServiceType
}
// Hash is used to figure out if this forwarder is the unchanged or not from the config file updates
func (s *ResolverService) Hash() string {
return s.resolver.Hash()
}
// Shutdown stops the tunneldns listener
func (s *ResolverService) Shutdown() {
s.shutdown <- struct{}{}
}
// Run is the run loop that is started by the overwatch service
func (s *ResolverService) Run() error {
// create a listener
l, err := tunneldns.CreateListener(s.resolver.AddressOrDefault(), s.resolver.PortOrDefault(),
s.resolver.UpstreamsOrDefault(), s.resolver.BootstrapsOrDefault(), s.resolver.MaxUpstreamConnectionsOrDefault(), s.log)
if err != nil {
return err
}
// start the listener.
readySignal := make(chan struct{})
err = l.Start(readySignal)
if err != nil {
_ = l.Stop()
return err
}
<-readySignal
resolverLog := s.log.With().
Str(LogFieldResolverAddress, s.resolver.AddressOrDefault()).
Uint16(LogFieldResolverPort, s.resolver.PortOrDefault()).
Int(LogFieldResolverMaxUpstreamConns, s.resolver.MaxUpstreamConnectionsOrDefault()).
Logger()
resolverLog.Info().Msg("Starting resolver")
// wait for shutdown signal
<-s.shutdown
resolverLog.Info().Msg("Shutting down resolver")
return l.Stop()
}
+92
View File
@@ -0,0 +1,92 @@
package main
import (
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/overwatch"
)
// AppService is the main service that runs when no command lines flags are passed to cloudflared
// it manages all the running services such as tunnels, forwarders, DNS resolver, etc
type AppService struct {
configManager config.Manager
serviceManager overwatch.Manager
shutdownC chan struct{}
configUpdateChan chan config.Root
log *zerolog.Logger
}
// NewAppService creates a new AppService with needed supporting services
func NewAppService(configManager config.Manager, serviceManager overwatch.Manager, shutdownC chan struct{}, log *zerolog.Logger) *AppService {
return &AppService{
configManager: configManager,
serviceManager: serviceManager,
shutdownC: shutdownC,
configUpdateChan: make(chan config.Root),
log: log,
}
}
// Run starts the run loop to handle config updates and run forwarders, tunnels, etc
func (s *AppService) Run() error {
go s.actionLoop()
return s.configManager.Start(s)
}
// Shutdown kills all the running services
func (s *AppService) Shutdown() error {
s.configManager.Shutdown()
s.shutdownC <- struct{}{}
return nil
}
// ConfigDidUpdate is a delegate notification from the config manager
// it is trigger when the config file has been updated and now the service needs
// to update its services accordingly
func (s *AppService) ConfigDidUpdate(c config.Root) {
s.configUpdateChan <- c
}
// actionLoop handles the actions from running processes
func (s *AppService) actionLoop() {
for {
select {
case c := <-s.configUpdateChan:
s.handleConfigUpdate(c)
case <-s.shutdownC:
for _, service := range s.serviceManager.Services() {
service.Shutdown()
}
return
}
}
}
func (s *AppService) handleConfigUpdate(c config.Root) {
// handle the client forward listeners
activeServices := map[string]struct{}{}
for _, f := range c.Forwarders {
service := NewForwardService(f, s.log)
s.serviceManager.Add(service)
activeServices[service.Name()] = struct{}{}
}
// handle resolver changes
if c.Resolver.Enabled {
service := NewResolverService(c.Resolver, s.log)
s.serviceManager.Add(service)
activeServices[service.Name()] = struct{}{}
}
// TODO: TUN-1451 - tunnels
// remove any services that are no longer active
for _, service := range s.serviceManager.Services() {
if _, ok := activeServices[service.Name()]; !ok {
s.serviceManager.Remove(service.Name())
}
}
}
+49
View File
@@ -0,0 +1,49 @@
package cliutil
import (
"fmt"
"runtime"
"github.com/rs/zerolog"
)
type BuildInfo struct {
GoOS string `json:"go_os"`
GoVersion string `json:"go_version"`
GoArch string `json:"go_arch"`
BuildType string `json:"build_type"`
CloudflaredVersion string `json:"cloudflared_version"`
}
func GetBuildInfo(buildType, version string) *BuildInfo {
return &BuildInfo{
GoOS: runtime.GOOS,
GoVersion: runtime.Version(),
GoArch: runtime.GOARCH,
BuildType: buildType,
CloudflaredVersion: version,
}
}
func (bi *BuildInfo) Log(log *zerolog.Logger) {
log.Info().Msgf("Version %s", bi.CloudflaredVersion)
if bi.BuildType != "" {
log.Info().Msgf("Built%s", bi.GetBuildTypeMsg())
}
log.Info().Msgf("GOOS: %s, GOVersion: %s, GoArch: %s", bi.GoOS, bi.GoVersion, bi.GoArch)
}
func (bi *BuildInfo) OSArch() string {
return fmt.Sprintf("%s_%s", bi.GoOS, bi.GoArch)
}
func (bi *BuildInfo) Version() string {
return bi.CloudflaredVersion
}
func (bi *BuildInfo) GetBuildTypeMsg() string {
if bi.BuildType == "" {
return ""
}
return fmt.Sprintf(" with %s", bi.BuildType)
}
+21
View File
@@ -0,0 +1,21 @@
package cliutil
import (
"fmt"
"github.com/urfave/cli/v2"
)
func RemovedCommand(name string) *cli.Command {
return &cli.Command{
Name: name,
Action: func(context *cli.Context) error {
return cli.Exit(
fmt.Sprintf("%s command is no longer supported by cloudflared. Consult Cloudflare Tunnel documentation for possible alternative solutions.", name),
-1,
)
},
Description: fmt.Sprintf("%s is deprecated", name),
Hidden: true,
}
}
+38
View File
@@ -0,0 +1,38 @@
package cliutil
import (
"fmt"
"github.com/urfave/cli/v2"
)
type usageError string
func (ue usageError) Error() string {
return string(ue)
}
func UsageError(format string, args ...interface{}) error {
if len(args) == 0 {
return usageError(format)
} else {
msg := fmt.Sprintf(format, args...)
return usageError(msg)
}
}
// Ensures exit with error code if actionFunc returns an error
func WithErrorHandler(actionFunc cli.ActionFunc) cli.ActionFunc {
return func(ctx *cli.Context) error {
err := actionFunc(ctx)
if err != nil {
if _, ok := err.(usageError); ok {
msg := fmt.Sprintf("%s\nSee 'cloudflared %s --help'.", err.Error(), ctx.Command.FullName())
err = cli.Exit(msg, -1)
} else if _, ok := err.(cli.ExitCoder); !ok {
err = cli.Exit(err.Error(), 1)
}
}
return err
}
}
+50
View File
@@ -0,0 +1,50 @@
package cliutil
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
)
func Action(actionFunc cli.ActionFunc) cli.ActionFunc {
return WithErrorHandler(actionFunc)
}
func ConfiguredAction(actionFunc cli.ActionFunc) cli.ActionFunc {
// Adapt actionFunc to the type signature required by ConfiguredActionWithWarnings
f := func(context *cli.Context, _ string) error {
return actionFunc(context)
}
return ConfiguredActionWithWarnings(f)
}
// Just like ConfiguredAction, but accepts a second parameter with configuration warnings.
func ConfiguredActionWithWarnings(actionFunc func(*cli.Context, string) error) cli.ActionFunc {
return WithErrorHandler(func(c *cli.Context) error {
warnings, err := setFlagsFromConfigFile(c)
if err != nil {
return err
}
return actionFunc(c, warnings)
})
}
func setFlagsFromConfigFile(c *cli.Context) (configWarnings string, err error) {
const errorExitCode = 1
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
inputSource, warnings, err := config.ReadConfigFile(c, log)
if err != nil {
if err == config.ErrNoConfigFile {
return "", nil
}
return "", cli.Exit(err, errorExitCode)
}
if err := altsrc.ApplyInputSource(c, inputSource); err != nil {
return "", cli.Exit(err, errorExitCode)
}
return warnings, nil
}
-62
View File
@@ -1,62 +0,0 @@
package config
import (
"os"
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"gopkg.in/urfave/cli.v2"
"gopkg.in/urfave/cli.v2/altsrc"
)
var (
// File names from which we attempt to read configuration.
DefaultConfigFiles = []string{"config.yml", "config.yaml"}
// Launchd doesn't set root env variables, so there is default
// Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
DefaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp", "/usr/local/etc/cloudflared", "/etc/cloudflared"}
)
const DefaultCredentialFile = "cert.pem"
// FileExists checks to see if a file exist at the provided path.
func FileExists(path string) (bool, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
// ignore missing files
return false, nil
}
return false, err
}
f.Close()
return true, nil
}
// FindInputSourceContext pulls the input source from the config flag.
func FindInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, error) {
if context.String("config") != "" {
return altsrc.NewYamlSourceFromFile(context.String("config"))
}
return nil, nil
}
// FindDefaultConfigPath returns the first path that contains a config file.
// If none of the combination of DefaultConfigDirs and DefaultConfigFiles
// contains a config file, return empty string.
func FindDefaultConfigPath() string {
for _, configDir := range DefaultConfigDirs {
for _, configFile := range DefaultConfigFiles {
dirPath, err := homedir.Expand(configDir)
if err != nil {
continue
}
path := filepath.Join(dirPath, configFile)
if ok, _ := FileExists(path); ok {
return path
}
}
}
return ""
}
+3 -2
View File
@@ -1,3 +1,4 @@
//go:build !windows && !darwin && !linux
// +build !windows,!darwin,!linux
package main
@@ -5,9 +6,9 @@ package main
import (
"os"
cli "gopkg.in/urfave/cli.v2"
cli "github.com/urfave/cli/v2"
)
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Run(os.Args)
}
+122 -64
View File
@@ -1,3 +1,4 @@
//go:build linux
// +build linux
package main
@@ -7,24 +8,35 @@ import (
"os"
"path/filepath"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
cli "gopkg.in/urfave/cli.v2"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
)
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel system service",
Usage: "Manages the Cloudflare Tunnel system service",
Subcommands: []*cli.Command{
&cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as a system service",
Action: installLinuxService,
Usage: "Install Cloudflare Tunnel as a system service",
Action: cliutil.ConfiguredAction(installLinuxService),
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "legacy",
Usage: "Generate service file for non-named tunnels",
},
},
},
&cli.Command{
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel service",
Action: uninstallLinuxService,
Usage: "Uninstall the Cloudflare Tunnel service",
Action: cliutil.ConfiguredAction(uninstallLinuxService),
},
},
})
@@ -37,19 +49,20 @@ const (
serviceConfigDir = "/etc/cloudflared"
serviceConfigFile = "config.yml"
serviceCredentialFile = "cert.pem"
serviceConfigPath = serviceConfigDir + "/" + serviceConfigFile
)
var systemdTemplates = []ServiceTemplate{
{
Path: "/etc/systemd/system/cloudflared.service",
Content: `[Unit]
Description=Argo Tunnel
Description=Cloudflare Tunnel
After=network.target
[Service]
TimeoutStartSec=0
Type=notify
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --no-autoupdate
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
Restart=on-failure
RestartSec=5s
@@ -60,20 +73,20 @@ WantedBy=multi-user.target
{
Path: "/etc/systemd/system/cloudflared-update.service",
Content: `[Unit]
Description=Update Argo Tunnel
Description=Update Cloudflare Tunnel
After=network.target
[Service]
ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 64 ]; then systemctl restart cloudflared; exit 0; fi; exit $code'
ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 11 ]; then systemctl restart cloudflared; exit 0; fi; exit $code'
`,
},
{
Path: "/etc/systemd/system/cloudflared-update.timer",
Content: `[Unit]
Description=Update Argo Tunnel
Description=Update Cloudflare Tunnel
[Timer]
OnUnitActiveSec=1d
OnCalendar=daily
[Install]
WantedBy=timers.target
@@ -84,9 +97,10 @@ WantedBy=timers.target
var sysvTemplate = ServiceTemplate{
Path: "/etc/init.d/cloudflared",
FileMode: 0755,
Content: `# For RedHat and cousins:
Content: `#!/bin/sh
# For RedHat and cousins:
# chkconfig: 2345 99 01
# description: Argo Tunnel agent
# description: Cloudflare Tunnel agent
# processname: {{.Path}}
### BEGIN INIT INFO
# Provides: {{.Path}}
@@ -94,11 +108,11 @@ var sysvTemplate = ServiceTemplate{
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Argo Tunnel
# Description: Argo Tunnel agent
# Short-Description: Cloudflare Tunnel
# Description: Cloudflare Tunnel agent
### END INIT INFO
name=$(basename $(readlink -f $0))
cmd="{{.Path}} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s"
cmd="{{.Path}} --config /etc/cloudflared/config.yml --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"
@@ -117,10 +131,6 @@ case "$1" in
echo "Starting $name"
$cmd >> "$stdout_log" 2>> "$stderr_log" &
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see $stdout_log and $stderr_log"
exit 1
fi
fi
;;
stop)
@@ -181,77 +191,123 @@ func isSystemd() bool {
return false
}
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string) error {
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
return err
}
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string, log *zerolog.Logger) error {
srcCredentialPath := filepath.Join(userConfigDir, userCredentialFile)
destCredentialPath := filepath.Join(serviceConfigDir, serviceCredentialFile)
if err := copyCredential(srcCredentialPath, destCredentialPath); err != nil {
return err
if srcCredentialPath != destCredentialPath {
if err := copyCredential(srcCredentialPath, destCredentialPath); err != nil {
return err
}
}
srcConfigPath := filepath.Join(userConfigDir, userConfigFile)
destConfigPath := filepath.Join(serviceConfigDir, serviceConfigFile)
if err := copyConfig(srcConfigPath, destConfigPath); err != nil {
return err
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)
etPath, err := os.Executable()
if err != nil {
return fmt.Errorf("error determining executable path: %v", err)
}
templateArgs := ServiceTemplateArgs{Path: etPath}
templateArgs := ServiceTemplateArgs{
Path: etPath,
}
userConfigDir := filepath.Dir(c.String("config"))
userConfigFile := filepath.Base(c.String("config"))
userCredentialFile := config.DefaultCredentialFile
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile); err != nil {
logger.WithError(err).Infof("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s",
serviceConfigDir, serviceCredentialFile, serviceConfigFile)
if err := ensureConfigDirExists(serviceConfigDir); 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",
}
}
switch {
case isSystemd():
logger.Infof("Using Systemd")
return installSystemd(&templateArgs)
log.Info().Msgf("Using Systemd")
return installSystemd(&templateArgs, log)
default:
logger.Infof("Using Sysv")
return installSysv(&templateArgs)
log.Info().Msgf("Using SysV")
return installSysv(&templateArgs, log)
}
}
func installSystemd(templateArgs *ServiceTemplateArgs) error {
func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
for _, serviceTemplate := range systemdTemplates {
err := serviceTemplate.Generate(templateArgs)
if err != nil {
logger.WithError(err).Infof("error generating service template")
log.Err(err).Msg("error generating service template")
return err
}
}
if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil {
logger.WithError(err).Infof("systemctl enable cloudflared.service error")
log.Err(err).Msg("systemctl enable cloudflared.service error")
return err
}
if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil {
logger.WithError(err).Infof("systemctl start cloudflared-update.timer error")
log.Err(err).Msg("systemctl start cloudflared-update.timer error")
return err
}
logger.Infof("systemctl daemon-reload")
log.Info().Msg("systemctl daemon-reload")
return runCommand("systemctl", "daemon-reload")
}
func installSysv(templateArgs *ServiceTemplateArgs) error {
func installSysv(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
confPath, err := sysvTemplate.ResolvePath()
if err != nil {
logger.WithError(err).Infof("error resolving system path")
log.Err(err).Msg("error resolving system path")
return err
}
if err := sysvTemplate.Generate(templateArgs); err != nil {
logger.WithError(err).Infof("error generating system template")
log.Err(err).Msg("error generating system template")
return err
}
for _, i := range [...]string{"2", "3", "4", "5"} {
@@ -268,38 +324,40 @@ func installSysv(templateArgs *ServiceTemplateArgs) error {
}
func uninstallLinuxService(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
switch {
case isSystemd():
logger.Infof("Using Systemd")
return uninstallSystemd()
log.Info().Msg("Using Systemd")
return uninstallSystemd(log)
default:
logger.Infof("Using Sysv")
return uninstallSysv()
log.Info().Msg("Using SysV")
return uninstallSysv(log)
}
}
func uninstallSystemd() error {
func uninstallSystemd(log *zerolog.Logger) error {
if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil {
logger.WithError(err).Infof("systemctl disable cloudflared.service error")
log.Err(err).Msg("systemctl disable cloudflared.service error")
return err
}
if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil {
logger.WithError(err).Infof("systemctl stop cloudflared-update.timer error")
log.Err(err).Msg("systemctl stop cloudflared-update.timer error")
return err
}
for _, serviceTemplate := range systemdTemplates {
if err := serviceTemplate.Remove(); err != nil {
logger.WithError(err).Infof("error removing service template")
log.Err(err).Msg("error removing service template")
return err
}
}
logger.Infof("Successfully uninstall cloudflared service")
log.Info().Msgf("Successfully uninstalled cloudflared service from systemd")
return nil
}
func uninstallSysv() error {
func uninstallSysv(log *zerolog.Logger) error {
if err := sysvTemplate.Remove(); err != nil {
logger.WithError(err).Infof("error removing service template")
log.Err(err).Msg("error removing service template")
return err
}
for _, i := range [...]string{"2", "3", "4", "5"} {
@@ -312,6 +370,6 @@ func uninstallSysv() error {
continue
}
}
logger.Infof("Successfully uninstall cloudflared service")
log.Info().Msgf("Successfully uninstalled cloudflared service from sysv")
return nil
}
+35 -32
View File
@@ -1,3 +1,4 @@
//go:build darwin
// +build darwin
package main
@@ -6,33 +7,35 @@ import (
"fmt"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
)
const (
launchdIdentifier = "com.cloudflare.cloudflared"
)
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel launch agent",
Usage: "Manages the Cloudflare Tunnel launch agent",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as an user launch agent",
Action: installLaunchd,
Usage: "Install Cloudflare Tunnel as an user launch agent",
Action: cliutil.ConfiguredAction(installLaunchd),
},
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel launch agent",
Action: uninstallLaunchd,
Usage: "Uninstall the Cloudflare Tunnel launch agent",
Action: cliutil.ConfiguredAction(uninstallLaunchd),
},
},
})
app.Run(os.Args)
_ = app.Run(os.Args)
}
func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemplate {
@@ -60,7 +63,7 @@ func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemp
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>20</integer>
<integer>5</integer>
</dict>
</plist>`, launchdIdentifier, stdoutPath, stderrPath),
}
@@ -105,58 +108,61 @@ func stderrPath() (string, error) {
}
func installLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
logger.Infof("Installing Argo Tunnel client as a system launch daemon. " +
"Argo Tunnel client will run at boot")
log.Info().Msg("Installing Cloudflare Tunnel client as a system launch daemon. " +
"Cloudflare Tunnel client will run at boot")
} else {
logger.Infof("Installing Argo Tunnel client as an user launch agent. " +
"Note that Argo Tunnel client will only run when the user is logged in. " +
"If you want to run Argo Tunnel client at boot, install with root permission. " +
"For more information, visit https://developers.cloudflare.com/argo-tunnel/reference/service/")
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. " +
"For more information, visit https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service")
}
etPath, err := os.Executable()
if err != nil {
logger.WithError(err).Errorf("Error determining executable path")
log.Err(err).Msg("Error determining executable path")
return fmt.Errorf("Error determining executable path: %v", err)
}
installPath, err := installPath()
if err != nil {
log.Err(err).Msg("Error determining install path")
return errors.Wrap(err, "Error determining install path")
}
stdoutPath, err := stdoutPath()
if err != nil {
log.Err(err).Msg("error determining stdout path")
return errors.Wrap(err, "error determining stdout path")
}
stderrPath, err := stderrPath()
if err != nil {
log.Err(err).Msg("error determining stderr path")
return errors.Wrap(err, "error determining stderr path")
}
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
if err != nil {
logger.WithError(err).Errorf("error creating launchd template")
return errors.Wrap(err, "error creating launchd template")
}
templateArgs := ServiceTemplateArgs{Path: etPath}
err = launchdTemplate.Generate(&templateArgs)
if err != nil {
logger.WithError(err).Errorf("error generating launchd template")
log.Err(err).Msg("error generating launchd template")
return err
}
plistPath, err := launchdTemplate.ResolvePath()
if err != nil {
logger.WithError(err).Infof("error resolving launchd template path")
log.Err(err).Msg("error resolving launchd template path")
return err
}
logger.Infof("Outputs are logged to %s and %s", stderrPath, stdoutPath)
log.Info().Msgf("Outputs are logged to %s and %s", stderrPath, stdoutPath)
return runCommand("launchctl", "load", plistPath)
}
func uninstallLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
logger.Infof("Uninstalling Argo Tunnel as a system launch daemon")
log.Info().Msg("Uninstalling Cloudflare Tunnel as a system launch daemon")
} else {
logger.Infof("Uninstalling Argo Tunnel as an user launch agent")
log.Info().Msg("Uninstalling Cloudflare Tunnel as an user launch agent")
}
installPath, err := installPath()
if err != nil {
@@ -171,20 +177,17 @@ func uninstallLaunchd(c *cli.Context) error {
return errors.Wrap(err, "error determining stderr path")
}
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
if err != nil {
return errors.Wrap(err, "error creating launchd template")
}
plistPath, err := launchdTemplate.ResolvePath()
if err != nil {
logger.WithError(err).Infof("error resolving launchd template path")
log.Err(err).Msg("error resolving launchd template path")
return err
}
err = runCommand("launchctl", "unload", plistPath)
if err != nil {
logger.WithError(err).Infof("error unloading")
log.Err(err).Msg("error unloading")
return err
}
logger.Infof("Outputs are logged to %s and %s", stderrPath, stdoutPath)
log.Info().Msgf("Outputs are logged to %s and %s", stderrPath, stdoutPath)
return launchdTemplate.Remove()
}
+151 -39
View File
@@ -2,77 +2,138 @@ package main
import (
"fmt"
"math/rand"
"strings"
"time"
"github.com/getsentry/raven-go"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"go.uber.org/automaxprocs/maxprocs"
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns"
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/log"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/getsentry/raven-go"
"github.com/mitchellh/go-homedir"
"gopkg.in/urfave/cli.v2"
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/overwatch"
"github.com/cloudflare/cloudflared/watcher"
)
const (
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
licenseUrl = developerPortal + "/licence/"
versionText = "Print the version"
)
var (
Version = "DEV"
BuildTime = "unknown"
logger = log.CreateLogger()
BuildType = ""
// Mostly network errors that we don't want reported back to Sentry, this is done by substring match.
ignoredErrors = []string{
"connection reset by peer",
"An existing connection was forcibly closed by the remote host.",
"use of closed connection",
"You need to enable Argo Smart Routing",
"3001 connection closed",
"3002 connection dropped",
"rpc exception: dial tcp",
"rpc exception: EOF",
}
)
func main() {
metrics.RegisterBuildInfo(BuildTime, Version)
rand.Seed(time.Now().UnixNano())
metrics.RegisterBuildInfo(BuildType, BuildTime, Version)
raven.SetRelease(Version)
maxprocs.Set()
bInfo := cliutil.GetBuildInfo(BuildType, Version)
// Force shutdown channel used by the app. When closed, app must terminate.
// Windows service manager closes this channel when it receives shutdown command.
shutdownC := make(chan struct{})
// Graceful shutdown channel used by the app. When closed, app must terminate.
// Graceful shutdown channel used by the app. When closed, app must terminate gracefully.
// Windows service manager closes this channel when it receives stop command.
graceShutdownC := make(chan struct{})
cli.VersionFlag = &cli.BoolFlag{
Name: "version",
Aliases: []string{"v", "V"},
Usage: versionText,
}
app := &cli.App{}
app.Name = "cloudflared"
app.Usage = "Cloudflare's command-line tool and agent"
app.ArgsUsage = "origin-url"
app.Copyright = fmt.Sprintf(`(c) %d Cloudflare Inc.
Use is subject to the license agreement at %s`, time.Now().Year(), licenseUrl)
app.Version = fmt.Sprintf("%s (built %s)", Version, BuildTime)
app.Description = `cloudflared connects your machine or user identity to Cloudflare's global network.
app.UsageText = "cloudflared [global options] [command] [command options]"
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),
Terms (https://www.cloudflare.com/terms/) and Privacy Policy (https://www.cloudflare.com/privacypolicy/).`,
time.Now().Year(),
)
app.Version = fmt.Sprintf("%s (built %s%s)", Version, BuildTime, bInfo.GetBuildTypeMsg())
app.Description = `cloudflared connects your machine or user identity to Cloudflare's global network.
You can use it to authenticate a session to reach an API behind Access, route web traffic to this machine,
and configure access control.`
app.Flags = flags()
app.Action = action(Version, shutdownC, graceShutdownC)
app.Before = tunnel.Before
app.Commands = commands()
and configure access control.
tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command...
runApp(app, shutdownC, graceShutdownC)
See https://developers.cloudflare.com/cloudflare-one/connections/connect-apps for more in-depth documentation.`
app.Flags = flags()
app.Action = action(graceShutdownC)
app.Commands = commands(cli.ShowVersion)
tunnel.Init(bInfo, graceShutdownC) // we need this to support the tunnel sub command...
access.Init(graceShutdownC)
updater.Init(Version)
runApp(app, graceShutdownC)
}
func commands() []*cli.Command {
func commands(version func(c *cli.Context)) []*cli.Command {
cmds := []*cli.Command{
{
Name: "update",
Action: updater.Update,
Usage: "Update the agent if a new version exists",
ArgsUsage: " ",
Name: "update",
Action: cliutil.ConfiguredAction(updater.Update),
Usage: "Update the agent if a new version exists",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "beta",
Usage: "specify if you wish to update to the latest beta version",
},
&cli.BoolFlag{
Name: "force",
Usage: "specify if you wish to force an upgrade to the latest version regardless of the current version",
Hidden: true,
},
&cli.BoolFlag{
Name: "staging",
Usage: "specify if you wish to use the staging url for updating",
Hidden: true,
},
&cli.StringFlag{
Name: "version",
Usage: "specify a version you wish to upgrade or downgrade to",
Hidden: false,
},
},
Description: `Looks for a new version on the official download server.
If a new version exists, updates the agent binary and quits.
Otherwise, does nothing.
To determine if an update happened in a script, check for error code 64.`,
To determine if an update happened in a script, check for error code 11.`,
},
{
Name: "version",
Action: func(c *cli.Context) (err error) {
version(c)
return nil
},
Usage: versionText,
Description: versionText,
},
}
cmds = append(cmds, tunnel.Commands()...)
cmds = append(cmds, proxydns.Command(false))
cmds = append(cmds, access.Commands()...)
return cmds
}
@@ -82,17 +143,24 @@ func flags() []cli.Flag {
return append(flags, access.Flags()...)
}
func action(version string, shutdownC, graceShutdownC chan struct{}) cli.ActionFunc {
return func(c *cli.Context) (err error) {
func isEmptyInvocation(c *cli.Context) bool {
return c.NArg() == 0 && c.NumFlags() == 0
}
func action(graceShutdownC chan struct{}) cli.ActionFunc {
return cliutil.ConfiguredAction(func(c *cli.Context) (err error) {
if isEmptyInvocation(c) {
return handleServiceMode(c, graceShutdownC)
}
tags := make(map[string]string)
tags["hostname"] = c.String("hostname")
raven.SetTagsContext(tags)
raven.CapturePanic(func() { err = tunnel.StartServer(c, version, shutdownC, graceShutdownC) }, nil)
raven.CapturePanic(func() { err = tunnel.TunnelCommand(c) }, nil)
if err != nil {
raven.CaptureError(err, nil)
captureError(err)
}
return err
}
})
}
func userHomeDir() (string, error) {
@@ -102,8 +170,52 @@ func userHomeDir() (string, error) {
// use with sudo.
homeDir, err := homedir.Dir()
if err != nil {
logger.WithError(err).Error("Cannot determine home directory for the user")
return "", errors.Wrap(err, "Cannot determine home directory for the user")
}
return homeDir, nil
}
// In order to keep the amount of noise sent to Sentry low, typical network errors can be filtered out here by a substring match.
func captureError(err error) {
errorMessage := err.Error()
for _, ignoredErrorMessage := range ignoredErrors {
if strings.Contains(errorMessage, ignoredErrorMessage) {
return
}
}
raven.CaptureError(err, nil)
}
// cloudflared was started without any flags
func handleServiceMode(c *cli.Context, shutdownC chan struct{}) error {
log := logger.CreateLoggerFromContext(c, logger.DisableTerminalLog)
// start the main run loop that reads from the config file
f, err := watcher.NewFile()
if err != nil {
log.Err(err).Msg("Cannot load config file")
return err
}
configPath := config.FindOrCreateConfigPath()
configManager, err := config.NewFileManager(f, configPath, log)
if err != nil {
log.Err(err).Msg("Cannot setup config file for monitoring")
return err
}
log.Info().Msgf("monitoring config file at: %s", configPath)
serviceCallback := func(t string, name string, err error) {
if err != nil {
log.Err(err).Msgf("%s service: %s encountered an error", t, name)
}
}
serviceManager := overwatch.NewAppManager(serviceCallback)
appService := NewAppService(configManager, serviceManager, shutdownC, log)
if err := appService.Run(); err != nil {
log.Err(err).Msg("Failed to start app service")
return err
}
return nil
}
+114
View File
@@ -0,0 +1,114 @@
package proxydns
import (
"net"
"os"
"os/signal"
"syscall"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/tunneldns"
)
func Command(hidden bool) *cli.Command {
return &cli.Command{
Name: "proxy-dns",
Action: cliutil.ConfiguredAction(Run),
Usage: "Run a DNS over HTTPS proxy server.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "metrics",
Value: "localhost:",
Usage: "Listen address for metrics reporting.",
EnvVars: []string{"TUNNEL_METRICS"},
},
&cli.StringFlag{
Name: "address",
Usage: "Listen address for the DNS over HTTPS proxy server.",
Value: "localhost",
EnvVars: []string{"TUNNEL_DNS_ADDRESS"},
},
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
&cli.IntFlag{
Name: "port",
Usage: "Listen on given port for the DNS over HTTPS proxy server.",
Value: 53,
EnvVars: []string{"TUNNEL_DNS_PORT"},
},
&cli.StringSliceFlag{
Name: "upstream",
Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
},
&cli.StringSliceFlag{
Name: "bootstrap",
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
},
&cli.IntFlag{
Name: "max-upstream-conns",
Usage: "Maximum concurrent connections to upstream. Setting to 0 means unlimited.",
Value: tunneldns.MaxUpstreamConnsDefault,
EnvVars: []string{"TUNNEL_DNS_MAX_UPSTREAM_CONNS"},
},
},
ArgsUsage: " ", // can't be the empty string or we get the default output
Hidden: hidden,
}
}
// Run implements a foreground runner
func Run(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
metricsListener, err := net.Listen("tcp", c.String("metrics"))
if err != nil {
log.Fatal().Err(err).Msg("Failed to open the metrics listener")
}
go metrics.ServeMetrics(metricsListener, nil, nil, "", log)
listener, err := tunneldns.CreateListener(
c.String("address"),
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
uint16(c.Int("port")),
c.StringSlice("upstream"),
c.StringSlice("bootstrap"),
c.Int("max-upstream-conns"),
log,
)
if err != nil {
log.Err(err).Msg("Failed to create the listeners")
return err
}
// Try to start the server
readySignal := make(chan struct{})
err = listener.Start(readySignal)
if err != nil {
log.Err(err).Msg("Failed to start the listeners")
return listener.Stop()
}
<-readySignal
// Wait for signal
signals := make(chan os.Signal, 10)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
defer signal.Stop(signals)
<-signals
// Shut down server
err = listener.Stop()
if err != nil {
log.Err(err).Msg("failed to stop")
}
return err
}
+36 -14
View File
@@ -10,8 +10,9 @@ import (
"os/exec"
"text/template"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/mitchellh/go-homedir"
homedir "github.com/mitchellh/go-homedir"
"github.com/cloudflare/cloudflared/config"
)
type ServiceTemplate struct {
@@ -21,7 +22,8 @@ type ServiceTemplate struct {
}
type ServiceTemplateArgs struct {
Path string
Path string
ExtraArgs []string
}
func (st *ServiceTemplate) ResolvePath() (string, error) {
@@ -73,21 +75,16 @@ func runCommand(command string, args ...string) error {
cmd := exec.Command(command, args...)
stderr, err := cmd.StderrPipe()
if err != nil {
logger.WithError(err).Infof("error getting stderr pipe")
return fmt.Errorf("error getting stderr pipe: %v", err)
}
err = cmd.Start()
if err != nil {
logger.WithError(err).Infof("error starting %s", command)
return fmt.Errorf("error starting %s: %v", command, err)
}
commandErr, _ := ioutil.ReadAll(stderr)
if len(commandErr) > 0 {
logger.Errorf("%s: %s", command, commandErr)
}
_, _ = ioutil.ReadAll(stderr)
err = cmd.Wait()
if err != nil {
logger.WithError(err).Infof("%s returned error", command)
return fmt.Errorf("%s returned with error: %v", command, err)
}
return nil
@@ -96,7 +93,7 @@ func runCommand(command string, args ...string) error {
func ensureConfigDirExists(configDir string) error {
ok, err := config.FileExists(configDir)
if !ok && err == nil {
err = os.Mkdir(configDir, 0700)
err = os.Mkdir(configDir, 0755)
}
return err
}
@@ -144,12 +141,38 @@ func copyCredential(srcCredentialPath, destCredentialPath string) error {
return nil
}
func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
ok := false
defer func() {
destFile.Close()
if !ok {
_ = os.Remove(dest)
}
}()
if _, err := io.Copy(destFile, srcFile); err != nil {
return err
}
ok = true
return nil
}
func copyConfig(srcConfigPath, destConfigPath string) error {
// Copy or create config
destFile, exists, err := openFile(destConfigPath, true)
if err != nil {
logger.WithError(err).Infof("cannot open %s", destConfigPath)
return err
return fmt.Errorf("cannot open %s with error: %s", destConfigPath, err)
} else if exists {
// config already exists, do nothing
return nil
@@ -173,7 +196,6 @@ func copyConfig(srcConfigPath, destConfigPath string) error {
if err != nil {
return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err)
}
logger.Infof("Copied %s to %s", srcConfigPath, destConfigPath)
}
return nil
-47
View File
@@ -1,47 +0,0 @@
package shell
import (
"io"
"os"
"os/exec"
"runtime"
)
// OpenBrowser opens the specified URL in the default browser of the user
func OpenBrowser(url string) error {
var cmd string
var args []string
switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()
}
// Run will kick off a shell task and pipe the results to the respective std pipes
func Run(cmd string, args ...string) error {
c := exec.Command(cmd, args...)
stderr, err := c.StderrPipe()
if err != nil {
return err
}
go func() {
io.Copy(os.Stderr, stderr)
}()
stdout, err := c.StdoutPipe()
if err != nil {
return err
}
go func() {
io.Copy(os.Stdout, stdout)
}()
return c.Run()
}
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
package tunnel
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHostnameFromURI(t *testing.T) {
assert.Equal(t, "awesome.warptunnels.horse:22", hostnameFromURI("ssh://awesome.warptunnels.horse:22"))
assert.Equal(t, "awesome.warptunnels.horse:22", hostnameFromURI("ssh://awesome.warptunnels.horse"))
assert.Equal(t, "awesome.warptunnels.horse:2222", hostnameFromURI("ssh://awesome.warptunnels.horse:2222"))
assert.Equal(t, "localhost:3389", hostnameFromURI("rdp://localhost"))
assert.Equal(t, "localhost:3390", hostnameFromURI("rdp://localhost:3390"))
assert.Equal(t, "", hostnameFromURI("trash"))
assert.Equal(t, "", hostnameFromURI("https://awesomesauce.com"))
}
+13
View File
@@ -0,0 +1,13 @@
package tunnel
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDedup(t *testing.T) {
expected := []string{"a", "b"}
actual := dedup([]string{"a", "b", "a"})
require.ElementsMatch(t, expected, actual)
}
+229 -159
View File
@@ -2,44 +2,48 @@ package tunnel
import (
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/ssh/terminal"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tlsconfig"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
"golang.org/x/crypto/ssh/terminal"
"github.com/sirupsen/logrus"
"gopkg.in/urfave/cli.v2"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
)
const LogFieldOriginCertPath = "originCertPath"
var (
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
quickStartUrl = developerPortal + "/quickstart/quickstart/"
serviceUrl = developerPortal + "/reference/service/"
argumentsUrl = developerPortal + "/reference/arguments/"
LogFieldHostname = "hostname"
)
// returns the first path that contains a cert.pem file. If none of the DefaultConfigDirs
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
// contains a cert.pem file, return empty string
func findDefaultOriginCertPath() string {
for _, defaultConfigDir := range config.DefaultConfigDirs {
for _, defaultConfigDir := range config.DefaultConfigSearchDirectories() {
originCertPath, _ := homedir.Expand(filepath.Join(defaultConfigDir, config.DefaultCredentialFile))
if ok, _ := config.FileExists(originCertPath); ok {
return originCertPath
@@ -48,42 +52,23 @@ func findDefaultOriginCertPath() string {
return ""
}
func generateRandomClientID() string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
id := make([]byte, 32)
r.Read(id)
return hex.EncodeToString(id)
}
func handleDeprecatedOptions(c *cli.Context) error {
// Fail if the user provided an old authentication method
if c.IsSet("api-key") || c.IsSet("api-email") || c.IsSet("api-ca-key") {
logger.Error("You don't need to give us your api-key anymore. Please use the new login method. Just run cloudflared login")
return fmt.Errorf("Client provided deprecated options")
func generateRandomClientID(log *zerolog.Logger) (string, error) {
u, err := uuid.NewRandom()
if err != nil {
log.Error().Msgf("couldn't create UUID for client ID %s", err)
return "", err
}
return nil
return u.String(), nil
}
// validate url. It can be either from --url or argument
func validateUrl(c *cli.Context) (string, error) {
var url = c.String("url")
if c.NArg() > 0 {
if c.IsSet("url") {
return "", errors.New("Specified origin urls using both --url and argument. Decide which one you want, I can only support one.")
}
url = c.Args().Get(0)
}
validUrl, err := validation.ValidateUrl(url)
return validUrl, err
}
func logClientOptions(c *cli.Context) {
func logClientOptions(c *cli.Context, log *zerolog.Logger) {
flags := make(map[string]interface{})
for _, flag := range c.LocalFlagNames() {
for _, flag := range c.FlagNames() {
flags[flag] = c.Generic(flag)
}
if len(flags) > 0 {
logger.Infof("Flags %v", flags)
log.Info().Msgf("Settings: %v", flags)
}
envs := make(map[string]string)
@@ -98,38 +83,39 @@ func logClientOptions(c *cli.Context) {
}
}
if len(envs) > 0 {
logger.Infof("Environmental variables %v", envs)
log.Info().Msgf("Environmental variables %v", envs)
}
}
func dnsProxyStandAlone(c *cli.Context) bool {
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world"))
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelConfig) bool {
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil)
}
func getOriginCert(c *cli.Context) ([]byte, error) {
if c.String("origincert") == "" {
logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigDirs)
func findOriginCert(originCertPath string, log *zerolog.Logger) (string, error) {
if originCertPath == "" {
log.Info().Msgf("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigSearchDirectories())
if isRunningFromTerminal() {
logger.Errorf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl)
return nil, fmt.Errorf("Client didn't specify origincert path when running from terminal")
log.Error().Msgf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl)
return "", fmt.Errorf("client didn't specify origincert path when running from terminal")
} else {
logger.Errorf("You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", serviceUrl)
return nil, fmt.Errorf("Client didn't specify origincert path")
log.Error().Msgf("You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", serviceUrl)
return "", fmt.Errorf("client didn't specify origincert path")
}
}
// Check that the user has acquired a certificate using the login command
originCertPath, err := homedir.Expand(c.String("origincert"))
var err error
originCertPath, err = homedir.Expand(originCertPath)
if err != nil {
logger.WithError(err).Errorf("Cannot resolve path %s", c.String("origincert"))
return nil, fmt.Errorf("Cannot resolve path %s", c.String("origincert"))
log.Err(err).Msgf("Cannot resolve origin certificate path")
return "", fmt.Errorf("cannot resolve path %s", originCertPath)
}
// Check that the user has acquired a certificate using the login command
ok, err := config.FileExists(originCertPath)
if err != nil {
logger.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert"))
return nil, fmt.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert"))
log.Error().Err(err).Msgf("Cannot check if origin cert exists at path %s", originCertPath)
return "", fmt.Errorf("cannot check if origin cert exists at path %s", originCertPath)
}
if !ok {
logger.Errorf(`Cannot find a valid certificate for your origin at the path:
log.Error().Msgf(`Cannot find a valid certificate for your origin at the path:
%s
@@ -138,134 +124,218 @@ If you don't have a certificate signed by Cloudflare, run the command:
%s login
`, originCertPath, os.Args[0])
return nil, fmt.Errorf("Cannot find a valid certificate at the path %s", originCertPath)
return "", fmt.Errorf("cannot find a valid certificate at the path %s", originCertPath)
}
return originCertPath, nil
}
func readOriginCert(originCertPath string) ([]byte, error) {
// Easier to send the certificate as []byte via RPC than decoding it at this point
originCert, err := ioutil.ReadFile(originCertPath)
if err != nil {
logger.WithError(err).Errorf("Cannot read %s to load origin certificate", originCertPath)
return nil, fmt.Errorf("Cannot read %s to load origin certificate", originCertPath)
return nil, fmt.Errorf("cannot read %s to load origin certificate", originCertPath)
}
return originCert, nil
}
func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version string, logger, protoLogger *logrus.Logger) (*origin.TunnelConfig, error) {
hostname, err := validation.ValidateHostname(c.String("hostname"))
func getOriginCert(originCertPath string, log *zerolog.Logger) ([]byte, error) {
if originCertPath, err := findOriginCert(originCertPath, log); err != nil {
return nil, err
} else {
return readOriginCert(originCertPath)
}
}
func prepareTunnelConfig(
c *cli.Context,
info *cliutil.BuildInfo,
log, logTransport *zerolog.Logger,
observer *connection.Observer,
namedTunnel *connection.NamedTunnelConfig,
) (*origin.TunnelConfig, ingress.Ingress, error) {
isNamedTunnel := namedTunnel != nil
configHostname := c.String("hostname")
hostname, err := validation.ValidateHostname(configHostname)
if err != nil {
logger.WithError(err).Error("Invalid hostname")
return nil, errors.Wrap(err, "Invalid hostname")
log.Err(err).Str(LogFieldHostname, configHostname).Msg("Invalid hostname")
return nil, ingress.Ingress{}, errors.Wrap(err, "Invalid hostname")
}
clientID := c.String("id")
if !c.IsSet("id") {
clientID = generateRandomClientID()
clientID, err = generateRandomClientID(log)
if err != nil {
return nil, ingress.Ingress{}, err
}
}
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
if err != nil {
logger.WithError(err).Error("Tag parse failure")
return nil, errors.Wrap(err, "Tag parse failure")
log.Err(err).Msg("Tag parse failure")
return nil, ingress.Ingress{}, errors.Wrap(err, "Tag parse failure")
}
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
originURL, err := validateUrl(c)
if err != nil {
logger.WithError(err).Error("Error validating origin URL")
return nil, errors.Wrap(err, "Error validating origin URL")
}
logger.Infof("Proxying tunnel requests to %s", originURL)
originCert, err := getOriginCert(c)
if err != nil {
return nil, errors.Wrap(err, "Error getting origin cert")
}
originCertPool, err := loadCertPool(c, logger)
if err != nil {
logger.WithError(err).Error("Error loading cert pool")
return nil, errors.Wrap(err, "Error loading cert pool")
}
tunnelMetrics := origin.NewTunnelMetrics()
httpTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: c.Duration("proxy-connect-timeout"),
KeepAlive: c.Duration("proxy-tcp-keepalive"),
DualStack: !c.Bool("proxy-no-happy-eyeballs"),
}).DialContext,
MaxIdleConns: c.Int("proxy-keepalive-connections"),
IdleConnTimeout: c.Duration("proxy-keepalive-timeout"),
TLSHandshakeTimeout: c.Duration("proxy-tls-timeout"),
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: c.IsSet("no-tls-verify")},
}
if !c.IsSet("hello-world") && c.IsSet("origin-server-name") {
httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name")
}
err = validation.ValidateHTTPService(originURL, httpTransport)
if err != nil {
logger.WithError(err).Error("unable to connect to the origin")
return nil, errors.Wrap(err, "unable to connect to the origin")
}
return &origin.TunnelConfig{
EdgeAddrs: c.StringSlice("edge"),
OriginUrl: originURL,
Hostname: hostname,
OriginCert: originCert,
TlsConfig: tlsconfig.CreateTunnelConfig(c, c.StringSlice("edge")),
ClientTlsConfig: httpTransport.TLSClientConfig,
Retries: c.Uint("retries"),
HeartbeatInterval: c.Duration("heartbeat-interval"),
MaxHeartbeats: c.Uint64("heartbeat-count"),
ClientID: clientID,
BuildInfo: buildInfo,
ReportedVersion: version,
LBPool: c.String("lb-pool"),
Tags: tags,
HAConnections: c.Int("ha-connections"),
HTTPTransport: httpTransport,
Metrics: tunnelMetrics,
MetricsUpdateFreq: c.Duration("metrics-update-freq"),
ProtocolLogger: protoLogger,
Logger: logger,
IsAutoupdated: c.Bool("is-autoupdated"),
GracePeriod: c.Duration("grace-period"),
RunFromTerminal: isRunningFromTerminal(),
NoChunkedEncoding: c.Bool("no-chunked-encoding"),
CompressionQuality: c.Uint64("compression-quality"),
}, nil
}
func loadCertPool(c *cli.Context, logger *logrus.Logger) (*x509.CertPool, error) {
const originCAPoolFlag = "origin-ca-pool"
originCAPoolFilename := c.String(originCAPoolFlag)
var originCustomCAPool []byte
if originCAPoolFilename != "" {
var err error
originCustomCAPool, err = ioutil.ReadFile(originCAPoolFilename)
var (
ingressRules ingress.Ingress
classicTunnel *connection.ClassicTunnelConfig
)
cfg := config.GetConfiguration()
if isNamedTunnel {
clientUUID, err := uuid.NewRandom()
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s for --%s", originCAPoolFilename, originCAPoolFlag))
return nil, ingress.Ingress{}, errors.Wrap(err, "can't generate connector UUID")
}
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
features := append(c.StringSlice("features"), origin.FeatureSerializedHeaders)
namedTunnel.Client = tunnelpogs.ClientInfo{
ClientID: clientUUID[:],
Features: dedup(features),
Version: info.Version(),
Arch: info.OSArch(),
}
ingressRules, err = ingress.ParseIngress(cfg)
if err != nil && err != ingress.ErrNoIngressRules {
return nil, ingress.Ingress{}, err
}
if !ingressRules.IsEmpty() && c.IsSet("url") {
return nil, ingress.Ingress{}, ingress.ErrURLIncompatibleWithIngress
}
} else {
originCertPath := c.String("origincert")
originCertLog := log.With().
Str(LogFieldOriginCertPath, originCertPath).
Logger()
originCert, err := getOriginCert(originCertPath, &originCertLog)
if err != nil {
return nil, ingress.Ingress{}, errors.Wrap(err, "Error getting origin cert")
}
classicTunnel = &connection.ClassicTunnelConfig{
Hostname: hostname,
OriginCert: originCert,
// turn off use of reconnect token and auth refresh when using named tunnels
UseReconnectToken: !isNamedTunnel && c.Bool("use-reconnect-token"),
}
}
originCertPool, err := tlsconfig.LoadOriginCertPool(originCustomCAPool)
// Convert single-origin configuration into multi-origin configuration.
if ingressRules.IsEmpty() {
ingressRules, err = ingress.NewSingleOrigin(c, !isNamedTunnel)
if err != nil {
return nil, ingress.Ingress{}, err
}
}
var warpRoutingService *ingress.WarpRoutingService
warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel)
if warpRoutingEnabled {
warpRoutingService = ingress.NewWarpRoutingService()
log.Info().Msgf("Warp-routing is enabled")
}
protocolSelector, err := connection.NewProtocolSelector(c.String("protocol"), warpRoutingEnabled, namedTunnel, edgediscovery.ProtocolPercentage, origin.ResolveTTL, log)
if err != nil {
return nil, errors.Wrap(err, "error loading the certificate pool")
return nil, ingress.Ingress{}, err
}
log.Info().Msgf("Initial protocol %s", protocolSelector.Current())
edgeTLSConfigs := make(map[connection.Protocol]*tls.Config, len(connection.ProtocolList))
for _, p := range connection.ProtocolList {
tlsSettings := p.TLSSettings()
if tlsSettings == nil {
return nil, ingress.Ingress{}, fmt.Errorf("%s has unknown TLS settings", p)
}
edgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c, tlsSettings.ServerName)
if err != nil {
return nil, ingress.Ingress{}, errors.Wrap(err, "unable to create TLS config to connect with edge")
}
if len(tlsSettings.NextProtos) > 0 {
edgeTLSConfig.NextProtos = tlsSettings.NextProtos
}
edgeTLSConfigs[p] = edgeTLSConfig
}
// Windows users should be notified that they can use the flag
if runtime.GOOS == "windows" && originCAPoolFilename == "" {
logger.Infof("cloudflared does not support loading the system root certificate pool on Windows. Please use the --%s to specify it", originCAPoolFlag)
originProxy := origin.NewOriginProxy(ingressRules, warpRoutingService, tags, log)
gracePeriod, err := gracePeriod(c)
if err != nil {
return nil, ingress.Ingress{}, err
}
connectionConfig := &connection.Config{
OriginProxy: originProxy,
GracePeriod: gracePeriod,
ReplaceExisting: c.Bool("force"),
}
muxerConfig := &connection.MuxerConfig{
HeartbeatInterval: c.Duration("heartbeat-interval"),
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
MaxHeartbeats: uint64(c.Int("heartbeat-count")),
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
CompressionSetting: h2mux.CompressionSetting(uint64(c.Int("compression-quality"))),
MetricsUpdateFreq: c.Duration("metrics-update-freq"),
}
return originCertPool, nil
return &origin.TunnelConfig{
ConnectionConfig: connectionConfig,
OSArch: info.OSArch(),
ClientID: clientID,
EdgeAddrs: c.StringSlice("edge"),
Region: c.String("region"),
HAConnections: c.Int("ha-connections"),
IncidentLookup: origin.NewIncidentLookup(),
IsAutoupdated: c.Bool("is-autoupdated"),
LBPool: c.String("lb-pool"),
Tags: tags,
Log: log,
LogTransport: logTransport,
Observer: observer,
ReportedVersion: info.Version(),
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
Retries: uint(c.Int("retries")),
RunFromTerminal: isRunningFromTerminal(),
NamedTunnel: namedTunnel,
ClassicTunnel: classicTunnel,
MuxerConfig: muxerConfig,
ProtocolSelector: protocolSelector,
EdgeTLSConfigs: edgeTLSConfigs,
}, ingressRules, nil
}
func gracePeriod(c *cli.Context) (time.Duration, error) {
period := c.Duration("grace-period")
if period > connection.MaxGracePeriod {
return time.Duration(0), fmt.Errorf("grace-period must be equal or less than %v", connection.MaxGracePeriod)
}
return period, nil
}
func isWarpRoutingEnabled(warpConfig config.WarpRoutingConfig, isNamedTunnel bool) bool {
return warpConfig.Enabled && isNamedTunnel
}
func isRunningFromTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}
// Remove any duplicates from the slice
func dedup(slice []string) []string {
// Convert the slice into a set
set := make(map[string]bool, 0)
for _, str := range slice {
set[str] = true
}
// Convert the set back into a slice
keys := make([]string, len(set))
i := 0
for str := range set {
keys[i] = str
i++
}
return keys
}
@@ -0,0 +1,216 @@
//go:build ignore
// +build ignore
// TODO: Remove the above build tag and include this test when we start compiling with Golang 1.10.0+
package tunnel
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
// Generated using `openssl req -newkey rsa:512 -nodes -x509 -days 3650`
var samplePEM = []byte(`
-----BEGIN CERTIFICATE-----
MIIB4DCCAYoCCQCb/H0EUrdXEjANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV
UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv
dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE
AwwIVGVzdCBPbmUwHhcNMTgwNDI2MTYxMDUxWhcNMjgwNDIzMTYxMDUxWjB3MQsw
CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG
A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn
eTERMA8GA1UEAwwIVGVzdCBPbmUwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAwVQD
K0SJ25UFLznm2pU3zhzMEvpDEofHVNnCjk4mlDrtVop7PkKZ8pDEmuQANltUrxC8
yHBE2wXMv+GlH+bDtwIDAQABMA0GCSqGSIb3DQEBCwUAA0EAjVYQzozIFPkt/HRY
uUoZ8zEHIDICb0syFf5VAjm9AgTwIPzUmD+c5vl6LWDnxq7L45nLCzhhQ6YmiwDz
X7Wcyg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB4DCCAYoCCQDZfCdAJ+mwzDANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV
UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv
dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE
AwwIVGVzdCBUd28wHhcNMTgwNDI2MTYxMTIwWhcNMjgwNDIzMTYxMTIwWjB3MQsw
CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG
A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn
eTERMA8GA1UEAwwIVGVzdCBUd28wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAoHKp
ROVK3zCSsH7ocYeyRAML4V7SFAbZcb4WIwDnE08oMBVRkQVcW5tqEkvG3RiClfzV
wZIJ3CfqKIeSNSDU9wIDAQABMA0GCSqGSIb3DQEBCwUAA0EAJw2gUbnPiq4C2p5b
iWzlA9Q7aKo+VQ4H7IZS7tTccr59nVjvH/TG3eWujpnocr4TOqW9M3CK1DF9mUGP
3pQ3Jg==
-----END CERTIFICATE-----
`)
var systemCertPoolSubjects []*pkix.Name
type certificateFixture struct {
ou string
cn string
}
func TestMain(m *testing.M) {
systemCertPool, err := x509.SystemCertPool()
if isUnrecoverableError(err) {
os.Exit(1)
}
if systemCertPool == nil {
// On Windows, let's just assume the system cert pool was empty
systemCertPool = x509.NewCertPool()
}
systemCertPoolSubjects, err = getCertPoolSubjects(systemCertPool)
if err != nil {
os.Exit(1)
}
os.Exit(m.Run())
}
func TestLoadOriginCertPoolJustSystemPool(t *testing.T) {
certPoolSubjects := loadCertPoolSubjects(t, nil)
extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects)
// Remove extra subjects from the cert pool
var filteredSystemCertPoolSubjects []*pkix.Name
t.Log(extraSubjects)
OUTER:
for _, subject := range certPoolSubjects {
for _, extraSubject := range extraSubjects {
if subject == extraSubject {
t.Log(extraSubject)
continue OUTER
}
}
filteredSystemCertPoolSubjects = append(filteredSystemCertPoolSubjects, subject)
}
assert.Equal(t, len(filteredSystemCertPoolSubjects), len(systemCertPoolSubjects))
difference := subjectSubtract(systemCertPoolSubjects, filteredSystemCertPoolSubjects)
assert.Equal(t, 0, len(difference))
}
func TestLoadOriginCertPoolCFCertificates(t *testing.T) {
certPoolSubjects := loadCertPoolSubjects(t, nil)
extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects)
expected := []*certificateFixture{
{ou: "CloudFlare Origin SSL ECC Certificate Authority"},
{ou: "CloudFlare Origin SSL Certificate Authority"},
{cn: "origin-pull.cloudflare.net"},
{cn: "Argo Tunnel Sample Hello Server Certificate"},
}
assertFixturesMatchSubjects(t, expected, extraSubjects)
}
func TestLoadOriginCertPoolWithExtraPEMs(t *testing.T) {
certPoolWithoutPEMSubjects := loadCertPoolSubjects(t, nil)
certPoolWithPEMSubjects := loadCertPoolSubjects(t, samplePEM)
difference := subjectSubtract(certPoolWithoutPEMSubjects, certPoolWithPEMSubjects)
assert.Equal(t, 2, len(difference))
expected := []*certificateFixture{
{cn: "Test One"},
{cn: "Test Two"},
}
assertFixturesMatchSubjects(t, expected, difference)
}
func loadCertPoolSubjects(t *testing.T, originCAPoolPEM []byte) []*pkix.Name {
certPool, err := loadOriginCertPool(originCAPoolPEM)
if isUnrecoverableError(err) {
t.Fatal(err)
}
assert.NotEmpty(t, certPool.Subjects())
certPoolSubjects, err := getCertPoolSubjects(certPool)
if err != nil {
t.Fatal(err)
}
return certPoolSubjects
}
func assertFixturesMatchSubjects(t *testing.T, fixtures []*certificateFixture, subjects []*pkix.Name) {
assert.Equal(t, len(fixtures), len(subjects))
for _, fixture := range fixtures {
found := false
for _, subject := range subjects {
found = found || fixtureMatchesSubjectPredicate(fixture, subject)
}
if !found {
t.Fail()
}
}
}
func fixtureMatchesSubjectPredicate(fixture *certificateFixture, subject *pkix.Name) bool {
cnMatch := true
if fixture.cn != "" {
cnMatch = fixture.cn == subject.CommonName
}
ouMatch := true
if fixture.ou != "" {
ouMatch = len(subject.OrganizationalUnit) > 0 && fixture.ou == subject.OrganizationalUnit[0]
}
return cnMatch && ouMatch
}
func subjectSubtract(left []*pkix.Name, right []*pkix.Name) []*pkix.Name {
var difference []*pkix.Name
var found bool
for _, r := range right {
found = false
for _, l := range left {
if (*l).String() == (*r).String() {
found = true
}
}
if !found {
difference = append(difference, r)
}
}
return difference
}
func getCertPoolSubjects(certPool *x509.CertPool) ([]*pkix.Name, error) {
var subjects []*pkix.Name
for _, subject := range certPool.Subjects() {
var sequence pkix.RDNSequence
_, err := asn1.Unmarshal(subject, &sequence)
if err != nil {
return nil, err
}
name := pkix.Name{}
name.FillFromRDNSequence(&sequence)
subjects = append(subjects, &name)
}
return subjects, nil
}
func isUnrecoverableError(err error) bool {
return err != nil && err.Error() != "crypto/x509: system root pool is not available on Windows"
}
@@ -0,0 +1,83 @@
package tunnel
import (
"fmt"
"path/filepath"
"github.com/cloudflare/cloudflared/config"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
)
// CredFinder can find the tunnel credentials file.
type CredFinder interface {
Path() (string, error)
}
// Implements CredFinder and looks for the credentials file at the given
// filepath.
type staticPath struct {
filePath string
fs fileSystem
}
func newStaticPath(filePath string, fs fileSystem) CredFinder {
return staticPath{
filePath: filePath,
fs: fs,
}
}
func (a staticPath) Path() (string, error) {
if a.filePath != "" && a.fs.validFilePath(a.filePath) {
return a.filePath, nil
}
return "", fmt.Errorf("Tunnel credentials file '%s' doesn't exist or is not a file", a.filePath)
}
// Implements CredFinder and looks for the credentials file in several directories
// searching for a file named <id>.json
type searchByID struct {
id uuid.UUID
c *cli.Context
log *zerolog.Logger
fs fileSystem
}
func newSearchByID(id uuid.UUID, c *cli.Context, log *zerolog.Logger, fs fileSystem) CredFinder {
return searchByID{
id: id,
c: c,
log: log,
fs: fs,
}
}
func (s searchByID) Path() (string, error) {
originCertPath := s.c.String("origincert")
originCertLog := s.log.With().
Str(LogFieldOriginCertPath, originCertPath).
Logger()
// Fallback to look for tunnel credentials in the origin cert directory
if originCertPath, err := findOriginCert(originCertPath, &originCertLog); err == nil {
originCertDir := filepath.Dir(originCertPath)
if filePath, err := tunnelFilePath(s.id, originCertDir); err == nil {
if s.fs.validFilePath(filePath) {
return filePath, nil
}
}
}
// Last resort look under default config directories
for _, configDir := range config.DefaultConfigSearchDirectories() {
if filePath, err := tunnelFilePath(s.id, configDir); err == nil {
if s.fs.validFilePath(filePath) {
return filePath, nil
}
}
}
return "", fmt.Errorf("tunnel credentials file not found")
}
+27
View File
@@ -0,0 +1,27 @@
package tunnel
import (
"io/ioutil"
"os"
)
// Abstract away details of reading files, so that SubcommandContext can read
// from either the real filesystem, or a mock (when running unit tests).
type fileSystem interface {
readFile(filePath string) ([]byte, error)
validFilePath(path string) bool
}
type realFileSystem struct{}
func (fs realFileSystem) validFilePath(path string) bool {
fileStat, err := os.Stat(path)
if err != nil {
return false
}
return !fileStat.IsDir()
}
func (fs realFileSystem) readFile(filePath string) ([]byte, error) {
return ioutil.ReadFile(filePath)
}
-20
View File
@@ -1,20 +0,0 @@
package tunnel
import (
"fmt"
"gopkg.in/urfave/cli.v2"
"github.com/cloudflare/cloudflared/hello"
)
func helloWorld(c *cli.Context) error {
address := fmt.Sprintf(":%d", c.Int("port"))
listener, err := hello.CreateTLSListener(address)
if err != nil {
return err
}
defer listener.Close()
err = hello.StartHelloWorldServer(logger, listener, nil)
return err
}
+16
View File
@@ -0,0 +1,16 @@
package tunnel
import (
"time"
"github.com/google/uuid"
"github.com/cloudflare/cloudflared/cfapi"
)
type Info struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
Connectors []*cfapi.ActiveClient `json:"conns"`
}
@@ -0,0 +1,119 @@
package tunnel
import (
"fmt"
"net/url"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
func buildIngressSubcommand() *cli.Command {
return &cli.Command{
Name: "ingress",
Category: "Tunnel",
Usage: "Validate and test cloudflared tunnel's ingress configuration",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress COMMAND [arguments...]",
Hidden: true,
Description: ` Cloudflared lets you route traffic from the internet to multiple different addresses on your
origin. Multiple-origin routing is configured by a set of rules. Each rule matches traffic
by its hostname or path, and routes it to an address. These rules are configured under the
'ingress' key of your config.yaml, for example:
ingress:
- hostname: www.example.com
service: https://localhost:8000
- hostname: *.example.xyz
path: /[a-zA-Z]+.html
service: https://localhost:8001
- hostname: *
service: https://localhost:8002
To ensure cloudflared can route all incoming requests, the last rule must be a catch-all
rule that matches all traffic. You can validate these rules with the 'ingress validate'
command, and test which rule matches a particular URL with 'ingress rule <URL>'.
Multiple-origin routing is incompatible with the --url flag.`,
Subcommands: []*cli.Command{buildValidateIngressCommand(), buildTestURLCommand()},
}
}
func buildValidateIngressCommand() *cli.Command {
return &cli.Command{
Name: "validate",
Action: cliutil.ConfiguredActionWithWarnings(validateIngressCommand),
Usage: "Validate the ingress configuration ",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
}
}
func buildTestURLCommand() *cli.Command {
return &cli.Command{
Name: "rule",
Action: cliutil.ConfiguredAction(testURLCommand),
Usage: "Check which ingress rule matches a given request URL",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress rule URL",
ArgsUsage: "URL",
Description: "Check which ingress rule matches a given request URL. " +
"Ingress rules match a request's hostname and path. Hostname is " +
"optional and is either a full hostname like `www.example.com` or a " +
"hostname with a `*` for its subdomains, e.g. `*.example.com`. Path " +
"is optional and matches a regular expression, like `/[a-zA-Z0-9_]+.html`",
}
}
// validateIngressCommand check the syntax of the ingress rules in the cloudflared config file
func validateIngressCommand(c *cli.Context, warnings string) error {
conf := config.GetConfiguration()
if conf.Source() == "" {
fmt.Println("No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files")
return nil
}
fmt.Println("Validating rules from", conf.Source())
if _, err := ingress.ParseIngress(conf); err != nil {
return errors.Wrap(err, "Validation failed")
}
if c.IsSet("url") {
return ingress.ErrURLIncompatibleWithIngress
}
if warnings != "" {
fmt.Println("Warning: unused keys detected in your config file. Here is a list of unused keys:")
fmt.Println(warnings)
return nil
}
fmt.Println("OK")
return nil
}
// testURLCommand checks which ingress rule matches the given URL.
func testURLCommand(c *cli.Context) error {
requestArg := c.Args().First()
if requestArg == "" {
return errors.New("cloudflared tunnel rule expects a single argument, the URL to test")
}
requestURL, err := url.Parse(requestArg)
if err != nil {
return fmt.Errorf("%s is not a valid URL", requestArg)
}
if requestURL.Hostname() == "" && requestURL.Scheme == "" {
return fmt.Errorf("%s doesn't have a hostname, consider adding a scheme", requestArg)
}
conf := config.GetConfiguration()
fmt.Println("Using rules from", conf.Source())
ing, err := ingress.ParseIngress(conf)
if err != nil {
return errors.Wrap(err, "Validation failed")
}
_, i := ing.FindMatchingRule(requestURL.Hostname(), requestURL.Path)
fmt.Printf("Matched rule #%d\n", i+1)
fmt.Println(ing.Rules[i].MultiLineString())
return nil
}
-67
View File
@@ -1,67 +0,0 @@
package tunnel
import (
"fmt"
"os"
"github.com/cloudflare/cloudflared/log"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"gopkg.in/urfave/cli.v2"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
)
var logger = log.CreateLogger()
func configMainLogger(c *cli.Context) error {
logLevel, err := logrus.ParseLevel(c.String("loglevel"))
if err != nil {
logger.WithError(err).Error("Unknown logging level specified")
return errors.Wrap(err, "Unknown logging level specified")
}
logger.SetLevel(logLevel)
return nil
}
func configProtoLogger(c *cli.Context) (*logrus.Logger, error) {
protoLogLevel, err := logrus.ParseLevel(c.String("proto-loglevel"))
if err != nil {
logger.WithError(err).Fatal("Unknown protocol logging level specified")
return nil, errors.Wrap(err, "Unknown protocol logging level specified")
}
protoLogger := logrus.New()
protoLogger.Level = protoLogLevel
return protoLogger, nil
}
func initLogFile(c *cli.Context, loggers ...*logrus.Logger) error {
filePath, err := homedir.Expand(c.String("logfile"))
if err != nil {
return errors.Wrap(err, "Cannot resolve logfile path")
}
fileMode := os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC
// do not truncate log file if the client has been autoupdated
if c.Bool("is-autoupdated") {
fileMode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
}
f, err := os.OpenFile(filePath, fileMode, 0664)
if err != nil {
errors.Wrap(err, fmt.Sprintf("Cannot open file %s", filePath))
}
defer f.Close()
pathMap := lfshook.PathMap{
logrus.InfoLevel: filePath,
logrus.ErrorLevel: filePath,
logrus.FatalLevel: filePath,
logrus.PanicLevel: filePath,
}
for _, l := range loggers {
l.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{}))
}
return nil
}
+35 -6
View File
@@ -2,23 +2,40 @@ package tunnel
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"syscall"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
homedir "github.com/mitchellh/go-homedir"
cli "gopkg.in/urfave/cli.v2"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/token"
)
const (
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
callbackStoreURL = "https://login.cloudflarewarp.com/"
callbackStoreURL = "https://login.cloudflareaccess.org/"
)
func buildLoginSubcommand(hidden bool) *cli.Command {
return &cli.Command{
Name: "login",
Action: cliutil.ConfiguredAction(login),
Usage: "Generate a configuration file with your login details",
ArgsUsage: " ",
Hidden: hidden,
}
}
func login(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
path, ok, err := checkForExistingCert()
if ok {
fmt.Fprintf(os.Stdout, "You have an existing certificate at %s which login would overwrite.\nIf this is intentional, please move or delete that file then run this command again.\n", path)
@@ -33,18 +50,30 @@ func login(c *cli.Context) error {
return err
}
_, err = transfer.Run(c, loginURL, "cert", "callback", callbackStoreURL, path, false)
resourceData, err := token.RunTransfer(
loginURL,
"cert",
"callback",
callbackStoreURL,
false,
false,
log,
)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write the certificate due to the following error:\n%v\n\nYour browser will download the certificate instead. You will have to manually\ncopy it to the following path:\n\n%s\n", err, path)
return err
}
if err := ioutil.WriteFile(path, resourceData, 0600); err != nil {
return errors.Wrap(err, fmt.Sprintf("error writing cert to %s", path))
}
fmt.Fprintf(os.Stdout, "You have successfully logged in.\nIf you wish to copy your credentials to a server, they have been saved to:\n%s\n", path)
return nil
}
func checkForExistingCert() (string, bool, error) {
configPath, err := homedir.Expand(config.DefaultConfigDirs[0])
configPath, err := homedir.Expand(config.DefaultConfigSearchDirectories()[0])
if err != nil {
return "", false, err
}
+126
View File
@@ -0,0 +1,126 @@
package tunnel
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/connection"
)
const httpTimeout = 15 * time.Second
const disclaimer = "Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to" +
" experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee. If you " +
"intend to use Tunnels in production you should use a pre-created named tunnel by following: " +
"https://developers.cloudflare.com/cloudflare-one/connections/connect-apps"
// RunQuickTunnel requests a tunnel from the specified service.
// We use this to power quick tunnels on trycloudflare.com, but the
// service is open-source and could be used by anyone.
func RunQuickTunnel(sc *subcommandContext) error {
sc.log.Info().Msg(disclaimer)
sc.log.Info().Msg("Requesting new quick Tunnel on trycloudflare.com...")
client := http.Client{
Transport: &http.Transport{
TLSHandshakeTimeout: httpTimeout,
ResponseHeaderTimeout: httpTimeout,
},
Timeout: httpTimeout,
}
resp, err := client.Post(fmt.Sprintf("%s/tunnel", sc.c.String("quick-service")), "application/json", nil)
if err != nil {
return errors.Wrap(err, "failed to request quick Tunnel")
}
defer resp.Body.Close()
var data QuickTunnelResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return errors.Wrap(err, "failed to unmarshal quick Tunnel")
}
tunnelID, err := uuid.Parse(data.Result.ID)
if err != nil {
return errors.Wrap(err, "failed to parse quick Tunnel ID")
}
credentials := connection.Credentials{
AccountTag: data.Result.AccountTag,
TunnelSecret: data.Result.Secret,
TunnelID: tunnelID,
TunnelName: data.Result.Name,
}
url := data.Result.Hostname
if !strings.HasPrefix(url, "https://") {
url = "https://" + url
}
for _, line := range AsciiBox([]string{
"Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):",
url,
}, 2) {
sc.log.Info().Msg(line)
}
if !sc.c.IsSet("protocol") {
sc.c.Set("protocol", "quic")
}
return StartServer(
sc.c,
buildInfo,
&connection.NamedTunnelConfig{Credentials: credentials, QuickTunnelUrl: data.Result.Hostname},
sc.log,
sc.isUIEnabled,
)
}
type QuickTunnelResponse struct {
Success bool
Result QuickTunnel
Errors []QuickTunnelError
}
type QuickTunnelError struct {
Code int
Message string
}
type QuickTunnel struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
AccountTag string `json:"account_tag"`
Secret []byte `json:"secret"`
}
// Print out the given lines in a nice ASCII box.
func AsciiBox(lines []string, padding int) (box []string) {
maxLen := maxLen(lines)
spacer := strings.Repeat(" ", padding)
border := "+" + strings.Repeat("-", maxLen+(padding*2)) + "+"
box = append(box, border)
for _, line := range lines {
box = append(box, "|"+spacer+line+strings.Repeat(" ", maxLen-len(line))+spacer+"|")
}
box = append(box, border)
return
}
func maxLen(lines []string) int {
max := 0
for _, line := range lines {
if len(line) > max {
max = len(line)
}
}
return max
}
+12 -8
View File
@@ -1,33 +1,37 @@
package tunnel
import (
"fmt"
"github.com/cloudflare/cloudflared/tunneldns"
"gopkg.in/urfave/cli.v2"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
)
func runDNSProxyServer(c *cli.Context, dnsReadySignal, shutdownC chan struct{}) error {
func runDNSProxyServer(c *cli.Context, dnsReadySignal chan struct{}, shutdownC <-chan struct{}, log *zerolog.Logger) error {
port := c.Int("proxy-dns-port")
if port <= 0 || port > 65535 {
logger.Errorf("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
return errors.New("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
}
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"))
maxUpstreamConnections := c.Int("proxy-dns-max-upstream-conns")
if maxUpstreamConnections < 0 {
return fmt.Errorf("'%s' must be 0 or higher", "proxy-dns-max-upstream-conns")
}
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"), c.StringSlice("proxy-dns-bootstrap"), maxUpstreamConnections, log)
if err != nil {
close(dnsReadySignal)
listener.Stop()
logger.WithError(err).Error("Cannot create the DNS over HTTPS proxy server")
return errors.Wrap(err, "Cannot create the DNS over HTTPS proxy server")
}
err = listener.Start(dnsReadySignal)
if err != nil {
logger.WithError(err).Error("Cannot start the DNS over HTTPS proxy server")
return errors.Wrap(err, "Cannot start the DNS over HTTPS proxy server")
}
<-shutdownC
listener.Stop()
_ = listener.Stop()
log.Info().Msg("DNS server stopped")
return nil
}
+6 -62
View File
@@ -4,76 +4,20 @@ import (
"os"
"os/signal"
"syscall"
"time"
"github.com/rs/zerolog"
)
// waitForSignal notifies all routines to shutdownC immediately by closing the
// shutdownC when one of the routines in main exits, or when this process receives
// SIGTERM/SIGINT
func waitForSignal(errC chan error, shutdownC chan struct{}) error {
// waitForSignal closes graceShutdownC to indicate that we should start graceful shutdown sequence
func waitForSignal(graceShutdownC chan struct{}, logger *zerolog.Logger) {
signals := make(chan os.Signal, 10)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
defer signal.Stop(signals)
select {
case err := <-errC:
close(shutdownC)
return err
case <-signals:
close(shutdownC)
case <-shutdownC:
}
return nil
}
// waitForSignalWithGraceShutdown notifies all routines to shutdown immediately
// by closing the shutdownC when one of the routines in main exits.
// When this process recieves SIGTERM/SIGINT, it closes the graceShutdownC to
// notify certain routines to start graceful shutdown. When grace period is over,
// or when some routine exits, it notifies the rest of the routines to shutdown
// immediately by closing shutdownC.
// In the case of handling commands from Windows Service Manager, closing graceShutdownC
// initiate graceful shutdown.
func waitForSignalWithGraceShutdown(errC chan error,
shutdownC, graceShutdownC chan struct{},
gracePeriod time.Duration,
) error {
signals := make(chan os.Signal, 10)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
defer signal.Stop(signals)
select {
case err := <-errC:
case s := <-signals:
logger.Info().Msgf("Initiating graceful shutdown due to signal %s ...", s)
close(graceShutdownC)
close(shutdownC)
return err
case <-signals:
close(graceShutdownC)
waitForGracePeriod(signals, errC, shutdownC, gracePeriod)
case <-graceShutdownC:
waitForGracePeriod(signals, errC, shutdownC, gracePeriod)
case <-shutdownC:
close(graceShutdownC)
}
return nil
}
func waitForGracePeriod(signals chan os.Signal,
errC chan error,
shutdownC chan struct{},
gracePeriod time.Duration,
) {
logger.Infof("Initiating graceful shutdown...")
// Unregister signal handler early, so the client can send a second SIGTERM/SIGINT
// to force shutdown cloudflared
signal.Stop(signals)
graceTimerTick := time.Tick(gracePeriod)
// send close signal via shutdownC when grace period expires or when an
// error is encountered.
select {
case <-graceTimerTick:
case <-errC:
}
close(shutdownC)
}
+61 -107
View File
@@ -1,11 +1,16 @@
//go:build !windows
// +build !windows
package tunnel
import (
"fmt"
"sync"
"syscall"
"testing"
"time"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
)
@@ -17,136 +22,85 @@ var (
graceShutdownErr = fmt.Errorf("receive grace shutdown")
)
func testChannelClosed(t *testing.T, c chan struct{}) {
func channelClosed(c chan struct{}) bool {
select {
case <-c:
return
return true
default:
t.Fatal("Channel should be closed")
return false
}
}
func TestWaitForSignal(t *testing.T) {
// Test handling server error
errC := make(chan error)
shutdownC := make(chan struct{})
go func() {
errC <- serverErr
}()
// received error, shutdownC should be closed
err := waitForSignal(errC, shutdownC)
assert.Equal(t, serverErr, err)
testChannelClosed(t, shutdownC)
func TestSignalShutdown(t *testing.T) {
log := zerolog.Nop()
// Test handling SIGTERM & SIGINT
for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
errC = make(chan error)
shutdownC = make(chan struct{})
go func(shutdownC chan struct{}) {
<-shutdownC
errC <- shutdownErr
}(shutdownC)
graceShutdownC := make(chan struct{})
go func(sig syscall.Signal) {
// sleep for a tick to prevent sending signal before calling waitForSignal
time.Sleep(tick)
syscall.Kill(syscall.Getpid(), sig)
_ = syscall.Kill(syscall.Getpid(), sig)
}(sig)
err = waitForSignal(errC, shutdownC)
assert.Equal(t, nil, err)
assert.Equal(t, shutdownErr, <-errC)
testChannelClosed(t, shutdownC)
time.AfterFunc(time.Second, func() {
select {
case <-graceShutdownC:
default:
close(graceShutdownC)
t.Fatal("waitForSignal timed out")
}
})
waitForSignal(graceShutdownC, &log)
assert.True(t, channelClosed(graceShutdownC))
}
}
func TestWaitForSignalWithGraceShutdown(t *testing.T) {
// Test server returning error
errC := make(chan error)
shutdownC := make(chan struct{})
graceshutdownC := make(chan struct{})
func TestWaitForShutdown(t *testing.T) {
log := zerolog.Nop()
errC := make(chan error)
graceShutdownC := make(chan struct{})
const gracePeriod = 5 * time.Second
contextCancelled := false
cancel := func() {
contextCancelled = true
}
var wg sync.WaitGroup
// on, error stop immediately
contextCancelled = false
startTime := time.Now()
go func() {
errC <- serverErr
}()
// received error, both shutdownC and graceshutdownC should be closed
err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
err := waitToShutdown(&wg, cancel, errC, graceShutdownC, gracePeriod, &log)
assert.Equal(t, serverErr, err)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
assert.True(t, contextCancelled)
assert.False(t, channelClosed(graceShutdownC))
assert.True(t, time.Now().Sub(startTime) < time.Second) // check that wait ended early
// shutdownC closed, graceshutdownC should also be closed and no error
errC = make(chan error)
shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{})
close(shutdownC)
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.NoError(t, err)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
// on graceful shutdown, ignore error but stop as soon as an error arrives
contextCancelled = false
startTime = time.Now()
go func() {
close(graceShutdownC)
time.Sleep(tick)
errC <- serverErr
}()
err = waitToShutdown(&wg, cancel, errC, graceShutdownC, gracePeriod, &log)
assert.Nil(t, err)
assert.True(t, contextCancelled)
assert.True(t, time.Now().Sub(startTime) < time.Second) // check that wait ended early
// graceshutdownC closed, shutdownC should also be closed and no error
errC = make(chan error)
shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{})
close(graceshutdownC)
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.NoError(t, err)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
// Test handling SIGTERM & SIGINT
for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
errC := make(chan error)
shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{})
go func(shutdownC, graceshutdownC chan struct{}) {
<-graceshutdownC
<-shutdownC
errC <- graceShutdownErr
}(shutdownC, graceshutdownC)
go func(sig syscall.Signal) {
// sleep for a tick to prevent sending signal before calling waitForSignalWithGraceShutdown
time.Sleep(tick)
syscall.Kill(syscall.Getpid(), sig)
}(sig)
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.Equal(t, nil, err)
assert.Equal(t, graceShutdownErr, <-errC)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
}
// Test handling SIGTERM & SIGINT, server send error before end of grace period
for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
errC := make(chan error)
shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{})
go func(shutdownC, graceshutdownC chan struct{}) {
<-graceshutdownC
errC <- graceShutdownErr
<-shutdownC
errC <- shutdownErr
}(shutdownC, graceshutdownC)
go func(sig syscall.Signal) {
// sleep for a tick to prevent sending signal before calling waitForSignalWithGraceShutdown
time.Sleep(tick)
syscall.Kill(syscall.Getpid(), sig)
}(sig)
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.Equal(t, nil, err)
assert.Equal(t, shutdownErr, <-errC)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
}
// with graceShutdownC closed stop right away without grace period
contextCancelled = false
startTime = time.Now()
err = waitToShutdown(&wg, cancel, errC, graceShutdownC, 0, &log)
assert.Nil(t, err)
assert.True(t, contextCancelled)
assert.True(t, time.Now().Sub(startTime) < time.Second) // check that wait ended early
}
@@ -0,0 +1,427 @@
package tunnel
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/certutil"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/logger"
)
type errInvalidJSONCredential struct {
err error
path string
}
func (e errInvalidJSONCredential) Error() string {
return "Invalid JSON when parsing tunnel credentials file"
}
// subcommandContext carries structs shared between subcommands, to reduce number of arguments needed to
// pass between subcommands, and make sure they are only initialized once
type subcommandContext struct {
c *cli.Context
log *zerolog.Logger
isUIEnabled bool
fs fileSystem
// These fields should be accessed using their respective Getter
tunnelstoreClient cfapi.Client
userCredential *userCredential
}
func newSubcommandContext(c *cli.Context) (*subcommandContext, error) {
isUIEnabled := c.IsSet(uiFlag) && c.String("name") != ""
// If UI is enabled, terminal log output should be disabled -- log should be written into a UI log window instead
log := logger.CreateLoggerFromContext(c, isUIEnabled)
return &subcommandContext{
c: c,
log: log,
isUIEnabled: isUIEnabled,
fs: realFileSystem{},
}, nil
}
// Returns something that can find the given tunnel's credentials file.
func (sc *subcommandContext) credentialFinder(tunnelID uuid.UUID) CredFinder {
if path := sc.c.String(CredFileFlag); path != "" {
return newStaticPath(path, sc.fs)
}
return newSearchByID(tunnelID, sc.c, sc.log, sc.fs)
}
type userCredential struct {
cert *certutil.OriginCert
certPath string
}
func (sc *subcommandContext) client() (cfapi.Client, error) {
if sc.tunnelstoreClient != nil {
return sc.tunnelstoreClient, nil
}
credential, err := sc.credential()
if err != nil {
return nil, err
}
userAgent := fmt.Sprintf("cloudflared/%s", buildInfo.Version())
client, err := cfapi.NewRESTClient(
sc.c.String("api-url"),
credential.cert.AccountID,
credential.cert.ZoneID,
credential.cert.ServiceKey,
userAgent,
sc.log,
)
if err != nil {
return nil, err
}
sc.tunnelstoreClient = client
return client, nil
}
func (sc *subcommandContext) credential() (*userCredential, error) {
if sc.userCredential == nil {
originCertPath := sc.c.String("origincert")
originCertLog := sc.log.With().
Str(LogFieldOriginCertPath, originCertPath).
Logger()
originCertPath, err := findOriginCert(originCertPath, &originCertLog)
if err != nil {
return nil, errors.Wrap(err, "Error locating origin cert")
}
blocks, err := readOriginCert(originCertPath)
if err != nil {
return nil, errors.Wrapf(err, "Can't read origin cert from %s", originCertPath)
}
cert, err := certutil.DecodeOriginCert(blocks)
if err != nil {
return nil, errors.Wrap(err, "Error decoding origin cert")
}
if cert.AccountID == "" {
return nil, errors.Errorf(`Origin certificate needs to be refreshed before creating new tunnels.\nDelete %s and run "cloudflared login" to obtain a new cert.`, originCertPath)
}
sc.userCredential = &userCredential{
cert: cert,
certPath: originCertPath,
}
}
return sc.userCredential, nil
}
func (sc *subcommandContext) readTunnelCredentials(credFinder CredFinder) (connection.Credentials, error) {
filePath, err := credFinder.Path()
if err != nil {
return connection.Credentials{}, err
}
body, err := sc.fs.readFile(filePath)
if err != nil {
return connection.Credentials{}, errors.Wrapf(err, "couldn't read tunnel credentials from %v", filePath)
}
var credentials connection.Credentials
if err = json.Unmarshal(body, &credentials); err != nil {
if strings.HasSuffix(filePath, ".pem") {
return connection.Credentials{}, fmt.Errorf("The tunnel credentials file should be .json but you gave a .pem. " +
"The tunnel credentials file was originally created by `cloudflared tunnel create`. " +
"You may have accidentally used the filepath to cert.pem, which is generated by `cloudflared tunnel " +
"login`.")
}
return connection.Credentials{}, errInvalidJSONCredential{path: filePath, err: err}
}
return credentials, nil
}
func (sc *subcommandContext) create(name string, credentialsFilePath string, secret string) (*cfapi.Tunnel, error) {
client, err := sc.client()
if err != nil {
return nil, errors.Wrap(err, "couldn't create client to talk to Cloudflare Tunnel backend")
}
var tunnelSecret []byte
if secret == "" {
tunnelSecret, err = generateTunnelSecret()
if err != nil {
return nil, errors.Wrap(err, "couldn't generate the secret for your new tunnel")
}
} else {
decodedSecret, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return nil, errors.Wrap(err, "Couldn't decode tunnel secret from base64")
}
tunnelSecret = []byte(decodedSecret)
if len(tunnelSecret) < 32 {
return nil, errors.New("Decoded tunnel secret must be at least 32 bytes long")
}
}
tunnel, err := client.CreateTunnel(name, tunnelSecret)
if err != nil {
return nil, errors.Wrap(err, "Create Tunnel API call failed")
}
credential, err := sc.credential()
if err != nil {
return nil, err
}
tunnelCredentials := connection.Credentials{
AccountTag: credential.cert.AccountID,
TunnelSecret: tunnelSecret,
TunnelID: tunnel.ID,
TunnelName: name,
}
usedCertPath := false
if credentialsFilePath == "" {
originCertDir := filepath.Dir(credential.certPath)
credentialsFilePath, err = tunnelFilePath(tunnelCredentials.TunnelID, originCertDir)
if err != nil {
return nil, err
}
usedCertPath = true
}
writeFileErr := writeTunnelCredentials(credentialsFilePath, &tunnelCredentials)
if writeFileErr != nil {
var errorLines []string
errorLines = append(errorLines, fmt.Sprintf("Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write tunnel credentials to %s.", tunnel.Name, tunnel.ID, credentialsFilePath))
errorLines = append(errorLines, fmt.Sprintf("The file-writing error is: %v", writeFileErr))
if deleteErr := client.DeleteTunnel(tunnel.ID); deleteErr != nil {
errorLines = append(errorLines, fmt.Sprintf("Cloudflared tried to delete the tunnel for you, but encountered an error. You should use `cloudflared tunnel delete %v` to delete the tunnel yourself, because the tunnel can't be run without the tunnelfile.", tunnel.ID))
errorLines = append(errorLines, fmt.Sprintf("The delete tunnel error is: %v", deleteErr))
} else {
errorLines = append(errorLines, fmt.Sprintf("The tunnel was deleted, because the tunnel can't be run without the credentials file"))
}
errorMsg := strings.Join(errorLines, "\n")
return nil, errors.New(errorMsg)
}
if outputFormat := sc.c.String(outputFormatFlag.Name); outputFormat != "" {
return nil, renderOutput(outputFormat, &tunnel)
}
fmt.Printf("Tunnel credentials written to %v.", credentialsFilePath)
if usedCertPath {
fmt.Print(" cloudflared chose this file based on where your origin certificate was found.")
}
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
}
func (sc *subcommandContext) list(filter *cfapi.TunnelFilter) ([]*cfapi.Tunnel, error) {
client, err := sc.client()
if err != nil {
return nil, err
}
return client.ListTunnels(filter)
}
func (sc *subcommandContext) delete(tunnelIDs []uuid.UUID) error {
forceFlagSet := sc.c.Bool("force")
client, err := sc.client()
if err != nil {
return err
}
for _, id := range tunnelIDs {
tunnel, err := client.GetTunnel(id)
if err != nil {
return errors.Wrapf(err, "Can't get tunnel information. Please check tunnel id: %s", id)
}
// Check if tunnel DeletedAt field has already been set
if !tunnel.DeletedAt.IsZero() {
return fmt.Errorf("Tunnel %s has already been deleted", tunnel.ID)
}
if forceFlagSet {
if err := client.CleanupConnections(tunnel.ID, cfapi.NewCleanupParams()); err != nil {
return errors.Wrapf(err, "Error cleaning up connections for tunnel %s", tunnel.ID)
}
}
if err := client.DeleteTunnel(tunnel.ID); err != nil {
return errors.Wrapf(err, "Error deleting tunnel %s", tunnel.ID)
}
credFinder := sc.credentialFinder(id)
if tunnelCredentialsPath, err := credFinder.Path(); err == nil {
if err = os.Remove(tunnelCredentialsPath); err != nil {
sc.log.Info().Msgf("Tunnel %v was deleted, but we could not remove its credentials file %s: %s. Consider deleting this file manually.", id, tunnelCredentialsPath, err)
}
}
}
return nil
}
// findCredentials will choose the right way to find the credentials file, find it,
// and add the TunnelID into any old credentials (generated before TUN-3581 added the `TunnelID`
// field to credentials files)
func (sc *subcommandContext) findCredentials(tunnelID uuid.UUID) (connection.Credentials, error) {
var credentials connection.Credentials
var err error
if credentialsContents := sc.c.String(CredContentsFlag); credentialsContents != "" {
if err = json.Unmarshal([]byte(credentialsContents), &credentials); err != nil {
err = errInvalidJSONCredential{path: "TUNNEL_CRED_CONTENTS", err: err}
}
} else {
credFinder := sc.credentialFinder(tunnelID)
credentials, err = sc.readTunnelCredentials(credFinder)
}
// This line ensures backwards compatibility with credentials files generated before
// TUN-3581. Those old credentials files don't have a TunnelID field, so we enrich the struct
// with the ID, which we have already resolved from the user input.
credentials.TunnelID = tunnelID
return credentials, err
}
func (sc *subcommandContext) run(tunnelID uuid.UUID) error {
credentials, err := sc.findCredentials(tunnelID)
if err != nil {
if e, ok := err.(errInvalidJSONCredential); ok {
sc.log.Error().Msgf("The credentials file at %s contained invalid JSON. This is probably caused by passing the wrong filepath. Reminder: the credentials file is a .json file created via `cloudflared tunnel create`.", e.path)
sc.log.Error().Msgf("Invalid JSON when parsing credentials file: %s", e.err.Error())
}
return err
}
return StartServer(
sc.c,
buildInfo,
&connection.NamedTunnelConfig{Credentials: credentials},
sc.log,
sc.isUIEnabled,
)
}
func (sc *subcommandContext) cleanupConnections(tunnelIDs []uuid.UUID) error {
params := cfapi.NewCleanupParams()
extraLog := ""
if connector := sc.c.String("connector-id"); connector != "" {
connectorID, err := uuid.Parse(connector)
if err != nil {
return errors.Wrapf(err, "%s is not a valid client ID (must be a UUID)", connector)
}
params.ForClient(connectorID)
extraLog = fmt.Sprintf(" for connector-id %s", connectorID.String())
}
client, err := sc.client()
if err != nil {
return err
}
for _, tunnelID := range tunnelIDs {
sc.log.Info().Msgf("Cleanup connection for tunnel %s%s", tunnelID, extraLog)
if err := client.CleanupConnections(tunnelID, params); err != nil {
sc.log.Error().Msgf("Error cleaning up connections for tunnel %v, error :%v", tunnelID, err)
}
}
return nil
}
func (sc *subcommandContext) route(tunnelID uuid.UUID, r cfapi.HostnameRoute) (cfapi.HostnameRouteResult, error) {
client, err := sc.client()
if err != nil {
return nil, err
}
return client.RouteTunnel(tunnelID, r)
}
// Query Tunnelstore to find the active tunnel with the given name.
func (sc *subcommandContext) tunnelActive(name string) (*cfapi.Tunnel, bool, error) {
filter := cfapi.NewTunnelFilter()
filter.NoDeleted()
filter.ByName(name)
tunnels, err := sc.list(filter)
if err != nil {
return nil, false, err
}
if len(tunnels) == 0 {
return nil, false, nil
}
// There should only be 1 active tunnel for a given name
return tunnels[0], true, nil
}
// findID parses the input. If it's a UUID, return the UUID.
// Otherwise, assume it's a name, and look up the ID of that tunnel.
func (sc *subcommandContext) findID(input string) (uuid.UUID, error) {
if u, err := uuid.Parse(input); err == nil {
return u, nil
}
// 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 {
return credentials.TunnelID, nil
}
}
// Fall back to querying Tunnelstore.
if tunnel, found, err := sc.tunnelActive(input); err != nil {
return uuid.Nil, err
} else if found {
return tunnel.ID, nil
}
return uuid.Nil, fmt.Errorf("%s is neither the ID nor the name of any of your tunnels", input)
}
// findIDs is just like mapping `findID` over a slice, but it only uses
// one Tunnelstore API call per non-UUID input provided.
func (sc *subcommandContext) findIDs(inputs []string) ([]uuid.UUID, error) {
uuids, names := splitUuids(inputs)
for _, name := range names {
filter := cfapi.NewTunnelFilter()
filter.NoDeleted()
filter.ByName(name)
tunnels, err := sc.list(filter)
if err != nil {
return nil, err
}
if len(tunnels) != 1 {
return nil, fmt.Errorf("there should only be 1 non-deleted Tunnel named %s", name)
}
uuids = append(uuids, tunnels[0].ID)
}
return uuids, nil
}
func splitUuids(inputs []string) ([]uuid.UUID, []string) {
uuids := make([]uuid.UUID, 0)
names := make([]string, 0)
for _, input := range inputs {
id, err := uuid.Parse(input)
if err != nil {
names = append(names, input)
} else {
uuids = append(uuids, id)
}
}
return uuids, names
}
@@ -0,0 +1,41 @@
package tunnel
import (
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/cfapi"
)
const noClientMsg = "error while creating backend client"
func (sc *subcommandContext) listRoutes(filter *cfapi.IpRouteFilter) ([]*cfapi.DetailedRoute, error) {
client, err := sc.client()
if err != nil {
return nil, errors.Wrap(err, noClientMsg)
}
return client.ListRoutes(filter)
}
func (sc *subcommandContext) addRoute(newRoute cfapi.NewRoute) (cfapi.Route, error) {
client, err := sc.client()
if err != nil {
return cfapi.Route{}, errors.Wrap(err, noClientMsg)
}
return client.AddRoute(newRoute)
}
func (sc *subcommandContext) deleteRoute(params cfapi.DeleteRouteParams) error {
client, err := sc.client()
if err != nil {
return errors.Wrap(err, noClientMsg)
}
return client.DeleteRoute(params)
}
func (sc *subcommandContext) getRouteByIP(params cfapi.GetRouteByIpParams) (cfapi.DetailedRoute, error) {
client, err := sc.client()
if err != nil {
return cfapi.DetailedRoute{}, errors.Wrap(err, noClientMsg)
}
return client.GetByIP(params)
}
@@ -0,0 +1,324 @@
package tunnel
import (
"encoding/base64"
"flag"
"fmt"
"reflect"
"testing"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/connection"
)
type mockFileSystem struct {
rf func(string) ([]byte, error)
vfp func(string) bool
}
func (fs mockFileSystem) validFilePath(path string) bool {
return fs.vfp(path)
}
func (fs mockFileSystem) readFile(filePath string) ([]byte, error) {
return fs.rf(filePath)
}
func Test_subcommandContext_findCredentials(t *testing.T) {
type fields struct {
c *cli.Context
log *zerolog.Logger
isUIEnabled bool
fs fileSystem
tunnelstoreClient cfapi.Client
userCredential *userCredential
}
type args struct {
tunnelID uuid.UUID
}
oldCertPath := "old_cert.json"
newCertPath := "new_cert.json"
accountTag := "0000d4d14e84bd4ae5a6a02e0000ac63"
secret := []byte{211, 79, 177, 245, 179, 194, 152, 127, 140, 71, 18, 46, 183, 209, 10, 24, 192, 150, 55, 249, 211, 16, 167, 30, 113, 51, 152, 168, 72, 100, 205, 144}
secretB64 := base64.StdEncoding.EncodeToString(secret)
tunnelID := uuid.MustParse("df5ed608-b8b4-4109-89f3-9f2cf199df64")
name := "mytunnel"
fs := mockFileSystem{
rf: func(filePath string) ([]byte, error) {
if filePath == oldCertPath {
// An old credentials file created before TUN-3581 added the new fields
return []byte(fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s"}`, accountTag, secretB64)), nil
}
if filePath == newCertPath {
// A new credentials file created after TUN-3581 with its new fields.
return []byte(fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s","TunnelID":"%s","TunnelName":"%s"}`, accountTag, secretB64, tunnelID, name)), nil
}
return nil, errors.New("file not found")
},
vfp: func(string) bool { return true },
}
log := zerolog.Nop()
tests := []struct {
name string
fields fields
args args
want connection.Credentials
wantErr bool
}{
{
name: "Filepath given leads to old credentials file",
fields: fields{
log: &log,
fs: fs,
c: func() *cli.Context {
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
flagSet.String(CredFileFlag, oldCertPath, "")
c := cli.NewContext(cli.NewApp(), flagSet, nil)
_ = c.Set(CredFileFlag, oldCertPath)
return c
}(),
},
args: args{
tunnelID: tunnelID,
},
want: connection.Credentials{
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
},
},
{
name: "Filepath given leads to new credentials file",
fields: fields{
log: &log,
fs: fs,
c: func() *cli.Context {
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
flagSet.String(CredFileFlag, newCertPath, "")
c := cli.NewContext(cli.NewApp(), flagSet, nil)
_ = c.Set(CredFileFlag, newCertPath)
return c
}(),
},
args: args{
tunnelID: tunnelID,
},
want: connection.Credentials{
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
TunnelName: name,
},
},
{
name: "TUNNEL_CRED_CONTENTS given contains old credentials contents",
fields: fields{
log: &log,
fs: fs,
c: func() *cli.Context {
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
flagSet.String(CredContentsFlag, "", "")
c := cli.NewContext(cli.NewApp(), flagSet, nil)
_ = c.Set(CredContentsFlag, fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s"}`, accountTag, secretB64))
return c
}(),
},
args: args{
tunnelID: tunnelID,
},
want: connection.Credentials{
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
},
},
{
name: "TUNNEL_CRED_CONTENTS given contains new credentials contents",
fields: fields{
log: &log,
fs: fs,
c: func() *cli.Context {
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
flagSet.String(CredContentsFlag, "", "")
c := cli.NewContext(cli.NewApp(), flagSet, nil)
_ = c.Set(CredContentsFlag, fmt.Sprintf(`{"AccountTag":"%s","TunnelSecret":"%s","TunnelID":"%s","TunnelName":"%s"}`, accountTag, secretB64, tunnelID, name))
return c
}(),
},
args: args{
tunnelID: tunnelID,
},
want: connection.Credentials{
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
TunnelName: name,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &subcommandContext{
c: tt.fields.c,
log: tt.fields.log,
isUIEnabled: tt.fields.isUIEnabled,
fs: tt.fields.fs,
tunnelstoreClient: tt.fields.tunnelstoreClient,
userCredential: tt.fields.userCredential,
}
got, err := sc.findCredentials(tt.args.tunnelID)
if (err != nil) != tt.wantErr {
t.Errorf("subcommandContext.findCredentials() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("subcommandContext.findCredentials() = %v, want %v", got, tt.want)
}
})
}
}
type deleteMockTunnelStore struct {
cfapi.Client
mockTunnels map[uuid.UUID]mockTunnelBehaviour
deletedTunnelIDs []uuid.UUID
}
type mockTunnelBehaviour struct {
tunnel cfapi.Tunnel
deleteErr error
cleanupErr error
}
func newDeleteMockTunnelStore(tunnels ...mockTunnelBehaviour) *deleteMockTunnelStore {
mockTunnels := make(map[uuid.UUID]mockTunnelBehaviour)
for _, tunnel := range tunnels {
mockTunnels[tunnel.tunnel.ID] = tunnel
}
return &deleteMockTunnelStore{
mockTunnels: mockTunnels,
deletedTunnelIDs: make([]uuid.UUID, 0),
}
}
func (d *deleteMockTunnelStore) GetTunnel(tunnelID uuid.UUID) (*cfapi.Tunnel, error) {
tunnel, ok := d.mockTunnels[tunnelID]
if !ok {
return nil, fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
}
return &tunnel.tunnel, nil
}
func (d *deleteMockTunnelStore) DeleteTunnel(tunnelID uuid.UUID) error {
tunnel, ok := d.mockTunnels[tunnelID]
if !ok {
return fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
}
if tunnel.deleteErr != nil {
return tunnel.deleteErr
}
d.deletedTunnelIDs = append(d.deletedTunnelIDs, tunnelID)
tunnel.tunnel.DeletedAt = time.Now()
delete(d.mockTunnels, tunnelID)
return nil
}
func (d *deleteMockTunnelStore) CleanupConnections(tunnelID uuid.UUID, _ *cfapi.CleanupParams) error {
tunnel, ok := d.mockTunnels[tunnelID]
if !ok {
return fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
}
return tunnel.cleanupErr
}
func Test_subcommandContext_Delete(t *testing.T) {
type fields struct {
c *cli.Context
log *zerolog.Logger
isUIEnabled bool
fs fileSystem
tunnelstoreClient *deleteMockTunnelStore
userCredential *userCredential
}
type args struct {
tunnelIDs []uuid.UUID
}
newCertPath := "new_cert.json"
tunnelID1 := uuid.MustParse("df5ed608-b8b4-4109-89f3-9f2cf199df64")
tunnelID2 := uuid.MustParse("af5ed608-b8b4-4109-89f3-9f2cf199df64")
log := zerolog.Nop()
var tests = []struct {
name string
fields fields
args args
want []uuid.UUID
wantErr bool
}{
{
name: "clean up continues if credentials are not found",
fields: fields{
log: &log,
fs: mockFileSystem{
rf: func(filePath string) ([]byte, error) {
return nil, errors.New("file not found")
},
vfp: func(string) bool { return true },
},
c: func() *cli.Context {
flagSet := flag.NewFlagSet("test0", flag.PanicOnError)
flagSet.String(CredFileFlag, newCertPath, "")
c := cli.NewContext(cli.NewApp(), flagSet, nil)
_ = c.Set(CredFileFlag, newCertPath)
return c
}(),
tunnelstoreClient: newDeleteMockTunnelStore(
mockTunnelBehaviour{
tunnel: cfapi.Tunnel{ID: tunnelID1},
},
mockTunnelBehaviour{
tunnel: cfapi.Tunnel{ID: tunnelID2},
},
),
},
args: args{
tunnelIDs: []uuid.UUID{tunnelID1, tunnelID2},
},
want: []uuid.UUID{tunnelID1, tunnelID2},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &subcommandContext{
c: tt.fields.c,
log: tt.fields.log,
isUIEnabled: tt.fields.isUIEnabled,
fs: tt.fields.fs,
tunnelstoreClient: tt.fields.tunnelstoreClient,
userCredential: tt.fields.userCredential,
}
err := sc.delete(tt.args.tunnelIDs)
if (err != nil) != tt.wantErr {
t.Errorf("subcommandContext.findCredentials() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := tt.fields.tunnelstoreClient.deletedTunnelIDs
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("subcommandContext.findCredentials() = %v, want %v", got, tt.want)
return
}
})
}
}
@@ -0,0 +1,40 @@
package tunnel
import (
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/cfapi"
)
func (sc *subcommandContext) addVirtualNetwork(newVnet cfapi.NewVirtualNetwork) (cfapi.VirtualNetwork, error) {
client, err := sc.client()
if err != nil {
return cfapi.VirtualNetwork{}, errors.Wrap(err, noClientMsg)
}
return client.CreateVirtualNetwork(newVnet)
}
func (sc *subcommandContext) listVirtualNetworks(filter *cfapi.VnetFilter) ([]*cfapi.VirtualNetwork, error) {
client, err := sc.client()
if err != nil {
return nil, errors.Wrap(err, noClientMsg)
}
return client.ListVirtualNetworks(filter)
}
func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID) error {
client, err := sc.client()
if err != nil {
return errors.Wrap(err, noClientMsg)
}
return client.DeleteVirtualNetwork(vnetId)
}
func (sc *subcommandContext) updateVirtualNetwork(vnetId uuid.UUID, updates cfapi.UpdateVirtualNetwork) error {
client, err := sc.client()
if err != nil {
return errors.Wrap(err, noClientMsg)
}
return client.UpdateVirtualNetwork(vnetId, updates)
}
+862
View File
@@ -0,0 +1,862 @@
package tunnel
import (
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"golang.org/x/net/idna"
yaml "gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
)
const (
allSortByOptions = "name, id, createdAt, deletedAt, numConnections"
connsSortByOptions = "id, startedAt, numConnections, version"
CredFileFlagAlias = "cred-file"
CredFileFlag = "credentials-file"
CredContentsFlag = "credentials-contents"
overwriteDNSFlagName = "overwrite-dns"
LogFieldTunnelID = "tunnelID"
)
var (
showDeletedFlag = &cli.BoolFlag{
Name: "show-deleted",
Aliases: []string{"d"},
Usage: "Include deleted tunnels in the list",
}
listNameFlag = &cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "List tunnels with the given `NAME`",
}
listNamePrefixFlag = &cli.StringFlag{
Name: "name-prefix",
Aliases: []string{"np"},
Usage: "List tunnels that start with the give `NAME` prefix",
}
listExcludeNamePrefixFlag = &cli.StringFlag{
Name: "exclude-name-prefix",
Aliases: []string{"enp"},
Usage: "List tunnels whose `NAME` does not start with the given prefix",
}
listExistedAtFlag = &cli.TimestampFlag{
Name: "when",
Aliases: []string{"w"},
Usage: "List tunnels that are active at the given `TIME` in RFC3339 format",
Layout: cfapi.TimeLayout,
DefaultText: fmt.Sprintf("current time, %s", time.Now().Format(cfapi.TimeLayout)),
}
listIDFlag = &cli.StringFlag{
Name: "id",
Aliases: []string{"i"},
Usage: "List tunnel by `ID`",
}
showRecentlyDisconnected = &cli.BoolFlag{
Name: "show-recently-disconnected",
Aliases: []string{"rd"},
Usage: "Include connections that have recently disconnected in the list",
}
outputFormatFlag = &cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'",
}
sortByFlag = &cli.StringFlag{
Name: "sort-by",
Value: "name",
Usage: fmt.Sprintf("Sorts the list of tunnels by the given field. Valid options are {%s}", allSortByOptions),
EnvVars: []string{"TUNNEL_LIST_SORT_BY"},
}
invertSortFlag = &cli.BoolFlag{
Name: "invert-sort",
Usage: "Inverts the sort order of the tunnel list.",
EnvVars: []string{"TUNNEL_LIST_INVERT_SORT"},
}
forceFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "By default, if a tunnel is currently being run from a cloudflared, you can't " +
"simultaneously rerun it again from a second cloudflared. The --force flag lets you " +
"overwrite the previous tunnel. If you want to use a single hostname with multiple " +
"tunnels, you can do so with Cloudflare's Load Balancer product.",
})
featuresFlag = altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: "features",
Aliases: []string{"F"},
Usage: "Opt into various features that are still being developed or tested.",
})
credentialsFileFlagCLIOnly = &cli.StringFlag{
Name: CredFileFlag,
Aliases: []string{CredFileFlagAlias},
Usage: "Filepath at which to read/write the tunnel credentials",
EnvVars: []string{"TUNNEL_CRED_FILE"},
}
credentialsFileFlag = altsrc.NewStringFlag(credentialsFileFlagCLIOnly)
credentialsContentsFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: CredContentsFlag,
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"},
})
forceDeleteFlag = &cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "Cleans up any stale connections before the tunnel is deleted. cloudflared will not " +
"delete a tunnel with connections without this flag.",
EnvVars: []string{"TUNNEL_RUN_FORCE_OVERWRITE"},
}
selectProtocolFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: "protocol",
Value: "auto",
Aliases: []string{"p"},
Usage: fmt.Sprintf("Protocol implementation to connect with Cloudflare's edge network. %s", connection.AvailableProtocolFlagMessage),
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
Hidden: true,
})
sortInfoByFlag = &cli.StringFlag{
Name: "sort-by",
Value: "createdAt",
Usage: fmt.Sprintf("Sorts the list of connections of a tunnel by the given field. Valid options are {%s}", connsSortByOptions),
EnvVars: []string{"TUNNEL_INFO_SORT_BY"},
}
invertInfoSortFlag = &cli.BoolFlag{
Name: "invert-sort",
Usage: "Inverts the sort order of the tunnel info.",
EnvVars: []string{"TUNNEL_INFO_INVERT_SORT"},
}
cleanupClientFlag = &cli.StringFlag{
Name: "connector-id",
Aliases: []string{"c"},
Usage: `Constraints the cleanup to stop the connections of a single Connector (by its ID). You can find the various Connectors (and their IDs) currently connected to your tunnel via 'cloudflared tunnel info <name>'.`,
EnvVars: []string{"TUNNEL_CLEANUP_CONNECTOR"},
}
overwriteDNSFlag = &cli.BoolFlag{
Name: overwriteDNSFlagName,
Aliases: []string{"f"},
Usage: `Overwrites existing DNS records with this hostname`,
EnvVars: []string{"TUNNEL_FORCE_PROVISIONING_DNS"},
}
createSecretFlag = &cli.StringFlag{
Name: "secret",
Aliases: []string{"s"},
Usage: "Base64 encoded secret to set for the tunnel. The decoded secret must be at least 32 bytes long. If not specified, a random 32-byte secret will be generated.",
EnvVars: []string{"TUNNEL_CREATE_SECRET"},
}
)
func buildCreateCommand() *cli.Command {
return &cli.Command{
Name: "create",
Action: cliutil.ConfiguredAction(createCommand),
Usage: "Create a new tunnel with given name",
UsageText: "cloudflared tunnel [tunnel command options] create [subcommand options] NAME",
Description: `Creates a tunnel, registers it with Cloudflare edge and generates credential file used to run this tunnel.
Use "cloudflared tunnel route" subcommand to map a DNS name to this tunnel and "cloudflared tunnel run" to start the connection.
For example, to create a tunnel named 'my-tunnel' run:
$ cloudflared tunnel create my-tunnel`,
Flags: []cli.Flag{outputFormatFlag, credentialsFileFlagCLIOnly, createSecretFlag},
CustomHelpTemplate: commandHelpTemplate(),
}
}
// generateTunnelSecret as an array of 32 bytes using secure random number generator
func generateTunnelSecret() ([]byte, error) {
randomBytes := make([]byte, 32)
_, err := rand.Read(randomBytes)
return randomBytes, err
}
func createCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return errors.Wrap(err, "error setting up logger")
}
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel create" requires exactly 1 argument, the name of tunnel to create.`)
}
name := c.Args().First()
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
_, err = sc.create(name, c.String(CredFileFlag), c.String(createSecretFlag.Name))
return errors.Wrap(err, "failed to create tunnel")
}
func tunnelFilePath(tunnelID uuid.UUID, directory string) (string, error) {
fileName := fmt.Sprintf("%v.json", tunnelID)
filePath := filepath.Clean(fmt.Sprintf("%s/%s", directory, fileName))
return homedir.Expand(filePath)
}
// writeTunnelCredentials saves `credentials` as a JSON into `filePath`, only if
// the file does not exist already
func writeTunnelCredentials(filePath string, credentials *connection.Credentials) error {
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
if err == nil {
return fmt.Errorf("%s already exists", filePath)
}
return err
}
body, err := json.Marshal(credentials)
if err != nil {
return errors.Wrap(err, "Unable to marshal tunnel credentials to JSON")
}
return ioutil.WriteFile(filePath, body, 400)
}
func buildListCommand() *cli.Command {
return &cli.Command{
Name: "list",
Action: cliutil.ConfiguredAction(listCommand),
Usage: "List existing tunnels",
UsageText: "cloudflared tunnel [tunnel command options] list [subcommand options]",
Description: "cloudflared tunnel list will display all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list",
Flags: []cli.Flag{
outputFormatFlag,
showDeletedFlag,
listNameFlag,
listNamePrefixFlag,
listExcludeNamePrefixFlag,
listExistedAtFlag,
listIDFlag,
showRecentlyDisconnected,
sortByFlag,
invertSortFlag,
},
CustomHelpTemplate: commandHelpTemplate(),
}
}
func listCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
filter := cfapi.NewTunnelFilter()
if !c.Bool("show-deleted") {
filter.NoDeleted()
}
if name := c.String("name"); name != "" {
filter.ByName(name)
}
if namePrefix := c.String("name-prefix"); namePrefix != "" {
filter.ByNamePrefix(namePrefix)
}
if excludePrefix := c.String("exclude-name-prefix"); excludePrefix != "" {
filter.ExcludeNameWithPrefix(excludePrefix)
}
if existedAt := c.Timestamp("time"); existedAt != nil {
filter.ByExistedAt(*existedAt)
}
if id := c.String("id"); id != "" {
tunnelID, err := uuid.Parse(id)
if err != nil {
return errors.Wrapf(err, "%s is not a valid tunnel ID", id)
}
filter.ByTunnelID(tunnelID)
}
if maxFetch := c.Int("max-fetch-size"); maxFetch > 0 {
filter.MaxFetchSize(uint(maxFetch))
}
tunnels, err := sc.list(filter)
if err != nil {
return err
}
// Sort the tunnels
sortBy := c.String("sort-by")
invalidSortField := false
sort.Slice(tunnels, func(i, j int) bool {
cmp := func() bool {
switch sortBy {
case "name":
return tunnels[i].Name < tunnels[j].Name
case "id":
return tunnels[i].ID.String() < tunnels[j].ID.String()
case "createdAt":
return tunnels[i].CreatedAt.Unix() < tunnels[j].CreatedAt.Unix()
case "deletedAt":
return tunnels[i].DeletedAt.Unix() < tunnels[j].DeletedAt.Unix()
case "numConnections":
return len(tunnels[i].Connections) < len(tunnels[j].Connections)
default:
invalidSortField = true
return tunnels[i].Name < tunnels[j].Name
}
}()
if c.Bool("invert-sort") {
return !cmp
}
return cmp
})
if invalidSortField {
sc.log.Error().Msgf("%s is not a valid sort field. Valid sort fields are %s. Defaulting to 'name'.", sortBy, allSortByOptions)
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, tunnels)
}
if len(tunnels) > 0 {
formatAndPrintTunnelList(tunnels, c.Bool("show-recently-disconnected"))
} else {
fmt.Println("No tunnels were found for the given filter flags. You can use 'cloudflared tunnel create' to create a tunnel.")
}
return nil
}
func formatAndPrintTunnelList(tunnels []*cfapi.Tunnel, showRecentlyDisconnected bool) {
writer := tabWriter()
defer writer.Flush()
_, _ = fmt.Fprintln(writer, "You can obtain more detailed information for each tunnel with `cloudflared tunnel info <name/uuid>`")
// Print column headers with tabbed columns
_, _ = fmt.Fprintln(writer, "ID\tNAME\tCREATED\tCONNECTIONS\t")
// Loop through tunnels, create formatted string for each, and print using tabwriter
for _, t := range tunnels {
formattedStr := fmt.Sprintf(
"%s\t%s\t%s\t%s\t",
t.ID,
t.Name,
t.CreatedAt.Format(time.RFC3339),
fmtConnections(t.Connections, showRecentlyDisconnected),
)
_, _ = fmt.Fprintln(writer, formattedStr)
}
}
func fmtConnections(connections []cfapi.Connection, showRecentlyDisconnected bool) string {
// Count connections per colo
numConnsPerColo := make(map[string]uint, len(connections))
for _, connection := range connections {
if !connection.IsPendingReconnect || showRecentlyDisconnected {
numConnsPerColo[connection.ColoName]++
}
}
// Get sorted list of colos
sortedColos := []string{}
for coloName := range numConnsPerColo {
sortedColos = append(sortedColos, coloName)
}
sort.Strings(sortedColos)
// Map each colo to its frequency, combine into output string.
var output []string
for _, coloName := range sortedColos {
output = append(output, fmt.Sprintf("%dx%s", numConnsPerColo[coloName], coloName))
}
return strings.Join(output, ", ")
}
func buildInfoCommand() *cli.Command {
return &cli.Command{
Name: "info",
Action: cliutil.ConfiguredAction(tunnelInfo),
Usage: "List details about the active connectors for a tunnel",
UsageText: "cloudflared tunnel [tunnel command options] info [subcommand options] [TUNNEL]",
Description: "cloudflared tunnel info displays details about the active connectors for a given tunnel (identified by name or uuid).",
Flags: []cli.Flag{
outputFormatFlag,
showRecentlyDisconnected,
sortInfoByFlag,
invertInfoSortFlag,
},
CustomHelpTemplate: commandHelpTemplate(),
}
}
func tunnelInfo(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel info" accepts exactly one argument, the ID or name of the tunnel to get info about.`)
}
tunnelID, err := sc.findID(c.Args().First())
if err != nil {
return errors.Wrap(err, "error parsing tunnel ID")
}
client, err := sc.client()
if err != nil {
return err
}
clients, err := client.ListActiveClients(tunnelID)
if err != nil {
return err
}
sortBy := c.String("sort-by")
invalidSortField := false
sort.Slice(clients, func(i, j int) bool {
cmp := func() bool {
switch sortBy {
case "id":
return clients[i].ID.String() < clients[j].ID.String()
case "createdAt":
return clients[i].RunAt.Unix() < clients[j].RunAt.Unix()
case "numConnections":
return len(clients[i].Connections) < len(clients[j].Connections)
case "version":
return clients[i].Version < clients[j].Version
default:
invalidSortField = true
return clients[i].RunAt.Unix() < clients[j].RunAt.Unix()
}
}()
if c.Bool("invert-sort") {
return !cmp
}
return cmp
})
if invalidSortField {
sc.log.Error().Msgf("%s is not a valid sort field. Valid sort fields are %s. Defaulting to 'name'.", sortBy, connsSortByOptions)
}
tunnel, err := getTunnel(sc, tunnelID)
if err != nil {
return err
}
info := Info{
tunnel.ID,
tunnel.Name,
tunnel.CreatedAt,
clients,
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, info)
}
if len(clients) > 0 {
formatAndPrintConnectionsList(info, c.Bool("show-recently-disconnected"))
} else {
fmt.Printf("Your tunnel %s does not have any active connection.\n", tunnelID)
}
return nil
}
func getTunnel(sc *subcommandContext, tunnelID uuid.UUID) (*cfapi.Tunnel, error) {
filter := cfapi.NewTunnelFilter()
filter.ByTunnelID(tunnelID)
tunnels, err := sc.list(filter)
if err != nil {
return nil, err
}
if len(tunnels) != 1 {
return nil, errors.Errorf("Expected to find a single tunnel with uuid %v but found %d tunnels.", tunnelID, len(tunnels))
}
return tunnels[0], nil
}
func formatAndPrintConnectionsList(tunnelInfo Info, showRecentlyDisconnected bool) {
writer := tabWriter()
defer writer.Flush()
// Print the general tunnel info table
_, _ = fmt.Fprintf(writer, "NAME: %s\nID: %s\nCREATED: %s\n\n", tunnelInfo.Name, tunnelInfo.ID, tunnelInfo.CreatedAt)
// Determine whether to print the connector table
shouldDisplayTable := false
for _, c := range tunnelInfo.Connectors {
conns := fmtConnections(c.Connections, showRecentlyDisconnected)
if len(conns) > 0 {
shouldDisplayTable = true
}
}
if !shouldDisplayTable {
fmt.Println("This tunnel has no active connectors.")
return
}
// Print the connector table
_, _ = fmt.Fprintln(writer, "CONNECTOR ID\tCREATED\tARCHITECTURE\tVERSION\tORIGIN IP\tEDGE\t")
for _, c := range tunnelInfo.Connectors {
conns := fmtConnections(c.Connections, showRecentlyDisconnected)
if len(conns) == 0 {
continue
}
originIp := c.Connections[0].OriginIP.String()
formattedStr := fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%s\t",
c.ID,
c.RunAt.Format(time.RFC3339),
c.Arch,
c.Version,
originIp,
conns,
)
_, _ = fmt.Fprintln(writer, formattedStr)
}
}
func tabWriter() *tabwriter.Writer {
const (
minWidth = 0
tabWidth = 8
padding = 1
padChar = ' '
flags = 0
)
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
return writer
}
func buildDeleteCommand() *cli.Command {
return &cli.Command{
Name: "delete",
Action: cliutil.ConfiguredAction(deleteCommand),
Usage: "Delete existing tunnel by UUID or name",
UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL",
Description: "cloudflared tunnel delete will delete tunnels with the given tunnel UUIDs or names. A tunnel cannot be deleted if it has active connections. To delete the tunnel unconditionally, use -f flag.",
Flags: []cli.Flag{credentialsFileFlagCLIOnly, forceDeleteFlag},
CustomHelpTemplate: commandHelpTemplate(),
}
}
func deleteCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() < 1 {
return cliutil.UsageError(`"cloudflared tunnel delete" requires at least 1 argument, the ID or name of the tunnel to delete.`)
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
tunnelIDs, err := sc.findIDs(c.Args().Slice())
if err != nil {
return err
}
return sc.delete(tunnelIDs)
}
func renderOutput(format string, v interface{}) error {
switch format {
case "json":
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(v)
case "yaml":
return yaml.NewEncoder(os.Stdout).Encode(v)
default:
return errors.Errorf("Unknown output format '%s'", format)
}
}
func buildRunCommand() *cli.Command {
flags := []cli.Flag{
forceFlag,
credentialsFileFlag,
credentialsContentsFlag,
selectProtocolFlag,
featuresFlag,
}
flags = append(flags, configureProxyFlags(false)...)
return &cli.Command{
Name: "run",
Action: cliutil.ConfiguredAction(runCommand),
Usage: "Proxy a local web server by running the given tunnel",
UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] [TUNNEL]",
Description: `Runs the tunnel identified by name or UUUD, creating highly available connections
between your server and the Cloudflare edge. You can provide name or UUID of tunnel to run either as the
last command line argument or in the configuration file using "tunnel: TUNNEL".
This command requires the tunnel credentials file created when "cloudflared tunnel create" was run,
however it does not need access to cert.pem from "cloudflared login" if you identify the tunnel by UUID.
If you experience other problems running the tunnel, "cloudflared tunnel cleanup" may help by removing
any old connection records.
`,
Flags: flags,
CustomHelpTemplate: commandHelpTemplate(),
}
}
func runCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
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 " +
"in the property `tunnel` to run. Make sure to provision the routing (e.g. via `cloudflared tunnel route dns/lb`) or else " +
"your origin will not be reachable. You should remove the `hostname` property to avoid this warning.")
}
return runNamedTunnel(sc, tunnelRef)
}
func runNamedTunnel(sc *subcommandContext, tunnelRef string) error {
tunnelID, err := sc.findID(tunnelRef)
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)
}
func buildCleanupCommand() *cli.Command {
return &cli.Command{
Name: "cleanup",
Action: cliutil.ConfiguredAction(cleanupCommand),
Usage: "Cleanup tunnel connections",
UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options] TUNNEL",
Description: "Delete connections for tunnels with the given UUIDs or names.",
Flags: []cli.Flag{cleanupClientFlag},
CustomHelpTemplate: commandHelpTemplate(),
}
}
func cleanupCommand(c *cli.Context) error {
if c.NArg() < 1 {
return cliutil.UsageError(`"cloudflared tunnel cleanup" requires at least 1 argument, the IDs of the tunnels to cleanup connections.`)
}
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
tunnelIDs, err := sc.findIDs(c.Args().Slice())
if err != nil {
return err
}
return sc.cleanupConnections(tunnelIDs)
}
func buildRouteCommand() *cli.Command {
return &cli.Command{
Name: "route",
Usage: "Define which traffic routed from Cloudflare edge to this tunnel: requests to a DNS hostname, to a Cloudflare Load Balancer, or traffic originating from Cloudflare WARP clients",
UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options] [dns TUNNEL HOSTNAME]|[lb TUNNEL HOSTNAME LB-POOL]|[ip NETWORK TUNNEL]",
Description: `The route command defines how Cloudflare will proxy requests to this tunnel.
To route a hostname by creating a DNS CNAME record to a tunnel:
cloudflared tunnel route dns <tunnel ID or name> <hostname>
You can read more at: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns
To use this tunnel as a load balancer origin, creating pool and load balancer if necessary:
cloudflared tunnel route lb <tunnel ID or name> <hostname> <load balancer pool>
You can read more at: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb
For Cloudflare WARP traffic to be routed to your private network, reachable from this tunnel as origins, use:
cloudflared tunnel route ip <network CIDR> <tunnel ID or name>
Further information about managing Cloudflare WARP traffic to your tunnel is available at:
cloudflared tunnel route ip --help
`,
CustomHelpTemplate: commandHelpTemplate(),
Subcommands: []*cli.Command{
{
Name: "dns",
Action: cliutil.ConfiguredAction(routeDnsCommand),
Usage: "HostnameRoute a hostname by creating a DNS CNAME record to a tunnel",
UsageText: "cloudflared tunnel route dns [TUNNEL] [HOSTNAME]",
Description: `Creates a DNS CNAME record hostname that points to the tunnel.`,
Flags: []cli.Flag{overwriteDNSFlag},
},
{
Name: "lb",
Action: cliutil.ConfiguredAction(routeLbCommand),
Usage: "Use this tunnel as a load balancer origin, creating pool and load balancer if necessary",
UsageText: "cloudflared tunnel route dns [TUNNEL] [HOSTNAME] [LB-POOL]",
Description: `Creates Load Balancer with an origin pool that points to the tunnel.`,
},
buildRouteIPSubcommand(),
},
}
}
func dnsRouteFromArg(c *cli.Context, overwriteExisting bool) (cfapi.HostnameRoute, error) {
const (
userHostnameIndex = 1
expectedNArgs = 2
)
if c.NArg() != expectedNArgs {
return nil, cliutil.UsageError("Expected %d arguments, got %d", expectedNArgs, c.NArg())
}
userHostname := c.Args().Get(userHostnameIndex)
if userHostname == "" {
return nil, cliutil.UsageError("The third argument should be the hostname")
} else if !validateHostname(userHostname, true) {
return nil, errors.Errorf("%s is not a valid hostname", userHostname)
}
return cfapi.NewDNSRoute(userHostname, overwriteExisting), nil
}
func lbRouteFromArg(c *cli.Context) (cfapi.HostnameRoute, error) {
const (
lbNameIndex = 1
lbPoolIndex = 2
expectedNArgs = 3
)
if c.NArg() != expectedNArgs {
return nil, cliutil.UsageError("Expected %d arguments, got %d", expectedNArgs, c.NArg())
}
lbName := c.Args().Get(lbNameIndex)
if lbName == "" {
return nil, cliutil.UsageError("The third argument should be the load balancer name")
} else if !validateHostname(lbName, true) {
return nil, errors.Errorf("%s is not a valid load balancer name", lbName)
}
lbPool := c.Args().Get(lbPoolIndex)
if lbPool == "" {
return nil, cliutil.UsageError("The fourth argument should be the pool name")
} else if !validateName(lbPool, false) {
return nil, errors.Errorf("%s is not a valid pool name", lbPool)
}
return cfapi.NewLBRoute(lbName, lbPool), nil
}
var nameRegex = regexp.MustCompile("^[_a-zA-Z0-9][-_.a-zA-Z0-9]*$")
var hostNameRegex = regexp.MustCompile("^[*_a-zA-Z0-9][-_.a-zA-Z0-9]*$")
func validateName(s string, allowWildcardSubdomain bool) bool {
if allowWildcardSubdomain {
return hostNameRegex.MatchString(s)
}
return nameRegex.MatchString(s)
}
func validateHostname(s string, allowWildcardSubdomain bool) bool {
// Slightly stricter than PunyCodeProfile
idnaProfile := idna.New(
idna.ValidateLabels(true),
idna.VerifyDNSLength(true))
puny, err := idnaProfile.ToASCII(s)
return err == nil && validateName(puny, allowWildcardSubdomain)
}
func routeDnsCommand(c *cli.Context) error {
if c.NArg() != 2 {
return cliutil.UsageError(`This command expects the format "cloudflared tunnel route dns <tunnel name/id> <hostname>"`)
}
return routeCommand(c, "dns")
}
func routeLbCommand(c *cli.Context) error {
if c.NArg() != 3 {
return cliutil.UsageError(`This command expects the format "cloudflared tunnel route lb <tunnel name/id> <hostname> <load balancer pool>"`)
}
return routeCommand(c, "lb")
}
func routeCommand(c *cli.Context, routeType string) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
tunnelID, err := sc.findID(c.Args().Get(0))
if err != nil {
return err
}
var route cfapi.HostnameRoute
switch routeType {
case "dns":
route, err = dnsRouteFromArg(c, c.Bool(overwriteDNSFlagName))
case "lb":
route, err = lbRouteFromArg(c)
}
if err != nil {
return err
}
res, err := sc.route(tunnelID, route)
if err != nil {
return err
}
sc.log.Info().Str(LogFieldTunnelID, tunnelID.String()).Msg(res.SuccessSummary())
return nil
}
func commandHelpTemplate() string {
var parentFlagsHelp string
for _, f := range configureCloudflaredFlags(false) {
parentFlagsHelp += fmt.Sprintf(" %s\n\t", f)
}
for _, f := range configureLoggingFlags(false) {
parentFlagsHelp += fmt.Sprintf(" %s\n\t", f)
}
const template = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.UsageText}}
DESCRIPTION:
{{.Description}}
TUNNEL COMMAND OPTIONS:
%s
SUBCOMMAND OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}
`
return fmt.Sprintf(template, parentFlagsHelp)
}
+179
View File
@@ -0,0 +1,179 @@
package tunnel
import (
"path/filepath"
"testing"
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/cfapi"
)
func Test_fmtConnections(t *testing.T) {
type args struct {
connections []cfapi.Connection
}
tests := []struct {
name string
args args
want string
}{
{
name: "empty",
args: args{
connections: []cfapi.Connection{},
},
want: "",
},
{
name: "trivial",
args: args{
connections: []cfapi.Connection{
{
ColoName: "DFW",
ID: uuid.MustParse("ea550130-57fd-4463-aab1-752822231ddd"),
},
},
},
want: "1xDFW",
},
{
name: "with a pending reconnect",
args: args{
connections: []cfapi.Connection{
{
ColoName: "DFW",
ID: uuid.MustParse("ea550130-57fd-4463-aab1-752822231ddd"),
IsPendingReconnect: true,
},
},
},
want: "",
},
{
name: "many colos",
args: args{
connections: []cfapi.Connection{
{
ColoName: "YRV",
ID: uuid.MustParse("ea550130-57fd-4463-aab1-752822231ddd"),
},
{
ColoName: "DFW",
ID: uuid.MustParse("c13c0b3b-0fbf-453c-8169-a1990fced6d0"),
},
{
ColoName: "ATL",
ID: uuid.MustParse("70c90639-e386-4e8d-9a4e-7f046d70e63f"),
},
{
ColoName: "DFW",
ID: uuid.MustParse("30ad6251-0305-4635-a670-d3994f474981"),
},
},
},
want: "1xATL, 2xDFW, 1xYRV",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := fmtConnections(tt.args.connections, false); got != tt.want {
t.Errorf("fmtConnections() = %v, want %v", got, tt.want)
}
})
}
}
func TestTunnelfilePath(t *testing.T) {
tunnelID, err := uuid.Parse("f48d8918-bc23-4647-9d48-082c5b76de65")
assert.NoError(t, err)
originCertDir := filepath.Dir("~/.cloudflared/cert.pem")
actual, err := tunnelFilePath(tunnelID, originCertDir)
assert.NoError(t, err)
homeDir, err := homedir.Dir()
assert.NoError(t, err)
expected := filepath.Join(homeDir, ".cloudflared", tunnelID.String()+".json")
assert.Equal(t, expected, actual)
}
func TestValidateName(t *testing.T) {
tests := []struct {
name string
want bool
}{
{name: "", want: false},
{name: "-", want: false},
{name: ".", want: false},
{name: "a b", want: false},
{name: "a+b", want: false},
{name: "-ab", want: false},
{name: "ab", want: true},
{name: "ab-c", want: true},
{name: "abc.def", want: true},
{name: "_ab_c.-d-ef", want: true},
}
for _, tt := range tests {
if got := validateName(tt.name, false); got != tt.want {
t.Errorf("validateName() = %v, want %v", got, tt.want)
}
}
}
func Test_validateHostname(t *testing.T) {
type args struct {
s string
allowWildcardSubdomain bool
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Normal",
args: args{
s: "example.com",
allowWildcardSubdomain: true,
},
want: true,
},
{
name: "wildcard subdomain for TUN-358",
args: args{
s: "*.ehrig.io",
allowWildcardSubdomain: true,
},
want: true,
},
{
name: "Misplaced wildcard",
args: args{
s: "subdomain.*.ehrig.io",
allowWildcardSubdomain: true,
},
},
{
name: "Invalid domain",
args: args{
s: "..",
allowWildcardSubdomain: true,
},
},
{
name: "Invalid domain",
args: args{
s: "..",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := validateHostname(tt.args.s, tt.args.allowWildcardSubdomain); got != tt.want {
t.Errorf("validateHostname() = %v, want %v", got, tt.want)
}
})
}
}
@@ -0,0 +1,279 @@
package tunnel
import (
"fmt"
"net"
"os"
"text/tabwriter"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
)
var (
vnetFlag = &cli.StringFlag{
Name: "virtual-network",
Aliases: []string{"vn"},
Usage: "The ID or name of the virtual network to which the route is associated to.",
}
)
func buildRouteIPSubcommand() *cli.Command {
return &cli.Command{
Name: "ip",
Usage: "Configure and query Cloudflare WARP routing to private IP networks made available through Cloudflare Tunnels.",
UsageText: "cloudflared tunnel [--config FILEPATH] route COMMAND [arguments...]",
Description: `cloudflared can provision routes for any IP space in your corporate network. Users enrolled in
your Cloudflare for Teams organization can reach those IPs through the Cloudflare WARP
client. You can then configure L7/L4 filtering on https://dash.teams.cloudflare.com to
determine who can reach certain routes.
By default IP routes all exist within a single virtual network. If you use the same IP
space(s) in different physical private networks, all meant to be reachable via IP routes,
then you have to manage the ambiguous IP routes by associating them to virtual networks.
See "cloudflared tunnel network --help" for more information.`,
Subcommands: []*cli.Command{
{
Name: "add",
Action: cliutil.ConfiguredAction(addRouteCommand),
Usage: "Add a new network to the routing table reachable via a Tunnel",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip add [flags] [CIDR] [TUNNEL] [COMMENT?]",
Description: `Adds a network IP route space (represented as a CIDR) to your routing table.
That network IP space becomes reachable for requests egressing from a user's machine
as long as it is using Cloudflare WARP client and is enrolled in the same account
that is running the Tunnel chosen here. Further, those requests will be proxied to
the specified Tunnel, and reach an IP in the given CIDR, as long as that IP is
reachable from cloudflared.
If the CIDR exists in more than one private network, to be connected with Cloudflare
Tunnels, then you have to manage those IP routes with virtual networks (see
"cloudflared tunnel network --help)". In those cases, you then have to tell
which virtual network's routing table you want to add the route to with:
"cloudflared tunnel route ip add --virtual-network [ID/name] [CIDR] [TUNNEL]".`,
Flags: []cli.Flag{vnetFlag},
},
{
Name: "show",
Aliases: []string{"list"},
Action: cliutil.ConfiguredAction(showRoutesCommand),
Usage: "Show the routing table",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip show [flags]",
Description: `Shows your organization private routing table. You can use flags to filter the results.`,
Flags: showRoutesFlags(),
},
{
Name: "delete",
Action: cliutil.ConfiguredAction(deleteRouteCommand),
Usage: "Delete a row from your organization's private routing table",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip delete [flags] [CIDR]",
Description: `Deletes the row for a given CIDR from your routing table. That portion of your network
will no longer be reachable by the WARP clients. Note that if you use virtual
networks, then you have to tell which virtual network whose routing table you
have a row deleted from.`,
Flags: []cli.Flag{vnetFlag},
},
{
Name: "get",
Action: cliutil.ConfiguredAction(getRouteByIPCommand),
Usage: "Check which row of the routing table matches a given IP.",
UsageText: "cloudflared tunnel [--config FILEPATH] route ip get [flags] [IP]",
Description: `Checks which row of the routing table will be used to proxy a given IP. This helps check
and validate your config. Note that if you use virtual networks, then you have
to tell which virtual network whose routing table you want to use.`,
Flags: []cli.Flag{vnetFlag},
},
},
}
}
func showRoutesFlags() []cli.Flag {
flags := make([]cli.Flag, 0)
flags = append(flags, cfapi.IpRouteFilterFlags...)
flags = append(flags, outputFormatFlag)
return flags
}
func showRoutesCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
filter, err := cfapi.NewIpRouteFilterFromCLI(c)
if err != nil {
return errors.Wrap(err, "invalid config for routing filters")
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
routes, err := sc.listRoutes(filter)
if err != nil {
return err
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, routes)
}
if len(routes) > 0 {
formatAndPrintRouteList(routes)
} else {
fmt.Println("No routes were found for the given filter flags. You can use 'cloudflared tunnel route ip add' to add a route.")
}
return nil
}
func addRouteCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() < 2 {
return errors.New("You must supply at least 2 arguments, first the network you wish to route (in CIDR form e.g. 1.2.3.4/32) and then the tunnel ID to proxy with")
}
args := c.Args()
_, network, err := net.ParseCIDR(args.Get(0))
if err != nil {
return errors.Wrap(err, "Invalid network CIDR")
}
if network == nil {
return errors.New("Invalid network CIDR")
}
tunnelRef := args.Get(1)
tunnelID, err := sc.findID(tunnelRef)
if err != nil {
return errors.Wrap(err, "Invalid tunnel")
}
comment := ""
if c.NArg() >= 3 {
comment = args.Get(2)
}
var vnetId *uuid.UUID
if c.IsSet(vnetFlag.Name) {
id, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
vnetId = &id
}
_, err = sc.addRoute(cfapi.NewRoute{
Comment: comment,
Network: *network,
TunnelID: tunnelID,
VNetID: vnetId,
})
if err != nil {
return errors.Wrap(err, "API error")
}
fmt.Printf("Successfully added route for %s over tunnel %s\n", network, tunnelID)
return nil
}
func deleteRouteCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() != 1 {
return errors.New("You must supply exactly one argument, the network whose route you want to delete (in CIDR form e.g. 1.2.3.4/32)")
}
_, network, err := net.ParseCIDR(c.Args().First())
if err != nil {
return errors.Wrap(err, "Invalid network CIDR")
}
if network == nil {
return errors.New("Invalid network CIDR")
}
params := cfapi.DeleteRouteParams{
Network: *network,
}
if c.IsSet(vnetFlag.Name) {
vnetId, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
params.VNetID = &vnetId
}
if err := sc.deleteRoute(params); err != nil {
return errors.Wrap(err, "API error")
}
fmt.Printf("Successfully deleted route for %s\n", network)
return nil
}
func getRouteByIPCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() != 1 {
return errors.New("You must supply exactly one argument, an IP whose route will be queried (e.g. 1.2.3.4 or 2001:0db8:::7334)")
}
ipInput := c.Args().First()
ip := net.ParseIP(ipInput)
if ip == nil {
return fmt.Errorf("Invalid IP %s", ipInput)
}
params := cfapi.GetRouteByIpParams{
Ip: ip,
}
if c.IsSet(vnetFlag.Name) {
vnetId, err := getVnetId(sc, c.String(vnetFlag.Name))
if err != nil {
return err
}
params.VNetID = &vnetId
}
route, err := sc.getRouteByIP(params)
if err != nil {
return errors.Wrap(err, "API error")
}
if route.IsZero() {
fmt.Printf("No route matches the IP %s\n", ip)
} else {
formatAndPrintRouteList([]*cfapi.DetailedRoute{&route})
}
return nil
}
func formatAndPrintRouteList(routes []*cfapi.DetailedRoute) {
const (
minWidth = 0
tabWidth = 8
padding = 1
padChar = ' '
flags = 0
)
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
defer writer.Flush()
// Print column headers with tabbed columns
_, _ = fmt.Fprintln(writer, "NETWORK\tVIRTUAL NET ID\tCOMMENT\tTUNNEL ID\tTUNNEL NAME\tCREATED\tDELETED\t")
// Loop through routes, create formatted string for each, and print using tabwriter
for _, route := range routes {
formattedStr := route.TableString()
_, _ = fmt.Fprintln(writer, formattedStr)
}
}
+285
View File
@@ -0,0 +1,285 @@
package tunnel
import (
"fmt"
"os"
"text/tabwriter"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
)
var (
makeDefaultFlag = &cli.BoolFlag{
Name: "default",
Aliases: []string{"d"},
Usage: "The virtual network becomes the default one for the account. This means that all operations that " +
"omit a virtual network will now implicitly be using this virtual network (i.e., the default one) such " +
"as new IP routes that are created. When this flag is not set, the virtual network will not become the " +
"default one in the account.",
}
newNameFlag = &cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "The new name for the virtual network.",
}
newCommentFlag = &cli.StringFlag{
Name: "comment",
Aliases: []string{"c"},
Usage: "A new comment describing the purpose of the virtual network.",
}
)
func buildVirtualNetworkSubcommand(hidden bool) *cli.Command {
return &cli.Command{
Name: "network",
Usage: "Configure and query virtual networks to manage private IP routes with overlapping IPs.",
UsageText: "cloudflared tunnel [--config FILEPATH] network COMMAND [arguments...]",
Description: `cloudflared allows to manage IP routes that expose origins in your private network space via their IP directly
to clients outside (e.g. using WARP client) --- those are configurable via "cloudflared tunnel route ip" commands.
By default, all those IP routes live in the same virtual network. Managing virtual networks (e.g. by creating a
new one) becomes relevant when you have different private networks that have overlapping IPs. E.g.: if you have
a private network A running Tunnel 1, and private network B running Tunnel 2, it is possible that both Tunnels
expose the same IP space (say 10.0.0.0/8); to handle that, you have to add each IP Route (one that points to
Tunnel 1 and another that points to Tunnel 2) in different Virtual Networks. That way, if your clients are on
Virtual Network X, they will see Tunnel 1 (via Route A) and not see Tunnel 2 (since its Route B is associated
to another Virtual Network Y).`,
Hidden: hidden,
Subcommands: []*cli.Command{
{
Name: "add",
Action: cliutil.ConfiguredAction(addVirtualNetworkCommand),
Usage: "Add a new virtual network to which IP routes can be attached",
UsageText: "cloudflared tunnel [--config FILEPATH] network add [flags] NAME [\"comment\"]",
Description: `Adds a new virtual network. You can then attach IP routes to this virtual network with "cloudflared tunnel route ip"
commands. By doing so, such route(s) become segregated from route(s) in another virtual networks. Note that all
routes exist within some virtual network. If you do not specify any, then the system pre-creates a default virtual
network to which all routes belong. That is fine if you do not have overlapping IPs within different physical
private networks in your infrastructure exposed via Cloudflare Tunnel. Note: if a virtual network is added as
the new default, then the previous existing default virtual network will be automatically modified to no longer
be the current default.`,
Flags: []cli.Flag{makeDefaultFlag},
Hidden: hidden,
},
{
Name: "list",
Action: cliutil.ConfiguredAction(listVirtualNetworksCommand),
Usage: "Lists the virtual networks",
UsageText: "cloudflared tunnel [--config FILEPATH] network list [flags]",
Description: "Lists the virtual networks based on the given filter flags.",
Flags: listVirtualNetworksFlags(),
Hidden: hidden,
},
{
Name: "delete",
Action: cliutil.ConfiguredAction(deleteVirtualNetworkCommand),
Usage: "Delete a virtual network",
UsageText: "cloudflared tunnel [--config FILEPATH] network delete VIRTUAL_NETWORK",
Description: `Deletes the virtual network (given its ID or name). This is only possible if that virtual network is unused.
A virtual network may be used by IP routes or by WARP devices.`,
Hidden: hidden,
},
{
Name: "update",
Action: cliutil.ConfiguredAction(updateVirtualNetworkCommand),
Usage: "Update a virtual network",
UsageText: "cloudflared tunnel [--config FILEPATH] network update [flags] VIRTUAL_NETWORK",
Description: `Updates the virtual network (given its ID or name). If this virtual network is updated to become the new
default, then the previously existing default virtual network will also be modified to no longer be the default.
You cannot update a virtual network to not be the default anymore directly. Instead, you should create a new
default or update an existing one to become the default.`,
Flags: []cli.Flag{newNameFlag, newCommentFlag, makeDefaultFlag},
Hidden: hidden,
},
},
}
}
func listVirtualNetworksFlags() []cli.Flag {
flags := make([]cli.Flag, 0)
flags = append(flags, cfapi.VnetFilterFlags...)
flags = append(flags, outputFormatFlag)
return flags
}
func addVirtualNetworkCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() < 1 {
return errors.New("You must supply at least 1 argument, the name of the virtual network you wish to add.")
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
args := c.Args()
name := args.Get(0)
comment := ""
if c.NArg() >= 2 {
comment = args.Get(1)
}
newVnet := cfapi.NewVirtualNetwork{
Name: name,
Comment: comment,
IsDefault: c.Bool(makeDefaultFlag.Name),
}
createdVnet, err := sc.addVirtualNetwork(newVnet)
if err != nil {
return errors.Wrap(err, "Could not add virtual network")
}
extraMsg := ""
if createdVnet.IsDefault {
extraMsg = " (as the new default for this account) "
}
fmt.Printf(
"Successfully added virtual 'network' %s with ID: %s%s\n"+
"You can now add IP routes attached to this virtual network. See `cloudflared tunnel route ip add -help`\n",
name, createdVnet.ID, extraMsg,
)
return nil
}
func listVirtualNetworksCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
filter, err := cfapi.NewFromCLI(c)
if err != nil {
return errors.Wrap(err, "invalid flags for filtering virtual networks")
}
vnets, err := sc.listVirtualNetworks(filter)
if err != nil {
return err
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, vnets)
}
if len(vnets) > 0 {
formatAndPrintVnetsList(vnets)
} else {
fmt.Println("No virtual networks were found for the given filter flags. You can use 'cloudflared tunnel network add' to add a virtual network.")
}
return nil
}
func deleteVirtualNetworkCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() != 1 {
return errors.New("You must supply exactly one argument, either the ID or name of the virtual network to delete")
}
input := c.Args().Get(0)
vnetId, err := getVnetId(sc, input)
if err != nil {
return err
}
if err := sc.deleteVirtualNetwork(vnetId); err != nil {
return errors.Wrap(err, "API error")
}
fmt.Printf("Successfully deleted virtual network '%s'\n", input)
return nil
}
func updateVirtualNetworkCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if c.NArg() != 1 {
return errors.New(" You must supply exactly one argument, either the ID or (current) name of the virtual network to update")
}
input := c.Args().Get(0)
vnetId, err := getVnetId(sc, input)
if err != nil {
return err
}
updates := cfapi.UpdateVirtualNetwork{}
if c.IsSet(newNameFlag.Name) {
newName := c.String(newNameFlag.Name)
updates.Name = &newName
}
if c.IsSet(newCommentFlag.Name) {
newComment := c.String(newCommentFlag.Name)
updates.Comment = &newComment
}
if c.IsSet(makeDefaultFlag.Name) {
isDefault := c.Bool(makeDefaultFlag.Name)
updates.IsDefault = &isDefault
}
if err := sc.updateVirtualNetwork(vnetId, updates); err != nil {
return errors.Wrap(err, "API error")
}
fmt.Printf("Successfully updated virtual network '%s'\n", input)
return nil
}
func getVnetId(sc *subcommandContext, input string) (uuid.UUID, error) {
val, err := uuid.Parse(input)
if err == nil {
return val, nil
}
filter := cfapi.NewVnetFilter()
filter.WithDeleted(false)
filter.ByName(input)
vnets, err := sc.listVirtualNetworks(filter)
if err != nil {
return uuid.Nil, err
}
if len(vnets) != 1 {
return uuid.Nil, fmt.Errorf("there should only be 1 non-deleted virtual network named %s", input)
}
return vnets[0].ID, nil
}
func formatAndPrintVnetsList(vnets []*cfapi.VirtualNetwork) {
const (
minWidth = 0
tabWidth = 8
padding = 1
padChar = ' '
flags = 0
)
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
defer writer.Flush()
_, _ = fmt.Fprintln(writer, "ID\tNAME\tIS DEFAULT\tCOMMENT\tCREATED\tDELETED\t")
for _, virtualNetwork := range vnets {
formattedStr := virtualNetwork.TableString()
_, _ = fmt.Fprintln(writer, formattedStr)
}
}
+223
View File
@@ -0,0 +1,223 @@
package ui
import (
"context"
"fmt"
"strings"
"github.com/gdamore/tcell"
"github.com/rivo/tview"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/ingress"
)
type connState struct {
location string
}
type uiModel struct {
version string
edgeURL string
metricsURL string
localServices []string
connections []connState
}
type palette struct {
url string
connected string
defaultText string
disconnected string
reconnecting string
unregistered string
}
func NewUIModel(version, hostname, metricsURL string, ing *ingress.Ingress, haConnections int) *uiModel {
localServices := make([]string, len(ing.Rules))
for i, rule := range ing.Rules {
localServices[i] = rule.Service.String()
}
return &uiModel{
version: version,
edgeURL: hostname,
metricsURL: metricsURL,
localServices: localServices,
connections: make([]connState, haConnections),
}
}
func (data *uiModel) Launch(
ctx context.Context,
log, transportLog *zerolog.Logger,
) connection.EventSink {
// Configure the logger to stream logs into the textview
// Add TextView as a group to write output to
logTextView := NewDynamicColorTextView()
// TODO: Format log for UI
//log.Add(logTextView, logger.NewUIFormatter(time.RFC3339), logLevels...)
//transportLog.Add(logTextView, logger.NewUIFormatter(time.RFC3339), logLevels...)
// Construct the UI
palette := palette{
url: "lightblue",
connected: "lime",
defaultText: "white",
disconnected: "red",
reconnecting: "orange",
unregistered: "orange",
}
app := tview.NewApplication()
grid := tview.NewGrid().SetGap(1, 0)
frame := tview.NewFrame(grid)
header := fmt.Sprintf("cloudflared [::b]%s", data.version)
frame.AddText(header, true, tview.AlignLeft, tcell.ColorWhite)
// Create table to store connection info and status
connTable := tview.NewTable()
// SetColumns takes a value for each column, representing the size of the column
// Numbers <= 0 represent proportional widths and positive numbers represent absolute widths
grid.SetColumns(20, 0)
// SetRows takes a value for each row, representing the size of the row
grid.SetRows(1, 1, len(data.connections), 1, 0)
// AddItem takes a primitive tview type, row, column, rowSpan, columnSpan, minGridHeight, minGridWidth, and focus
grid.AddItem(tview.NewTextView().SetText("Tunnel:"), 0, 0, 1, 1, 0, 0, false)
grid.AddItem(tview.NewTextView().SetText("Status:"), 1, 0, 1, 1, 0, 0, false)
grid.AddItem(tview.NewTextView().SetText("Connections:"), 2, 0, 1, 1, 0, 0, false)
grid.AddItem(tview.NewTextView().SetText("Metrics:"), 3, 0, 1, 1, 0, 0, false)
tunnelHostText := tview.NewTextView().SetText(data.edgeURL)
grid.AddItem(tunnelHostText, 0, 1, 1, 1, 0, 0, false)
status := fmt.Sprintf("[%s]\u2022[%s] Proxying to [%s::b]%s", palette.connected, palette.defaultText, palette.url, strings.Join(data.localServices, ", "))
grid.AddItem(NewDynamicColorTextView().SetText(status), 1, 1, 1, 1, 0, 0, false)
grid.AddItem(connTable, 2, 1, 1, 1, 0, 0, false)
grid.AddItem(NewDynamicColorTextView().SetText(fmt.Sprintf("Metrics at [%s::b]http://%s/metrics", palette.url, data.metricsURL)), 3, 1, 1, 1, 0, 0, false)
// Add TextView to stream logs
// Logs are displayed in a new grid so a border can be set around them
logGrid := tview.NewGrid().SetBorders(true).AddItem(logTextView.SetChangedFunc(handleNewText(app, logTextView)), 0, 0, 5, 2, 0, 0, false)
// LogFrame holds the Logs header as well as the grid with the textView for streamed logs
logFrame := tview.NewFrame(logGrid).AddText("[::b]Logs:[::-]", true, tview.AlignLeft, tcell.ColorWhite).SetBorders(0, 0, 0, 0, 0, 0)
// Footer for log frame
logFrame.AddText("[::d]Use Ctrl+C to exit[::-]", false, tview.AlignRight, tcell.ColorWhite)
grid.AddItem(logFrame, 4, 0, 5, 2, 0, 0, false)
go func() {
<-ctx.Done()
app.Stop()
return
}()
go func() {
if err := app.SetRoot(frame, true).Run(); err != nil {
log.Error().Msgf("Error launching UI: %s", err)
}
}()
return connection.EventSinkFunc(func(event connection.Event) {
switch event.EventType {
case connection.Connected:
data.setConnTableCell(event, connTable, palette)
case connection.Disconnected, connection.Reconnecting, connection.Unregistering:
data.changeConnStatus(event, connTable, log, palette)
case connection.SetURL:
tunnelHostText.SetText(event.URL)
data.edgeURL = event.URL
case connection.RegisteringTunnel:
if data.edgeURL == "" {
tunnelHostText.SetText(fmt.Sprintf("Registering tunnel connection %d...", event.Index))
}
}
app.Draw()
})
}
func NewDynamicColorTextView() *tview.TextView {
return tview.NewTextView().SetDynamicColors(true)
}
// Re-draws application when new logs are streamed to UI
func handleNewText(app *tview.Application, logTextView *tview.TextView) func() {
return func() {
app.Draw()
// SetFocus to enable scrolling in textview
app.SetFocus(logTextView)
}
}
func (data *uiModel) changeConnStatus(event connection.Event, table *tview.Table, log *zerolog.Logger, palette palette) {
index := int(event.Index)
// Get connection location and state
connState := data.getConnState(index)
// Check if connection is already displayed in UI
if connState == nil {
log.Info().Msg("Connection is not in the UI table")
return
}
locationState := event.Location
if event.EventType == connection.Reconnecting {
locationState = "Reconnecting..."
}
connectionNum := index + 1
// Get table cell
cell := table.GetCell(index, 0)
// Change dot color in front of text as well as location state
text := newCellText(palette, connectionNum, locationState, event.EventType)
cell.SetText(text)
}
// Return connection location and row in UI table
func (data *uiModel) getConnState(connID int) *connState {
if connID < len(data.connections) {
return &data.connections[connID]
}
return nil
}
func (data *uiModel) setConnTableCell(event connection.Event, table *tview.Table, palette palette) {
index := int(event.Index)
connectionNum := index + 1
// Update slice to keep track of connection location and state in UI table
data.connections[index].location = event.Location
// Update text in table cell to show disconnected state
text := newCellText(palette, connectionNum, event.Location, event.EventType)
cell := tview.NewTableCell(text)
table.SetCell(index, 0, cell)
}
func newCellText(palette palette, connectionNum int, location string, connectedStatus connection.Status) string {
// HA connection indicator formatted as: "• #<CONNECTION_INDEX>: <COLO>",
// where the left middle dot's color depends on the status of the connection
const connFmtString = "[%s]\u2022[%s] #%d: %s"
var dotColor string
switch connectedStatus {
case connection.Connected:
dotColor = palette.connected
case connection.Disconnected:
dotColor = palette.disconnected
case connection.Reconnecting:
dotColor = palette.reconnecting
case connection.Unregistering:
dotColor = palette.unregistered
}
return fmt.Sprintf(connFmtString, dotColor, palette.defaultText, connectionNum, location)
}
+49
View File
@@ -0,0 +1,49 @@
package updater
import (
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
)
type VersionWarningChecker struct {
warningChan chan string
}
func StartWarningCheck(c *cli.Context) VersionWarningChecker {
checker := VersionWarningChecker{
warningChan: make(chan string),
}
go func() {
options := updateOptions{
updateDisabled: true,
isBeta: c.Bool("beta"),
isStaging: c.Bool("staging"),
isForced: false,
intendedVersion: "",
}
checkResult, err := CheckForUpdate(options)
if err == nil {
checker.warningChan <- checkResult.UserMessage()
}
close(checker.warningChan)
}()
return checker
}
func (checker VersionWarningChecker) getWarning() string {
select {
case message := <-checker.warningChan:
return message
default:
// No feedback on time, we don't wait for it, since this is best-effort.
return ""
}
}
func (checker VersionWarningChecker) LogWarningIfAny(log *zerolog.Logger) {
if warning := checker.getWarning(); warning != "" {
log.Warn().Msg(warning)
}
}
+30
View File
@@ -0,0 +1,30 @@
package updater
// CheckResult is the behaviour resulting from checking in with the Update Service
type CheckResult interface {
Apply() error
Version() string
UserMessage() string
}
// Service is the functions to get check for new updates
type Service interface {
Check() (CheckResult, error)
}
const (
// OSKeyName is the url parameter key to send to the checkin API for the operating system of the local cloudflared (e.g. windows, darwin, linux)
OSKeyName = "os"
// ArchitectureKeyName is the url parameter key to send to the checkin API for the architecture of the local cloudflared (e.g. amd64, x86)
ArchitectureKeyName = "arch"
// BetaKeyName is the url parameter key to send to the checkin API to signal if the update should be a beta version or not
BetaKeyName = "beta"
// VersionKeyName is the url parameter key to send to the checkin API to specific what version to upgrade or downgrade to
VersionKeyName = "version"
// ClientVersionName is the url parameter key to send the version that this cloudflared is currently running with
ClientVersionName = "clientVersion"
)
+251 -70
View File
@@ -1,119 +1,300 @@
package updater
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v2"
"github.com/cloudflare/cloudflared/log"
"github.com/equinox-io/equinox"
"github.com/facebookgo/grace/gracenet"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/ssh/terminal"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
)
const (
appID = "app_idCzgxYerVD"
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/argo-tunnel/reference/service/"
noUpdateOnWindowsMessage = "cloudflared will not automatically update on Windows systems."
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"
noUpdateOnWindowsMessage = "cloudflared will not automatically update on Windows systems."
noUpdateManagedPackageMessage = "cloudflared will not automatically update if installed by a package manager."
isManagedInstallFile = ".installedFromPackageManager"
UpdateURL = "https://update.argotunnel.com"
StagingUpdateURL = "https://staging-update.argotunnel.com"
LogFieldVersion = "version"
)
var (
publicKey = []byte(`
-----BEGIN ECDSA PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4OWZocTVZ8Do/L6ScLdkV+9A0IYMHoOf
dsCmJ/QZ6aw0w9qkkwEpne1Lmo6+0pGexZzFZOH6w5amShn+RXt7qkSid9iWlzGq
EKx0BZogHSor9Wy5VztdFaAaVbsJiCbO
-----END ECDSA PUBLIC KEY-----
`)
logger = log.CreateLogger()
version string
)
type ReleaseInfo struct {
Updated bool
Version string
Error error
// BinaryUpdated implements ExitCoder interface, the app will exit with status code 11
// https://pkg.go.dev/github.com/urfave/cli/v2?tab=doc#ExitCoder
type statusSuccess struct {
newVersion string
}
func checkForUpdates() ReleaseInfo {
var opts equinox.Options
if err := opts.SetPublicKeyPEM(publicKey); err != nil {
return ReleaseInfo{Error: err}
}
func (u *statusSuccess) Error() string {
return fmt.Sprintf("cloudflared has been updated to version %s", u.newVersion)
}
resp, err := equinox.Check(appID, opts)
switch {
case err == equinox.NotAvailableErr:
return ReleaseInfo{}
case err != nil:
return ReleaseInfo{Error: err}
}
func (u *statusSuccess) ExitCode() int {
return 11
}
err = resp.Apply()
// UpdateErr implements ExitCoder interface, the app will exit with status code 10
type statusErr struct {
err error
}
func (e *statusErr) Error() string {
return fmt.Sprintf("failed to update cloudflared: %v", e.err)
}
func (e *statusErr) ExitCode() int {
return 10
}
type updateOptions struct {
updateDisabled bool
isBeta bool
isStaging bool
isForced bool
intendedVersion string
}
type UpdateOutcome struct {
Updated bool
Version string
UserMessage string
Error error
}
func (uo *UpdateOutcome) noUpdate() bool {
return uo.Error == nil && uo.Updated == false
}
func Init(v string) {
version = v
}
func CheckForUpdate(options updateOptions) (CheckResult, error) {
cfdPath, err := os.Executable()
if err != nil {
return ReleaseInfo{Error: err}
return nil, err
}
return ReleaseInfo{Updated: true, Version: resp.ReleaseVersion}
}
func Update(_ *cli.Context) error {
if updateApplied() {
os.Exit(64)
url := UpdateURL
if options.isStaging {
url = StagingUpdateURL
}
return nil
s := NewWorkersService(version, url, cfdPath, Options{IsBeta: options.isBeta,
IsForced: options.isForced, RequestedVersion: options.intendedVersion})
return s.Check()
}
func Autoupdate(freq time.Duration, listeners *gracenet.Net, shutdownC chan struct{}) error {
tickC := time.Tick(freq)
func applyUpdate(options updateOptions, update CheckResult) UpdateOutcome {
if update.Version() == "" || options.updateDisabled {
return UpdateOutcome{UserMessage: update.UserMessage()}
}
err := update.Apply()
if err != nil {
return UpdateOutcome{Error: err}
}
return UpdateOutcome{Updated: true, Version: update.Version(), UserMessage: update.UserMessage()}
}
// Update is the handler for the update command from the command line
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.")
return nil
}
isBeta := c.Bool("beta")
if isBeta {
log.Info().Msg("cloudflared is set to update to the latest beta version")
}
isStaging := c.Bool("staging")
if isStaging {
log.Info().Msg("cloudflared is set to update from staging")
}
isForced := c.Bool("force")
if isForced {
log.Info().Msg("cloudflared is set to upgrade to the latest publish version regardless of the current version")
}
updateOutcome := loggedUpdate(log, updateOptions{
updateDisabled: false,
isBeta: isBeta,
isStaging: isStaging,
isForced: isForced,
intendedVersion: c.String("version"),
})
if updateOutcome.Error != nil {
return &statusErr{updateOutcome.Error}
}
if updateOutcome.noUpdate() {
log.Info().Str(LogFieldVersion, updateOutcome.Version).Msg("cloudflared is up to date")
return nil
}
return &statusSuccess{newVersion: updateOutcome.Version}
}
// Checks for an update and applies it if one is available
func loggedUpdate(log *zerolog.Logger, options updateOptions) UpdateOutcome {
checkResult, err := CheckForUpdate(options)
if err != nil {
log.Err(err).Msg("update check failed")
return UpdateOutcome{Error: err}
}
updateOutcome := applyUpdate(options, checkResult)
if updateOutcome.Updated {
log.Info().Str(LogFieldVersion, updateOutcome.Version).Msg("cloudflared has been updated")
}
if updateOutcome.Error != nil {
log.Err(updateOutcome.Error).Msg("update failed to apply")
}
return updateOutcome
}
// AutoUpdater periodically checks for new version of cloudflared.
type AutoUpdater struct {
configurable *configurable
listeners *gracenet.Net
updateConfigChan chan *configurable
log *zerolog.Logger
}
// AutoUpdaterConfigurable is the attributes of AutoUpdater that can be reconfigured during runtime
type configurable struct {
enabled bool
freq time.Duration
}
func NewAutoUpdater(updateDisabled bool, freq time.Duration, listeners *gracenet.Net, log *zerolog.Logger) *AutoUpdater {
return &AutoUpdater{
configurable: createUpdateConfig(updateDisabled, freq, log),
listeners: listeners,
updateConfigChan: make(chan *configurable),
log: log,
}
}
func createUpdateConfig(updateDisabled bool, freq time.Duration, log *zerolog.Logger) *configurable {
if isAutoupdateEnabled(log, updateDisabled, freq) {
log.Info().Dur("autoupdateFreq", freq).Msg("Autoupdate frequency is set")
return &configurable{
enabled: true,
freq: freq,
}
} else {
return &configurable{
enabled: false,
freq: DefaultCheckUpdateFreq,
}
}
}
func (a *AutoUpdater) Run(ctx context.Context) error {
ticker := time.NewTicker(a.configurable.freq)
for {
if updateApplied() {
os.Args = append(os.Args, "--is-autoupdated=true")
pid, err := listeners.StartProcess()
if err != nil {
logger.WithError(err).Error("Unable to restart server automatically")
return err
updateOutcome := loggedUpdate(a.log, updateOptions{updateDisabled: !a.configurable.enabled})
if updateOutcome.Updated {
Init(updateOutcome.Version)
if IsSysV() {
// SysV doesn't have a mechanism to keep service alive, we have to restart the process
a.log.Info().Msg("Restarting service managed by SysV...")
pid, err := a.listeners.StartProcess()
if err != nil {
a.log.Err(err).Msg("Unable to restart server automatically")
return &statusErr{err: err}
}
// stop old process after autoupdate. Otherwise we create a new process
// after each update
a.log.Info().Msgf("PID of the new process is %d", pid)
}
// stop old process after autoupdate. Otherwise we create a new process
// after each update
logger.Infof("PID of the new process is %d", pid)
return nil
return &statusSuccess{newVersion: updateOutcome.Version}
} else if updateOutcome.UserMessage != "" {
a.log.Warn().Msg(updateOutcome.UserMessage)
}
select {
case <-tickC:
case <-shutdownC:
return nil
case <-ctx.Done():
return ctx.Err()
case newConfigurable := <-a.updateConfigChan:
ticker.Stop()
a.configurable = newConfigurable
ticker = time.NewTicker(a.configurable.freq)
// Check if there is new version of cloudflared after receiving new AutoUpdaterConfigurable
case <-ticker.C:
}
}
}
func updateApplied() bool {
releaseInfo := checkForUpdates()
if releaseInfo.Updated {
logger.Infof("Updated to version %s", releaseInfo.Version)
return true
}
if releaseInfo.Error != nil {
logger.WithError(releaseInfo.Error).Error("Update check failed")
}
return false
// Update is the method to pass new AutoUpdaterConfigurable to a running AutoUpdater. It is safe to be called concurrently
func (a *AutoUpdater) Update(updateDisabled bool, newFreq time.Duration) {
a.updateConfigChan <- createUpdateConfig(updateDisabled, newFreq, a.log)
}
func IsAutoupdateEnabled(c *cli.Context) bool {
func isAutoupdateEnabled(log *zerolog.Logger, updateDisabled bool, updateFreq time.Duration) bool {
if !supportAutoUpdate(log) {
return false
}
return !updateDisabled && updateFreq != 0
}
func supportAutoUpdate(log *zerolog.Logger) bool {
if runtime.GOOS == "windows" {
logger.Info(noUpdateOnWindowsMessage)
log.Info().Msg(noUpdateOnWindowsMessage)
return false
}
if wasInstalledFromPackageManager() {
log.Info().Msg(noUpdateManagedPackageMessage)
return false
}
if isRunningFromTerminal() {
logger.Info(noUpdateInShellMessage)
log.Info().Msg(noUpdateInShellMessage)
return false
}
return true
}
return !c.Bool("no-autoupdate") && c.Duration("autoupdate-freq") != 0
func wasInstalledFromPackageManager() bool {
ok, _ := config.FileExists(filepath.Join(config.DefaultUnixConfigLocation, isManagedInstallFile))
return ok
}
func isRunningFromTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}
func IsSysV() bool {
if runtime.GOOS != "linux" {
return false
}
if _, err := os.Stat("/run/systemd/system"); err == nil {
return false
}
return true
}
+40
View File
@@ -0,0 +1,40 @@
package updater
import (
"context"
"flag"
"testing"
"github.com/facebookgo/grace/gracenet"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
func TestDisabledAutoUpdater(t *testing.T) {
listeners := &gracenet.Net{}
log := zerolog.Nop()
autoupdater := NewAutoUpdater(false, 0, listeners, &log)
ctx, cancel := context.WithCancel(context.Background())
errC := make(chan error)
go func() {
errC <- autoupdater.Run(ctx)
}()
assert.False(t, autoupdater.configurable.enabled)
assert.Equal(t, DefaultCheckUpdateFreq, autoupdater.configurable.freq)
cancel()
// Make sure that autoupdater terminates after canceling the context
assert.Equal(t, context.Canceled, <-errC)
}
func TestCheckInWithUpdater(t *testing.T) {
flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError)
cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil)
warningChecker := StartWarningCheck(cliCtx)
warning := warningChecker.getWarning()
// Assuming this runs either on a release or development version, then the Worker will never have anything to tell us.
assert.Empty(t, warning)
}
@@ -0,0 +1,94 @@
package updater
import (
"encoding/json"
"errors"
"net/http"
"runtime"
)
// Options are the update options supported by the
type Options struct {
// IsBeta is for beta updates to be installed if available
IsBeta bool
// IsForced is to forcibly download the latest version regardless of the current version
IsForced bool
// RequestedVersion is the specific version to upgrade or downgrade to
RequestedVersion string
}
// VersionResponse is the JSON response from the Workers API endpoint
type VersionResponse struct {
URL string `json:"url"`
Version string `json:"version"`
Checksum string `json:"checksum"`
IsCompressed bool `json:"compressed"`
UserMessage string `json:"userMessage"`
ShouldUpdate bool `json:"shouldUpdate"`
Error string `json:"error"`
}
// WorkersService implements Service.
// It contains everything needed to check in with the WorkersAPI and download and apply the updates
type WorkersService struct {
currentVersion string
url string
targetPath string
opts Options
}
// NewWorkersService creates a new updater Service object.
func NewWorkersService(currentVersion, url, targetPath string, opts Options) Service {
return &WorkersService{
currentVersion: currentVersion,
url: url,
targetPath: targetPath,
opts: opts,
}
}
// Check does a check in with the Workers API to get a new version update
func (s *WorkersService) Check() (CheckResult, error) {
client := &http.Client{
Timeout: clientTimeout,
}
req, err := http.NewRequest(http.MethodGet, s.url, nil)
q := req.URL.Query()
q.Add(OSKeyName, runtime.GOOS)
q.Add(ArchitectureKeyName, runtime.GOARCH)
q.Add(ClientVersionName, s.currentVersion)
if s.opts.IsBeta {
q.Add(BetaKeyName, "true")
}
if s.opts.RequestedVersion != "" {
q.Add(VersionKeyName, s.opts.RequestedVersion)
}
req.URL.RawQuery = q.Encode()
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var v VersionResponse
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return nil, err
}
if v.Error != "" {
return nil, errors.New(v.Error)
}
versionToUpdate := ""
if v.ShouldUpdate {
versionToUpdate = v.Version
}
return NewWorkersVersion(v.URL, versionToUpdate, v.Checksum, s.targetPath, v.UserMessage, v.IsCompressed), nil
}
@@ -0,0 +1,349 @@
//go:build !windows
// +build !windows
package updater
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var testFilePath = filepath.Join(os.TempDir(), "test")
func respondWithJSON(w http.ResponseWriter, v interface{}, status int) {
data, _ := json.Marshal(v)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write(data)
}
func respondWithData(w http.ResponseWriter, b []byte, status int) {
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(status)
w.Write(b)
}
const mostRecentVersion = "2021.2.5"
const mostRecentBetaVersion = "2021.3.0"
const knownBuggyVersion = "2020.12.0"
const expectedUserMsg = "This message is expected when running a known buggy version"
func updateHandler(w http.ResponseWriter, r *http.Request) {
version := mostRecentVersion
host := fmt.Sprintf("http://%s", r.Host)
url := host + "/download"
query := r.URL.Query()
if query.Get(BetaKeyName) == "true" {
version = mostRecentBetaVersion
url = host + "/beta"
}
requestedVersion := query.Get(VersionKeyName)
if requestedVersion != "" {
version = requestedVersion
url = fmt.Sprintf("%s?version=%s", url, requestedVersion)
}
if query.Get(ArchitectureKeyName) != runtime.GOARCH || query.Get(OSKeyName) != runtime.GOOS {
respondWithJSON(w, VersionResponse{Error: "unsupported os and architecture"}, http.StatusBadRequest)
return
}
h := sha256.New()
fmt.Fprint(h, version)
checksum := fmt.Sprintf("%x", h.Sum(nil))
var userMessage = ""
if query.Get(ClientVersionName) == knownBuggyVersion {
userMessage = expectedUserMsg
}
shouldUpdate := requestedVersion != "" || IsNewerVersion(query.Get(ClientVersionName), version)
v := VersionResponse{
URL: url, Version: version, Checksum: checksum, UserMessage: userMessage, ShouldUpdate: shouldUpdate,
}
respondWithJSON(w, v, http.StatusOK)
}
func gzipUpdateHandler(w http.ResponseWriter, r *http.Request) {
log.Println("got a request!")
version := "2020.09.02"
h := sha256.New()
fmt.Fprint(h, version)
checksum := fmt.Sprintf("%x", h.Sum(nil))
url := fmt.Sprintf("http://%s/gzip-download.tgz", r.Host)
v := VersionResponse{URL: url, Version: version, Checksum: checksum, ShouldUpdate: true}
respondWithJSON(w, v, http.StatusOK)
}
func compressedDownloadHandler(w http.ResponseWriter, r *http.Request) {
version := "2020.09.02"
buf := new(bytes.Buffer)
gw := gzip.NewWriter(buf)
tw := tar.NewWriter(gw)
header := &tar.Header{
Size: int64(len(version)),
Name: "download",
}
tw.WriteHeader(header)
tw.Write([]byte(version))
tw.Close()
gw.Close()
respondWithData(w, buf.Bytes(), http.StatusOK)
}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
version := mostRecentVersion
requestedVersion := r.URL.Query().Get(VersionKeyName)
if requestedVersion != "" {
version = requestedVersion
}
respondWithData(w, []byte(version), http.StatusOK)
}
func betaHandler(w http.ResponseWriter, r *http.Request) {
respondWithData(w, []byte(mostRecentBetaVersion), http.StatusOK)
}
func failureHandler(w http.ResponseWriter, r *http.Request) {
respondWithJSON(w, VersionResponse{Error: "unsupported os and architecture"}, http.StatusBadRequest)
}
func IsNewerVersion(current string, check string) bool {
if current == "" || check == "" {
return false
}
if strings.Contains(strings.ToLower(current), "dev") {
return false // dev builds shouldn't update
}
cMajor, cMinor, cPatch, err := SemanticParts(current)
if err != nil {
return false
}
nMajor, nMinor, nPatch, err := SemanticParts(check)
if err != nil {
return false
}
if nMajor > cMajor {
return true
}
if nMajor == cMajor && nMinor > cMinor {
return true
}
if nMajor == cMajor && nMinor == cMinor && nPatch > cPatch {
return true
}
return false
}
func SemanticParts(version string) (major int, minor int, patch int, err error) {
major = 0
minor = 0
patch = 0
parts := strings.Split(version, ".")
if len(parts) != 3 {
err = errors.New("invalid version")
return
}
major, err = strconv.Atoi(parts[0])
if err != nil {
return
}
minor, err = strconv.Atoi(parts[1])
if err != nil {
return
}
patch, err = strconv.Atoi(parts[2])
if err != nil {
return
}
return
}
func createServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/updater", updateHandler)
mux.HandleFunc("/download", downloadHandler)
mux.HandleFunc("/beta", betaHandler)
mux.HandleFunc("/fail", failureHandler)
mux.HandleFunc("/compressed", gzipUpdateHandler)
mux.HandleFunc("/gzip-download.tgz", compressedDownloadHandler)
return httptest.NewServer(mux)
}
func createTestFile(t *testing.T, path string) {
f, err := os.Create(path)
require.NoError(t, err)
fmt.Fprint(f, "2020.08.04")
f.Close()
}
func TestUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
log.Println("server url: ", ts.URL)
s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, v.Version(), mostRecentVersion)
require.NoError(t, v.Apply())
dat, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
require.Equal(t, string(dat), mostRecentVersion)
}
func TestBetaUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{IsBeta: true})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, v.Version(), mostRecentBetaVersion)
require.NoError(t, v.Apply())
dat, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
require.Equal(t, string(dat), mostRecentBetaVersion)
}
func TestFailUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/fail", ts.URL), testFilePath, Options{})
v, err := s.Check()
require.Error(t, err)
require.Nil(t, v)
}
func TestNoUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService(mostRecentVersion, fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{})
v, err := s.Check()
require.NoError(t, err)
require.NotNil(t, v)
require.Empty(t, v.Version())
}
func TestForcedUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService("2020.8.5", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{IsForced: true})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, v.Version(), mostRecentVersion)
require.NoError(t, v.Apply())
dat, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
require.Equal(t, string(dat), mostRecentVersion)
}
func TestUpdateSpecificVersionService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
reqVersion := "2020.9.1"
s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{RequestedVersion: reqVersion})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, reqVersion, v.Version())
require.NoError(t, v.Apply())
dat, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
require.Equal(t, reqVersion, string(dat))
}
func TestCompressedUpdateService(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/compressed", ts.URL), testFilePath, Options{})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, "2020.09.02", v.Version())
require.NoError(t, v.Apply())
dat, err := ioutil.ReadFile(testFilePath)
require.NoError(t, err)
require.Equal(t, "2020.09.02", string(dat))
}
func TestUpdateWhenRunningKnownBuggyVersion(t *testing.T) {
ts := createServer()
defer ts.Close()
createTestFile(t, testFilePath)
defer os.Remove(testFilePath)
s := NewWorkersService(knownBuggyVersion, fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{})
v, err := s.Check()
require.NoError(t, err)
require.Equal(t, v.Version(), mostRecentVersion)
require.Equal(t, v.UserMessage(), expectedUserMsg)
}
+246
View File
@@ -0,0 +1,246 @@
package updater
import (
"archive/tar"
"compress/gzip"
"crypto/sha256"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"text/template"
"time"
)
const (
clientTimeout = time.Second * 60
// stop the service
// rename cloudflared.exe to cloudflared.exe.old
// rename cloudflared.exe.new to cloudflared.exe
// delete cloudflared.exe.old
// start the service
// delete the batch file
windowsUpdateCommandTemplate = `@echo off
sc stop cloudflared >nul 2>&1
rename "{{.TargetPath}}" {{.OldName}}
rename "{{.NewPath}}" {{.BinaryName}}
del "{{.OldPath}}"
sc start cloudflared >nul 2>&1
del {{.BatchName}}`
batchFileName = "cfd_update.bat"
)
// Prepare some data to insert into the template.
type batchData struct {
TargetPath string
OldName string
NewPath string
OldPath string
BinaryName string
BatchName string
}
// WorkersVersion implements the Version interface.
// It contains everything needed to perform a version upgrade
type WorkersVersion struct {
downloadURL string
checksum string
version string
targetPath string
isCompressed bool
userMessage string
}
// NewWorkersVersion creates a new Version object. This is normally created by a WorkersService JSON checkin response
// url is where to download the file
// version is the version of this update
// checksum is the expected checksum of the downloaded file
// target path is where the file should be replace. Normally this the running cloudflared's path
// userMessage is a possible message to convey back to the user after having checked in with the Updater Service
// isCompressed tells whether the asset to update cloudflared is compressed or not
func NewWorkersVersion(url, version, checksum, targetPath, userMessage string, isCompressed bool) CheckResult {
return &WorkersVersion{
downloadURL: url,
version: version,
checksum: checksum,
targetPath: targetPath,
isCompressed: isCompressed,
userMessage: userMessage,
}
}
// Apply does the actual verification and update logic.
// This includes signature and checksum validation,
// replacing the binary, etc
func (v *WorkersVersion) Apply() error {
newFilePath := fmt.Sprintf("%s.new", v.targetPath)
os.Remove(newFilePath) //remove any failed updates before download
// download the file
if err := download(v.downloadURL, newFilePath, v.isCompressed); err != nil {
return err
}
// check that the file is what is expected
if err := isValidChecksum(v.checksum, newFilePath); err != nil {
return err
}
oldFilePath := fmt.Sprintf("%s.old", v.targetPath)
// Windows requires more effort to self update, especially when it is running as a service:
// you have to stop the service (if running as one) in order to move/rename the binary
// but now the binary isn't running though, so an external process
// has to move the old binary out and the new one in then start the service
// the easiest way to do this is with a batch file (or with a DLL, but that gets ugly for a cross compiled binary like cloudflared)
// a batch file isn't ideal, but it is the simplest path forward for the constraints Windows creates
if runtime.GOOS == "windows" {
if err := writeBatchFile(v.targetPath, newFilePath, oldFilePath); err != nil {
return err
}
rootDir := filepath.Dir(v.targetPath)
batchPath := filepath.Join(rootDir, batchFileName)
return runWindowsBatch(batchPath)
}
// now move the current file out, move the new file in and delete the old file
if err := os.Rename(v.targetPath, oldFilePath); err != nil {
return err
}
if err := os.Rename(newFilePath, v.targetPath); err != nil {
//attempt rollback
os.Rename(oldFilePath, v.targetPath)
return err
}
os.Remove(oldFilePath)
return nil
}
// String returns the version number of this update/release (e.g. 2020.08.05)
func (v *WorkersVersion) Version() string {
return v.version
}
// String returns a possible message to convey back to user after having checked in with the Updater Service. E.g.
// it can warn about the need to update the version currently running.
func (v *WorkersVersion) UserMessage() string {
return v.userMessage
}
// download the file from the link in the json
func download(url, filepath string, isCompressed bool) error {
client := &http.Client{
Timeout: clientTimeout,
}
resp, err := client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
var r io.Reader
r = resp.Body
// compressed macos binary, need to decompress
if isCompressed || isCompressedFile(url) {
// first the gzip reader
gr, err := gzip.NewReader(resp.Body)
if err != nil {
return err
}
defer gr.Close()
// now the tar
tr := tar.NewReader(gr)
// advance the reader pass the header, which will be the single binary file
tr.Next()
r = tr
}
out, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, r)
return err
}
// isCompressedFile is a really simple file extension check to see if this is a macos tar and gzipped
func isCompressedFile(urlstring string) bool {
if strings.HasSuffix(urlstring, ".tgz") {
return true
}
u, err := url.Parse(urlstring)
if err != nil {
return false
}
return strings.HasSuffix(u.Path, ".tgz")
}
// checks if the checksum in the json response matches the checksum of the file download
func isValidChecksum(checksum, filePath string) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
hash := fmt.Sprintf("%x", h.Sum(nil))
if checksum != hash {
return errors.New("checksum validation failed")
}
return nil
}
// writeBatchFile writes a batch file out to disk
// see the dicussion on why it has to be done this way
func writeBatchFile(targetPath string, newPath string, oldPath string) error {
os.Remove(batchFileName) //remove any failed updates before download
f, err := os.Create(batchFileName)
if err != nil {
return err
}
defer f.Close()
cfdName := filepath.Base(targetPath)
oldName := filepath.Base(oldPath)
data := batchData{
TargetPath: targetPath,
OldName: oldName,
NewPath: newPath,
OldPath: oldPath,
BinaryName: cfdName,
BatchName: batchFileName,
}
t, err := template.New("batch").Parse(windowsUpdateCommandTemplate)
if err != nil {
return err
}
return t.Execute(f, data)
}
// run each OS command for windows
func runWindowsBatch(batchFile string) error {
cmd := exec.Command("cmd", "/c", batchFile)
return cmd.Start()
}
+115 -71
View File
@@ -1,3 +1,4 @@
//go:build windows
// +build windows
package main
@@ -8,21 +9,25 @@ package main
import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
cli "gopkg.in/urfave/cli.v2"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
)
const (
windowsServiceName = "Cloudflared"
windowsServiceDescription = "Argo Tunnel agent"
windowsServiceUrl = "https://developers.cloudflare.com/argo-tunnel/reference/service/"
windowsServiceDescription = "Cloudflare Tunnel agent"
windowsServiceUrl = "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service#windows"
recoverActionDelay = time.Second * 20
failureCountResetPeriod = time.Hour * 24
@@ -30,97 +35,138 @@ const (
// not defined in golang.org/x/sys/windows package
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681988(v=vs.85).aspx
serviceConfigFailureActionsFlag = 4
// ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
// https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1000-1299-
serviceControllerConnectionFailure = 1063
LogFieldWindowsServiceName = "windowsServiceName"
)
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Argo Tunnel Windows service",
Usage: "Manages the Cloudflare Tunnel Windows service",
Subcommands: []*cli.Command{
&cli.Command{
{
Name: "install",
Usage: "Install Argo Tunnel as a Windows service",
Action: installWindowsService,
Usage: "Install Cloudflare Tunnel as a Windows service",
Action: cliutil.ConfiguredAction(installWindowsService),
},
&cli.Command{
{
Name: "uninstall",
Usage: "Uninstall the Argo Tunnel service",
Action: uninstallWindowsService,
Usage: "Uninstall the Cloudflare Tunnel service",
Action: cliutil.ConfiguredAction(uninstallWindowsService),
},
},
})
// `IsAnInteractiveSession()` isn't exactly equivalent to "should the
// process run as a normal EXE?" There are legitimate non-service cases,
// like running cloudflared in a GCP startup script, for which
// `IsAnInteractiveSession()` returns false. For more context, see:
// https://github.com/judwhite/go-svc/issues/6
// It seems that the "correct way" to check "is this a normal EXE?" is:
// 1. attempt to connect to the Service Control Manager
// 2. get ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
// This involves actually trying to start the service.
log := logger.Create(nil)
isIntSess, err := svc.IsAnInteractiveSession()
if err != nil {
logger.Fatalf("failed to determine if we are running in an interactive session: %v", err)
log.Fatal().Err(err).Msg("failed to determine if we are running in an interactive session")
}
if isIntSess {
app.Run(os.Args)
return
}
// Run executes service name by calling windowsService which is a Handler
// interface that implements Execute method.
// It will set service status to stop after Execute returns
err = svc.Run(windowsServiceName, &windowsService{app: app, graceShutdownC: graceShutdownC})
if err != nil {
if errno, ok := err.(syscall.Errno); ok && int(errno) == serviceControllerConnectionFailure {
// Hack: assume this is a false negative from the IsAnInteractiveSession() check above.
// Run the app in "interactive" mode anyway.
app.Run(os.Args)
return
}
log.Fatal().Err(err).Msgf("%s service failed", windowsServiceName)
}
}
type windowsService struct {
app *cli.App
graceShutdownC chan struct{}
}
// Execute is called by the service manager when service starts, the state
// of the service will be set to Stopped when this function returns.
func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
log := logger.Create(nil)
elog, err := eventlog.Open(windowsServiceName)
if err != nil {
logger.WithError(err).Errorf("Cannot open event log for %s", windowsServiceName)
log.Err(err).Msgf("Cannot open event log for %s", windowsServiceName)
return
}
defer elog.Close()
elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
// Run executes service name by calling windowsService which is a Handler
// interface that implements Execute method.
// It will set service status to stop after Execute returns
err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog, shutdownC: shutdownC, graceShutdownC: graceShutdownC})
if err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err))
return
defer func() {
elog.Info(1, fmt.Sprintf("%s service stopped", windowsServiceName))
}()
// the arguments passed here are only meaningful if they were manually
// specified by the user, e.g. using the Services console or `sc start`.
// https://docs.microsoft.com/en-us/windows/desktop/services/service-entry-point
// https://stackoverflow.com/a/6235139
var args []string
if len(serviceArgs) > 1 {
args = serviceArgs
} else {
// fall back to the arguments from ImagePath (or, as sc calls it, binPath)
args = os.Args
}
elog.Info(1, fmt.Sprintf("%s service stopped", windowsServiceName))
}
elog.Info(1, fmt.Sprintf("%s service arguments: %v", windowsServiceName, args))
type windowsService struct {
app *cli.App
elog *eventlog.Log
shutdownC chan struct{}
graceShutdownC chan struct{}
}
// called by the package code at the start of the service
func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
statusChan <- svc.Status{State: svc.StartPending}
errC := make(chan error)
go func() {
errC <- s.app.Run(args)
}()
statusChan <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
statusChan <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
for {
select {
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
s.elog.Info(1, fmt.Sprintf("control request 1 #%d", c))
statusChan <- c.CurrentStatus
case svc.Stop:
s.elog.Info(1, "received stop control request")
close(s.graceShutdownC)
statusChan <- svc.Status{State: svc.StopPending}
case svc.Shutdown:
s.elog.Info(1, "received shutdown control request")
close(s.shutdownC)
case svc.Stop, svc.Shutdown:
if s.graceShutdownC != nil {
// start graceful shutdown
elog.Info(1, "cloudflared starting graceful shutdown")
close(s.graceShutdownC)
s.graceShutdownC = nil
statusChan <- svc.Status{State: svc.StopPending}
continue
}
// repeated attempts at graceful shutdown forces immediate stop
elog.Info(1, "cloudflared terminating immediately")
statusChan <- svc.Status{State: svc.StopPending}
return false, 0
default:
s.elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
case err := <-errC:
ssec = true
if err != nil {
s.elog.Error(1, fmt.Sprintf("cloudflared terminated with error %v", err))
elog.Error(1, fmt.Sprintf("cloudflared terminated with error %v", err))
ssec = true
errno = 1
} else {
s.elog.Info(1, "cloudflared terminated without error")
elog.Info(1, "cloudflared terminated without error")
errno = 0
}
return
@@ -129,70 +175,68 @@ func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, stat
}
func installWindowsService(c *cli.Context) error {
logger.Infof("Installing Argo Tunnel Windows service")
zeroLogger := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
zeroLogger.Info().Msg("Installing Cloudflare Tunnel Windows service")
exepath, err := os.Executable()
if err != nil {
logger.Errorf("Cannot find path name that start the process")
return err
return errors.Wrap(err, "Cannot find path name that start the process")
}
m, err := mgr.Connect()
if err != nil {
logger.WithError(err).Errorf("Cannot establish a connection to the service control manager")
return err
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
}
defer m.Disconnect()
s, err := m.OpenService(windowsServiceName)
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
if err == nil {
s.Close()
logger.Errorf("service %s already exists", windowsServiceName)
return fmt.Errorf("service %s already exists", windowsServiceName)
return fmt.Errorf("Service %s already exists", windowsServiceName)
}
config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription}
s, err = m.CreateService(windowsServiceName, exepath, config)
if err != nil {
logger.Errorf("Cannot install service %s", windowsServiceName)
return err
return errors.Wrap(err, "Cannot install service")
}
defer s.Close()
logger.Infof("Argo Tunnel agent service is installed")
log.Info().Msg("Cloudflare Tunnel agent service is installed")
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
logger.WithError(err).Errorf("Cannot install event logger")
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
return errors.Wrap(err, "Cannot install event logger")
}
err = configRecoveryOption(s.Handle)
if err != nil {
logger.WithError(err).Errorf("Cannot set service recovery actions")
logger.Infof("See %s to manually configure service recovery actions", windowsServiceUrl)
log.Err(err).Msg("Cannot set service recovery actions")
log.Info().Msgf("See %s to manually configure service recovery actions", windowsServiceUrl)
}
return nil
}
func uninstallWindowsService(c *cli.Context) error {
logger.Infof("Uninstalling Argo Tunnel Windows Service")
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog).
With().
Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
log.Info().Msg("Uninstalling Cloudflare Tunnel Windows Service")
m, err := mgr.Connect()
if err != nil {
logger.Errorf("Cannot establish a connection to the service control manager")
return err
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
}
defer m.Disconnect()
s, err := m.OpenService(windowsServiceName)
if err != nil {
logger.Errorf("service %s is not installed", windowsServiceName)
return fmt.Errorf("service %s is not installed", windowsServiceName)
return fmt.Errorf("Service %s is not installed", windowsServiceName)
}
defer s.Close()
err = s.Delete()
if err != nil {
logger.Errorf("Cannot delete service %s", windowsServiceName)
return err
return errors.Wrap(err, "Cannot delete service")
}
logger.Infof("Argo Tunnel agent service is uninstalled")
log.Info().Msg("Cloudflare Tunnel agent service is uninstalled")
err = eventlog.Remove(windowsServiceName)
if err != nil {
logger.Errorf("Cannot remove event logger")
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
return errors.Wrap(err, "Cannot remove event logger")
}
return nil
}
-148
View File
@@ -1,148 +0,0 @@
package sqlgateway
import (
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strings"
"time"
_ "github.com/lib/pq"
cli "gopkg.in/urfave/cli.v2"
"github.com/elgs/gosqljson"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type Message struct {
Connection Connection `json:"connection"`
Command string `json:"command"`
Params []interface{} `json:"params"`
}
type Connection struct {
SSLMode string `json:"sslmode"`
Token string `json:"token"`
}
type Response struct {
Columns []string `json:"columns"`
Rows [][]string `json:"rows"`
Error string `json:"error"`
}
type Proxy struct {
Context *cli.Context
Router *mux.Router
Token string
User string
Password string
Driver string
Database string
Logger *logrus.Logger
}
func StartProxy(c *cli.Context, logger *logrus.Logger, password string) error {
proxy := NewProxy(c, logger, password)
logger.Infof("Starting SQL Gateway Proxy on port %s", strings.Split(c.String("url"), ":")[1])
err := http.ListenAndServe(":"+strings.Split(c.String("url"), ":")[1], proxy.Router)
if err != nil {
return err
}
return nil
}
func randID(n int, c *cli.Context) string {
charBytes := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
b := make([]byte, n)
for i := range b {
b[i] = charBytes[rand.Intn(len(charBytes))]
}
return fmt.Sprintf("%s&%s", c.String("hostname"), b)
}
// db://user@dbname
func parseInfo(input string) (string, string, string) {
p1 := strings.Split(input, "://")
p2 := strings.Split(p1[1], "@")
return p1[0], p2[0], p2[1]
}
func NewProxy(c *cli.Context, logger *logrus.Logger, pass string) *Proxy {
rand.Seed(time.Now().UnixNano())
driver, user, dbname := parseInfo(c.String("address"))
proxy := Proxy{
Context: c,
Router: mux.NewRouter(),
Token: randID(64, c),
Logger: logger,
User: user,
Password: pass,
Database: dbname,
Driver: driver,
}
logger.Info(fmt.Sprintf(`
--------------------
SQL Gateway Proxy
Token: %s
--------------------
`, proxy.Token))
proxy.Router.HandleFunc("/", proxy.proxyRequest).Methods("POST")
return &proxy
}
func (proxy *Proxy) proxyRequest(rw http.ResponseWriter, req *http.Request) {
var message Message
response := Response{}
err := json.NewDecoder(req.Body).Decode(&message)
if err != nil {
proxy.Logger.Error(err)
http.Error(rw, fmt.Sprintf("400 - %s", err.Error()), http.StatusBadRequest)
return
}
if message.Connection.Token != proxy.Token {
proxy.Logger.Error("Invalid token")
http.Error(rw, "400 - Invalid token", http.StatusBadRequest)
return
}
connStr := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s", proxy.User, proxy.Password, proxy.Database, message.Connection.SSLMode)
db, err := sql.Open(proxy.Driver, connStr)
defer db.Close()
if err != nil {
proxy.Logger.Error(err)
http.Error(rw, fmt.Sprintf("400 - %s", err.Error()), http.StatusBadRequest)
return
} else {
proxy.Logger.Info("Forwarding SQL: ", message.Command)
rw.Header().Set("Content-Type", "application/json")
headers, data, err := gosqljson.QueryDbToArray(db, "lower", message.Command, message.Params...)
if err != nil {
proxy.Logger.Error(err)
http.Error(rw, fmt.Sprintf("400 - %s", err.Error()), http.StatusBadRequest)
return
} else {
response = Response{headers, data, ""}
}
}
json.NewEncoder(rw).Encode(response)
}

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