Compare commits

..

43 Commits

Author SHA1 Message Date
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
730 changed files with 76668 additions and 20450 deletions
+4 -1
View File
@@ -9,5 +9,8 @@ guide/public
\#*\#
cscope.*
cloudflared
cloudflared.exe
!cmd/cloudflared/
.DS_Store
.DS_Store
*-session.log
ssh_server_tests/.env
Generated
+59 -86
View File
@@ -18,7 +18,7 @@
revision = "648efa622239a2f6ff949fed78ee37b48d499ba4"
[[projects]]
digest = "1:4a296b8f487dab7ea37c4558d6e84be8103eb6bd297202e870266ff99ebd769d"
digest = "1:1df548d2da31574a48a7e2adbd228fe2171a7d5f1b4a2d7ca367cc57436c706a"
name = "github.com/aws/aws-sdk-go"
packages = [
"aws",
@@ -42,6 +42,7 @@
"internal/ini",
"internal/s3err",
"internal/sdkio",
"internal/sdkmath",
"internal/sdkrand",
"internal/sdkuri",
"internal/shareddefaults",
@@ -59,16 +60,16 @@
"service/sts/stsiface",
]
pruneopts = "UT"
revision = "2eb9a651f22769d792f974e8a8efe16afea3370f"
version = "v1.23.9"
revision = "6bb638a9cf6177e3cc9748a95a2410bb26f1f426"
version = "v1.25.8"
[[projects]]
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "UT"
revision = "4b2b341e8d7715fae06375aa633dbb6e91b3fb46"
version = "v1.0.0"
revision = "37c8de3658fcb183f997c4e13e8337516ab753e6"
version = "v1.0.1"
[[projects]]
digest = "1:fed1f537c2f1269fe475a8556c393fe466641682d73ef8fd0491cd3aa1e47bad"
@@ -137,16 +138,8 @@
name = "github.com/coreos/go-systemd"
packages = ["daemon"]
pruneopts = "UT"
revision = "95778dfbb74eb7e4dbaf43bf7d71809650ef8076"
version = "v19"
[[projects]]
digest = "1:385dff5bf83bf1c9d40ef2b0f2e697f0d1973fc9e24ae9401cf41698612bbebc"
name = "github.com/creack/pty"
packages = ["."]
pruneopts = "UT"
revision = "2769f65a3a94eb8f876f44a0459d24ae7ad2e488"
version = "v1.1.7"
revision = "e64a0ec8b42a61e2a9801dc1d0abe539dea79197"
version = "v20"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
@@ -202,12 +195,11 @@
revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa"
[[projects]]
digest = "1:0a01355005024757ae2a1f62e8fe68a30b4f5fde7a08ac88b3685e7999ac354b"
digest = "1:9f9a357116172beefdf218cada11a5123d84c6975c24a4380c75354c703ae0cf"
name = "github.com/gliderlabs/ssh"
packages = ["."]
pruneopts = "UT"
revision = "5b6cc7030f17095c0cf23bb063b0bfe824fe5f8b"
version = "v0.2.2"
revision = "63518b5243e0fe306cc35cb382f459de123e550e"
[[projects]]
branch = "master"
@@ -218,7 +210,7 @@
revision = "604e922904d35e97f98a774db7881f049cd8d970"
[[projects]]
digest = "1:239c4c7fd2159585454003d9be7207167970194216193a8a210b8d29576f19c9"
digest = "1:f5ce1529abc1204444ec73779f44f94e2fa8fcdb7aca3c355b0c95947e4005c6"
name = "github.com/golang/protobuf"
packages = [
"proto",
@@ -228,8 +220,8 @@
"ptypes/timestamp",
]
pruneopts = "UT"
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
version = "v1.3.1"
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
version = "v1.3.2"
[[projects]]
digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b"
@@ -240,12 +232,12 @@
version = "v1.1.1"
[[projects]]
digest = "1:d5f97fc268267ec1b61c3453058c738246fc3e746f14b1ae25161513b7367b0c"
digest = "1:cbec35fe4d5a4fba369a656a8cd65e244ea2c743007d8f6c1ccb132acf9d1296"
name = "github.com/gorilla/mux"
packages = ["."]
pruneopts = "UT"
revision = "c5c6c98bc25355028a63748a498942a6398ccd22"
version = "v1.7.1"
revision = "00bdffe0f3c77e27d2cf6f5c70232a2d3e4d9c15"
version = "v1.7.3"
[[projects]]
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
@@ -270,17 +262,6 @@
pruneopts = "UT"
revision = "c2b33e84"
[[projects]]
digest = "1:6c41d4f998a03b6604227ccad36edaed6126c397e5d78709ef4814a1145a6757"
name = "github.com/jmoiron/sqlx"
packages = [
".",
"reflectx",
]
pruneopts = "UT"
revision = "d161d7a76b5661016ad0b085869f77fd410f3e6a"
version = "v1.2.0"
[[projects]]
digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
name = "github.com/konsorten/go-windows-terminal-sequences"
@@ -290,7 +271,7 @@
version = "v1.0.2"
[[projects]]
digest = "1:bc1c0be40c67b6b4aee09d7508d5a2a52c1c116b1fa43806dad2b0d6b4d4003b"
digest = "1:12cb143f2148bf54bcd9fe622abac17325e85eeb1d84b8ec6caf1c80232108fd"
name = "github.com/lib/pq"
packages = [
".",
@@ -298,24 +279,24 @@
"scram",
]
pruneopts = "UT"
revision = "51e2106eed1cea199c802d2a49e91e2491b02056"
version = "v1.1.0"
revision = "3427c32cb71afc948325f299f040e53c1dd78979"
version = "v1.2.0"
[[projects]]
digest = "1:2fa7b0155cd54479a755c629de26f888a918e13f8857a2c442205d825368e084"
digest = "1:4a29eeb25603debe8f2098a9902c4d3851034cf70d33be428826e86e8c30a1b0"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "3a70a971f94a22f2fa562ffcc7a0eb45f5daf045"
version = "v0.1.1"
revision = "98ec13f34aabf44cc914c65a1cfb7b9bc815aef1"
version = "v0.1.4"
[[projects]]
digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d"
digest = "1:d62282425ffb75047679d7e2c3b980eea7f82c05ef5fb9142ee617ebac6e7432"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7"
version = "v0.0.7"
revision = "88ba11cfdc67c7588b30042edf244b2875f892b6"
version = "v0.0.10"
[[projects]]
digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc"
@@ -352,14 +333,6 @@
revision = "af06845cf3004701891bf4fdb884bfe4920b3727"
version = "v1.1.0"
[[projects]]
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
version = "v1.1.2"
[[projects]]
digest = "1:11e62d6050198055e6cd87ed57e5d8c669e84f839c16e16f192374d913d1a70d"
name = "github.com/opentracing/opentracing-go"
@@ -405,10 +378,10 @@
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "UT"
revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
revision = "14fe0d1b01d4d5fc031dd4bec1823bd3ebbe8016"
[[projects]]
digest = "1:35cf6bdf68db765988baa9c4f10cc5d7dda1126a54bd62e252dbcd0b1fc8da90"
digest = "1:f119e3205d3a1f0f19dbd7038eb37528e2c6f0933269dc344e305951fb87d632"
name = "github.com/prometheus/common"
packages = [
"expfmt",
@@ -416,16 +389,20 @@
"model",
]
pruneopts = "UT"
revision = "a82f4c12f983cc2649298185f296632953e50d3e"
version = "v0.3.0"
revision = "287d3e634a1e550c9e463dd7e5a75a422c614505"
version = "v0.7.0"
[[projects]]
branch = "master"
digest = "1:49b09905e781d7775c086604cc00083e1832d0783f1f421b79f42657c457d029"
digest = "1:a210815b437763623ecca8eb91e6a0bf4f2d6773c5a6c9aec0e28f19e5fd6deb"
name = "github.com/prometheus/procfs"
packages = ["."]
packages = [
".",
"internal/fs",
"internal/util",
]
pruneopts = "UT"
revision = "8368d24ba045f26503eb745b624d930cbe214c79"
revision = "499c85531f756d1129edd26485a5f73871eeb308"
version = "v0.0.5"
[[projects]]
digest = "1:1a23fdd843129ef761ffe7651bc5fe7c5b09fbe933e92783ab06cc11c37b7b37"
@@ -436,12 +413,9 @@
version = "v2.4"
[[projects]]
digest = "1:31538f8d774e8bf2fb9380d2d2a82d69e206a7d0603946586926f1819c546c26"
digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
name = "github.com/sirupsen/logrus"
packages = [
".",
"hooks/test",
]
packages = ["."]
pruneopts = "UT"
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.2"
@@ -459,7 +433,7 @@
[[projects]]
branch = "master"
digest = "1:a84d5ec8b40a827962ea250f2cf03434138ccae9d83fcac12fb49b70c70b80cc"
digest = "1:d9e4eb3f377dca21b3be66fc471106c3150cdbe43f1fba67fe79f6321b9731a8"
name = "golang.org/x/crypto"
packages = [
"curve25519",
@@ -475,11 +449,11 @@
"ssh/terminal",
]
pruneopts = "UT"
revision = "f416ebab96af27ca70b6e5c23d6a0747530da626"
revision = "34f69633bfdcf9db92f698f8487115767eebef81"
[[projects]]
branch = "master"
digest = "1:52d140f7ab52e491cc1cbc93e6637aa5e9a7f3beae7545d675b02e52ca9d7290"
digest = "1:ded5b1b804f079c993f887e0da394aa54cdf8d2a6a31e62c66d94ebe6f75bc2a"
name = "golang.org/x/net"
packages = [
"bpf",
@@ -497,7 +471,7 @@
"websocket",
]
pruneopts = "UT"
revision = "1da14a5a36f220ea3f03470682b737b1dfd5de22"
revision = "72f939374954d0a1b2a1e27dcda950e2724dd5c1"
[[projects]]
digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239"
@@ -508,7 +482,7 @@
[[projects]]
branch = "master"
digest = "1:77751d02e939d7078faedaeec10c09af575a09c528d84d18f2cb45a84bd1889a"
digest = "1:122914ab6dc8ee219eeb0d11276704e7ac3854feb55dad10f71081b9639ed28e"
name = "golang.org/x/sys"
packages = [
"cpu",
@@ -520,16 +494,18 @@
"windows/svc/mgr",
]
pruneopts = "UT"
revision = "12500544f89f9420afe9529ba8940bf72d294972"
revision = "543471e840be449c53d44b32c7adf1261ad67e37"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/language",
"internal/language/compact",
"internal/tag",
"internal/triegen",
"internal/ucd",
@@ -542,19 +518,19 @@
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
branch = "master"
digest = "1:c3076e7defee87de1236f1814beb588f40a75544c60121e6eb38b3b3721783e2"
digest = "1:583a0c80f5e3a9343d33aea4aead1e1afcc0043db66fdf961ddd1fe8cd3a4faf"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "d1146b9035b912113a38af3b138eb2af567b2c67"
revision = "a023cd5227bd25fd1f5c5633743ff3eacc93d169"
[[projects]]
digest = "1:31d87f39886fb38a2b6c097ff3b9f985d6960772170d64a68246f7790e955746"
digest = "1:6cd77d0b616d2dcebd363dfecba593f27b0151fc82cdb5fbfb96c5a7cfbc95b5"
name = "google.golang.org/grpc"
packages = [
".",
@@ -586,13 +562,14 @@
"resolver",
"resolver/dns",
"resolver/passthrough",
"serviceconfig",
"stats",
"status",
"tap",
]
pruneopts = "UT"
revision = "236199dd5f8031d698fb64091194aecd1c3895b2"
version = "v1.20.0"
revision = "f6d0f9ee430895e87ef1ceb5ac8f39725bafceef"
version = "v1.24.0"
[[projects]]
branch = "altsrc-parse-durations"
@@ -607,12 +584,12 @@
source = "https://github.com/cbranch/cli"
[[projects]]
digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96"
digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5"
version = "v2.2.4"
[[projects]]
digest = "1:8ffc3ddc31414c0a71220957bb723b16510d7fcb5b3880dc0da4cf6d39c31642"
@@ -642,7 +619,6 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/DATA-DOG/go-sqlmock",
"github.com/aws/aws-sdk-go/aws",
"github.com/aws/aws-sdk-go/aws/credentials",
"github.com/aws/aws-sdk-go/aws/session",
@@ -658,7 +634,6 @@
"github.com/coredns/coredns/request",
"github.com/coreos/go-oidc/jose",
"github.com/coreos/go-systemd/daemon",
"github.com/creack/pty",
"github.com/elgs/gosqljson",
"github.com/equinox-io/equinox",
"github.com/facebookgo/grace/gracenet",
@@ -672,13 +647,11 @@
"github.com/mattn/go-colorable",
"github.com/miekg/dns",
"github.com/mitchellh/go-homedir",
"github.com/mitchellh/mapstructure",
"github.com/pkg/errors",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/rifflock/lfshook",
"github.com/sirupsen/logrus",
"github.com/sirupsen/logrus/hooks/test",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"golang.org/x/crypto/nacl/box",
+1 -9
View File
@@ -86,17 +86,9 @@
name = "github.com/google/uuid"
version = "=1.1.1"
[[constraint]]
name = "github.com/mitchellh/mapstructure"
version = "1.1.2"
[[constraint]]
name = "github.com/gliderlabs/ssh"
version = "0.2.2"
[[constraint]]
name = "github.com/creack/pty"
version = "1.1.7"
revision = "63518b5243e0fe306cc35cb382f459de123e550e"
[[constraint]]
name = "github.com/aws/aws-sdk-go"
+4
View File
@@ -39,6 +39,10 @@ container:
test: vet
go test -v -race $(VERSION_FLAGS) ./...
.PHONY: test-ssh-server
test-ssh-server:
docker-compose -f ssh_server_tests/docker-compose.yml up
.PHONY: cloudflared-deb
cloudflared-deb: cloudflared
mkdir -p $(PACKAGE_DIR)
+49
View File
@@ -1,3 +1,52 @@
2019.10.3
- 2019-10-18 Fix #129: Excessive memory usage streaming large files (#142)
2019.10.2
- 2019-10-17 AUTH-2167: Adds CLI option for host key directory
2019.10.1
- 2019-10-17 Adds variable to fix windows build
2019.10.0
- 2019-10-11 AUTH-2105: Dont require --destination arg
- 2019-10-14 TUN-2344: log more details: http2.Framer.ErrorDetail() if available, connectionID
- 2019-10-16 AUTH-2159: Moves shutdownC close into error handling AUTH-2161: Lowers size of preamble length AUTH-2160: Fixes url parsing logic
- 2019-10-16 AUTH-2135: Adds support for IPv6 and tests
- 2019-10-02 AUTH-2105: Adds support for local forwarding. Refactor auditlogger creation. AUTH-2088: Adds dynamic destination routing
- 2019-10-09 AUTH-2114: Uses short lived cert auth for outgoing client connection
- 2019-09-30 AUTH-2089: Revise ssh server to function as a proxy
2019.9.2
- 2019-09-26 TUN-2355: Roll back TUN-2276
2019.9.1
- 2019-09-23 TUN-2334: remove tlsConfig.ServerName special case
- 2019-09-23 AUTH-2077: Quotes open browser command in windows
- 2019-09-11 AUTH-2050: Adds time.sleep to temporarily avoid hitting tunnel muxer dealock issue
- 2019-09-10 AUTH-2056: Writes stderr to its own stream for non-pty connections
- 2019-09-16 TUN-2307: Capnp is the only serialization format used in tunnelpogs
- 2019-09-18 TUN-2315: Replace Scope with IntentLabel
- 2019-09-17 TUN-2309: Split ConnectResult into ConnectError and ConnectSuccess, each implementing its own capnp serialization logic
- 2019-09-18 AUTH-2052: Adds tests for SSH server
- 2019-09-18 AUTH-2067: Log commands correctly
- 2019-09-19 AUTH-2055: Verifies token at edge on access login
- 2019-09-04 TUN-2201: change SRV records used by cloudflared
- 2019-09-06 TUN-2280: Revert "TUN-2260: add name/group to CapnpConnectParameters, remove Scope"
- 2019-09-03 AUTH-1943 hooked up uploader to logger, added timestamp to session logs, add tests
- 2019-09-04 AUTH-2036: Refactor user retrieval, shutdown after ssh server stops, add custom version string
- 2019-09-06 AUTH-1942 added event log to ssh server
- 2019-09-04 AUTH-2037: Adds support for ssh port forwarding
- 2019-09-05 TUN-2276: Path encoding broken
2019.9.0
- 2019-09-05 TUN-2279: Revert path encoding fix
- 2019-08-30 AUTH-2021 - check error for failing tests
- 2019-08-29 AUTH-2030: Support both authorized_key and short lived cert authentication simultaniously without specifiying at start time
- 2019-08-29 AUTH-2026: Adds support for non-pty sessions and inline command exec
- 2019-08-26 AUTH-1943: Adds session logging
- 2019-08-26 TUN-2162: Decomplect OpenStream to allow finer-grained timeouts
- 2019-08-29 TUN-2260: add name/group to CapnpConnectParameters, remove Scope
2019.8.4
- 2019-08-30 Fix #111: Add support for specifying a specific HTTP Host: header on the origin. (#114)
- 2019-08-22 TUN-2165: Add ClientConfig to tunnelrpc.ConnectResult
+2 -2
View File
@@ -69,7 +69,7 @@ func (m *DirectoryUploadManager) run() {
// sweep the directory and kick off uploads
func (m *DirectoryUploadManager) sweep() {
filepath.Walk(m.rootDirectory, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
if err != nil || info.IsDir() {
return nil
}
//30 days ago
@@ -77,7 +77,7 @@ func (m *DirectoryUploadManager) sweep() {
checkTime := time.Now().Add(-time.Duration(retentionTime))
//delete the file it is stale
if info.ModTime().After(checkTime) {
if info.ModTime().Before(checkTime) {
os.Remove(path)
return nil
}
+7 -7
View File
@@ -114,7 +114,7 @@ func createWebsocketStream(options *StartOptions) (*cloudflaredWebsocket.Conn, e
wsConn, resp, err := cloudflaredWebsocket.ClientConnect(req, nil)
defer closeRespBody(resp)
if err != nil && isAccessResponse(resp) {
if err != nil && IsAccessResponse(resp) {
wsConn, err = createAccessAuthenticatedStream(options)
if err != nil {
return nil, err
@@ -126,10 +126,10 @@ func createWebsocketStream(options *StartOptions) (*cloudflaredWebsocket.Conn, e
return &cloudflaredWebsocket.Conn{Conn: wsConn}, nil
}
// isAccessResponse checks the http Response to see if the url location
// 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 <= 300 {
func IsAccessResponse(resp *http.Response) bool {
if resp == nil || resp.StatusCode != http.StatusFound {
return false
}
@@ -156,7 +156,7 @@ func createAccessAuthenticatedStream(options *StartOptions) (*websocket.Conn, er
return wsConn, nil
}
if !isAccessResponse(resp) {
if !IsAccessResponse(resp) {
return nil, err
}
@@ -179,7 +179,7 @@ func createAccessAuthenticatedStream(options *StartOptions) (*websocket.Conn, er
// createAccessWebSocketStream builds an Access request and makes a connection
func createAccessWebSocketStream(options *StartOptions) (*websocket.Conn, *http.Response, error) {
req, err := buildAccessRequest(options)
req, err := BuildAccessRequest(options)
if err != nil {
return nil, nil, err
}
@@ -187,7 +187,7 @@ func createAccessWebSocketStream(options *StartOptions) (*websocket.Conn, *http.
}
// buildAccessRequest builds an HTTP request with the Access token set
func buildAccessRequest(options *StartOptions) (*http.Request, error) {
func BuildAccessRequest(options *StartOptions) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
+4 -4
View File
@@ -102,14 +102,14 @@ func TestIsAccessResponse(t *testing.T) {
ExpectedOut bool
}{
{"nil response", nil, false},
{"redirect with no location", &http.Response{StatusCode: http.StatusPermanentRedirect}, 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.StatusPermanentRedirect, Header: validLocationHeader}, true},
{"redirect with invalid location", &http.Response{StatusCode: http.StatusPermanentRedirect, Header: invalidLocationHeader}, 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 {
if IsAccessResponse(tc.In) != tc.ExpectedOut {
t.Fatalf("Failed case %d -- %s", i, tc.Description)
}
}
+5
View File
@@ -34,6 +34,11 @@ func ssh(c *cli.Context) error {
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
}
destination := c.String(sshDestinationFlag)
if destination != "" {
headers.Add("CF-Access-SSH-Destination", destination)
}
options := &carrier.StartOptions{
OriginURL: originURL,
Headers: headers,
+73 -4
View File
@@ -1,17 +1,20 @@
package access
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"text/template"
"time"
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
"github.com/cloudflare/cloudflared/sshgen"
"github.com/cloudflare/cloudflared/validation"
"github.com/pkg/errors"
"golang.org/x/net/idna"
"github.com/cloudflare/cloudflared/log"
@@ -21,6 +24,7 @@ import (
const (
sshHostnameFlag = "hostname"
sshDestinationFlag = "destination"
sshURLFlag = "url"
sshHeaderFlag = "header"
sshTokenIDFlag = "service-token-id"
@@ -124,6 +128,10 @@ func Commands() []*cli.Command {
Name: sshHostnameFlag,
Usage: "specify the hostname of your application.",
},
&cli.StringFlag{
Name: sshDestinationFlag,
Usage: "specify the destination address of your SSH server.",
},
&cli.StringFlag{
Name: sshURLFlag,
Usage: "specify the host:port to forward data to Cloudflare edge.",
@@ -188,9 +196,14 @@ func login(c *cli.Context) error {
logger.Errorf("Please provide the url of the Access application\n")
return err
}
token, err := token.FetchToken(appURL)
if err != nil {
logger.Errorf("Failed to fetch token: %s\n", err)
if err := verifyTokenAtEdge(appURL, c); err != nil {
logger.WithError(err).Error("Could not verify token")
return err
}
token, err := token.GetTokenIfExists(appURL)
if err != nil || token == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application.")
return err
}
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", string(token))
@@ -372,3 +385,59 @@ func isFileThere(candidate string) bool {
}
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, c *cli.Context) error {
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) {
headers.Add("CF-Access-Client-Id", c.String(sshTokenIDFlag))
}
if c.IsSet(sshTokenSecretFlag) {
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
}
options := &carrier.StartOptions{OriginURL: appUrl.String(), Headers: headers}
if valid, err := isTokenValid(options); err != nil {
return err
} else if valid {
return nil
}
if err := token.RemoveTokenIfExists(appUrl); err != nil {
return err
}
if valid, err := isTokenValid(options); 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) (bool, error) {
req, err := carrier.BuildAccessRequest(options)
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
}
@@ -0,0 +1,11 @@
//+build darwin
package shell
import (
"os/exec"
)
func getBrowserCmd(url string) *exec.Cmd {
return exec.Command("open", url)
}
@@ -0,0 +1,11 @@
//+build !windows,!darwin,!linux,!netbsd,!freebsd,!openbsd
package shell
import (
"os/exec"
)
func getBrowserCmd(url string) *exec.Cmd {
return nil
}
@@ -0,0 +1,11 @@
//+build linux freebsd openbsd netbsd
package shell
import (
"os/exec"
)
func getBrowserCmd(url string) *exec.Cmd {
return exec.Command("xdg-open", url)
}
@@ -0,0 +1,18 @@
//+build windows
package shell
import (
"fmt"
"os/exec"
"syscall"
)
func getBrowserCmd(url string) *exec.Cmd {
cmd := exec.Command("cmd")
// CmdLine is only defined when compiling for windows.
// Empty string is the cmd proc "Title". Needs to be included because the start command will interpret the first
// quoted string as that field and we want to quote the URL.
cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: fmt.Sprintf(`/c start "" "%s"`, url)}
return cmd
}
+1 -15
View File
@@ -4,25 +4,11 @@ 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()
return getBrowserCmd(url).Start()
}
// Run will kick off a shell task and pipe the results to the respective std pipes
+1 -1
View File
@@ -18,7 +18,7 @@ import (
)
const (
baseStoreURL = "https://login.cloudflarewarp.com/"
baseStoreURL = "https://login.argotunnel.com/"
clientTimeout = time.Second * 60
)
+46 -42
View File
@@ -23,6 +23,7 @@ import (
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/signal"
"github.com/cloudflare/cloudflared/sshlog"
"github.com/cloudflare/cloudflared/sshserver"
"github.com/cloudflare/cloudflared/supervisor"
"github.com/cloudflare/cloudflared/tlsconfig"
@@ -44,14 +45,11 @@ import (
const (
sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878"
sshLogFileDirectory = "/var/log/cloudflared/"
sshLogFileDirectory = "/usr/local/var/log/cloudflared/"
// sshPortFlag is the port on localhost the cloudflared ssh server will run on
sshPortFlag = "local-ssh-port"
// shortLivedCertFlag enables short lived cert authentication
shortLivedCertFlag = "short-lived-certs"
// sshIdleTimeoutFlag defines the duration a SSH session can remain idle before being closed
sshIdleTimeoutFlag = "ssh-idle-timeout"
@@ -75,6 +73,11 @@ const (
// s3URLFlag is the S3 URL of SSH log uploader (e.g. don't use AWS s3 and use google storage bucket instead)
s3URLFlag = "s3-url-host"
// hostKeyPath is the path of the dir to save SSH host keys too
hostKeyPath = "host-key-path"
noIntentMsg = "The --intent argument is required. Cloudflared looks up an Intent to determine what configuration to use (i.e. which tunnels to start). If you don't have any Intents yet, you can use a placeholder Intent Label for now. Then, when you make an Intent with that label, cloudflared will get notified and open the tunnels you specified in that Intent."
)
var (
@@ -365,42 +368,53 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
if c.IsSet("ssh-server") {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
logger.Errorf("--ssh-server is not supported on %s", runtime.GOOS)
return errors.New(fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS))
msg := fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)
logger.Error(msg)
return errors.New(msg)
}
logger.Infof("ssh-server set")
logManager := sshlog.NewEmptyManager()
if c.IsSet(bucketNameFlag) && c.IsSet(regionNameFlag) && c.IsSet(accessKeyIDFlag) && c.IsSet(secretIDFlag) {
uploader, err := awsuploader.NewFileUploader(c.String(bucketNameFlag), c.String(regionNameFlag),
c.String(accessKeyIDFlag), c.String(secretIDFlag), c.String(sessionTokenIDFlag), c.String(s3URLFlag))
if err != nil {
logger.WithError(err).Error("Cannot create uploader for SSH Server")
return errors.Wrap(err, "Cannot create uploader for SSH Server")
msg := "Cannot create uploader for SSH Server"
logger.WithError(err).Error(msg)
return errors.Wrap(err, msg)
}
os.Mkdir(sshLogFileDirectory, 0600)
if err := os.MkdirAll(sshLogFileDirectory, 0700); err != nil {
msg := fmt.Sprintf("Cannot create SSH log file directory %s", sshLogFileDirectory)
logger.WithError(err).Errorf(msg)
return errors.Wrap(err, msg)
}
logManager = sshlog.New(sshLogFileDirectory)
uploadManager := awsuploader.NewDirectoryUploadManager(logger, uploader, sshLogFileDirectory, 30*time.Minute, shutdownC)
uploadManager.Start()
}
sshServerAddress := "127.0.0.1:" + c.String(sshPortFlag)
server, err := sshserver.New(logger, sshServerAddress, shutdownC, c.Bool(shortLivedCertFlag), c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag))
localServerAddress := "127.0.0.1:" + c.String(sshPortFlag)
server, err := sshserver.New(logManager, logger, version, localServerAddress, c.String("hostname"), c.Path(hostKeyPath), shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag))
if err != nil {
logger.WithError(err).Error("Cannot create new SSH Server")
return errors.Wrap(err, "Cannot create new SSH Server")
msg := "Cannot create new SSH Server"
logger.WithError(err).Error(msg)
return errors.Wrap(err, msg)
}
wg.Add(1)
go func() {
defer wg.Done()
if err = server.Start(); err != nil && err != ssh.ErrServerClosed {
logger.WithError(err).Error("SSH server error")
// TODO: remove when declarative tunnels are implemented.
close(shutdownC)
}
}()
c.Set("url", "ssh://"+sshServerAddress)
c.Set("url", "ssh://"+localServerAddress)
}
if host := hostnameFromURI(c.String("url")); host != "" {
@@ -508,21 +522,16 @@ func startDeclarativeTunnel(ctx context.Context,
return err
}
var scope pogs.Scope
if c.IsSet("group") == c.IsSet("system-name") {
err = fmt.Errorf("exactly one of --group or --system-name must be specified")
logger.WithError(err).Error("unable to determine scope")
return err
} else if c.IsSet("group") {
scope = pogs.NewGroup(c.String("group"))
} else {
scope = pogs.NewSystemName(c.String("system-name"))
intentLabel := c.String("intent")
if intentLabel == "" {
logger.Error("--intent was empty")
return fmt.Errorf(noIntentMsg)
}
cloudflaredConfig := &connection.CloudflaredConfig{
BuildInfo: buildInfo,
CloudflaredID: cloudflaredID,
Scope: scope,
IntentLabel: intentLabel,
Tags: tags,
}
@@ -946,15 +955,9 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "system-name",
Usage: "Unique identifier for this cloudflared instance. It can be configured individually in the Declarative Tunnel UI. Mutually exclusive with `--group`.",
EnvVars: []string{"TUNNEL_SYSTEM_NAME"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "group",
Usage: "Name of a group of cloudflared instances, of which this instance should be an identical copy. They can be configured collectively in the Declarative Tunnel UI. Mutually exclusive with `--system-name`.",
EnvVars: []string{"TUNNEL_GROUP"},
Name: "intent",
Usage: "The label of an Intent from which `cloudflared` should gets its tunnels from. Intents can be created in the Origin Registry UI.",
EnvVars: []string{"TUNNEL_INTENT"},
Hidden: true,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
@@ -967,16 +970,10 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
altsrc.NewStringFlag(&cli.StringFlag{
Name: sshPortFlag,
Usage: "Localhost port that cloudflared SSH server will run on",
Value: "22",
Value: "2222",
EnvVars: []string{"LOCAL_SSH_PORT"},
Hidden: true,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: shortLivedCertFlag,
Usage: "Enable short lived cert authentication for SSH server",
EnvVars: []string{"SHORT_LIVED_CERTS"},
Hidden: true,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: sshIdleTimeoutFlag,
Usage: "Connection timeout after no activity",
@@ -1025,5 +1022,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
EnvVars: []string{"S3_URL"},
Hidden: true,
}),
altsrc.NewPathFlag(&cli.PathFlag{
Name: hostKeyPath,
Usage: "Absolute path of directory to save SSH host keys in",
EnvVars: []string{"HOST_KEY_PATH"},
Hidden: true,
}),
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ import (
const (
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
callbackStoreURL = "https://login.cloudflarewarp.com/"
callbackStoreURL = "https://login.argotunnel.com/"
)
func login(c *cli.Context) error {
+1 -1
View File
@@ -50,7 +50,7 @@ func (c *Connection) Serve(ctx context.Context) error {
}
// Connect is used to establish connections with cloudflare's edge network
func (c *Connection) Connect(ctx context.Context, parameters *tunnelpogs.ConnectParameters, logger *logrus.Entry) (*pogs.ConnectResult, error) {
func (c *Connection) Connect(ctx context.Context, parameters *tunnelpogs.ConnectParameters, logger *logrus.Entry) (pogs.ConnectResult, error) {
openStreamCtx, cancel := context.WithTimeout(ctx, openStreamTimeout)
defer cancel()
+8 -8
View File
@@ -13,13 +13,13 @@ import (
)
const (
// Used to discover HA Warp servers
srvService = "warp"
// Used to discover HA origintunneld servers
srvService = "origintunneld"
srvProto = "tcp"
srvName = "cloudflarewarp.com"
srvName = "argotunnel.com"
// Used to fallback to DoT when we can't use the default resolver to
// discover HA Warp servers (GitHub issue #75).
// discover HA origintunneld servers (GitHub issue #75).
dotServerName = "cloudflare-dns.com"
dotServerAddr = "1.1.1.1:853"
dotTimeout = time.Duration(15 * time.Second)
@@ -30,8 +30,8 @@ const (
var friendlyDNSErrorLines = []string{
`Please try the following things to diagnose this issue:`,
` 1. ensure that cloudflarewarp.com is returning "warp" service records.`,
` Run your system's equivalent of: dig srv _warp._tcp.cloudflarewarp.com`,
` 1. ensure that argotunnel.com is returning "origintunneld" service records.`,
` Run your system's equivalent of: dig srv _origintunneld._tcp.argotunnel.com`,
` 2. ensure that your DNS resolver is not returning compressed SRV records.`,
` See GitHub issue https://github.com/golang/go/issues/27546`,
` For example, you could use Cloudflare's 1.1.1.1 as your resolver:`,
@@ -102,7 +102,7 @@ func EdgeDiscovery(logger *logrus.Entry) ([]*net.TCPAddr, error) {
// Try to fall back to DoT from Cloudflare directly.
//
// Note: Instead of DoT, we could also have used DoH. Either of these:
// - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_warp._tcp.cloudflarewarp.com&type=srv)
// - directly via the JSON API (https://1.1.1.1/dns-query?ct=application/dns-json&name=_origintunneld._tcp.argotunnel.com&type=srv)
// - indirectly via `tunneldns.NewUpstreamHTTPS()`
// But both of these cases miss out on a key feature from the stdlib:
// "The returned records are sorted by priority and randomized by weight within a priority."
@@ -119,7 +119,7 @@ func EdgeDiscovery(logger *logrus.Entry) ([]*net.TCPAddr, error) {
for _, s := range friendlyDNSErrorLines {
logger.Errorln(s)
}
return nil, errors.Wrap(err, "Could not lookup srv records on _warp._tcp.cloudflarewarp.com")
return nil, errors.Wrap(err, "Could not lookup srv records on _origintunneld._tcp.argotunnel.com")
}
// Accept the fallback results and keep going
addrs = fallbackAddrs
+27 -17
View File
@@ -17,8 +17,9 @@ import (
)
const (
quickStartLink = "https://developers.cloudflare.com/argo-tunnel/quickstart/"
faqLink = "https://developers.cloudflare.com/argo-tunnel/faq/"
quickStartLink = "https://developers.cloudflare.com/argo-tunnel/quickstart/"
faqLink = "https://developers.cloudflare.com/argo-tunnel/faq/"
defaultRetryAfter = time.Second * 5
)
// EdgeManager manages connections with the edge
@@ -47,7 +48,7 @@ type CloudflaredConfig struct {
CloudflaredID uuid.UUID
Tags []pogs.Tag
BuildInfo *buildinfo.BuildInfo
Scope pogs.Scope
IntentLabel string
}
func NewEdgeManager(
@@ -87,8 +88,12 @@ func (em *EdgeManager) Run(ctx context.Context) error {
// Create/delete connection one at a time, so we don't need to adjust for connections that are being created/deleted
// in shouldCreateConnection or shouldReduceConnection calculation
if em.state.shouldCreateConnection(em.serviceDiscoverer.AvailableAddrs()) {
if err := em.newConnection(ctx); err != nil {
em.logger.WithError(err).Error("cannot create new connection")
if connErr := em.newConnection(ctx); connErr != nil {
if !connErr.ShouldRetry {
em.logger.WithError(connErr).Error(em.noRetryMessage())
return connErr
}
em.logger.WithError(connErr).Error("cannot create new connection")
}
} else if em.state.shouldReduceConnection() {
if err := em.closeConnection(ctx); err != nil {
@@ -103,11 +108,11 @@ func (em *EdgeManager) UpdateConfigurable(newConfigurable *EdgeManagerConfigurab
em.state.updateConfigurable(newConfigurable)
}
func (em *EdgeManager) newConnection(ctx context.Context) error {
func (em *EdgeManager) newConnection(ctx context.Context) *pogs.ConnectError {
edgeIP := em.serviceDiscoverer.Addr()
edgeConn, err := em.dialEdge(ctx, edgeIP)
if err != nil {
return errors.Wrap(err, "dial edge error")
return retryConnection(fmt.Sprintf("dial edge error: %v", err))
}
configurable := em.state.getConfigurable()
// Establish a muxed connection with the edge
@@ -121,12 +126,12 @@ func (em *EdgeManager) newConnection(ctx context.Context) error {
Logger: em.logger.WithField("subsystem", "muxer"),
})
if err != nil {
return errors.Wrap(err, "couldn't perform handshake with edge")
retryConnection(fmt.Sprintf("couldn't perform handshake with edge: %v", err))
}
h2muxConn, err := newConnection(muxer, edgeIP)
if err != nil {
return errors.Wrap(err, "couldn't create h2mux connection")
return retryConnection(fmt.Sprintf("couldn't create h2mux connection: %v", err))
}
go em.serveConn(ctx, h2muxConn)
@@ -136,23 +141,20 @@ func (em *EdgeManager) newConnection(ctx context.Context) error {
CloudflaredVersion: em.cloudflaredConfig.BuildInfo.CloudflaredVersion,
NumPreviousAttempts: 0,
OriginCert: em.state.getUserCredential(),
Scope: em.cloudflaredConfig.Scope,
IntentLabel: em.cloudflaredConfig.IntentLabel,
Tags: em.cloudflaredConfig.Tags,
}, em.logger)
if err != nil {
h2muxConn.Shutdown()
return errors.Wrap(err, "couldn't connect to edge")
return retryConnection(fmt.Sprintf("couldn't connect to edge: %v", err))
}
if connErr := connResult.Err; connErr != nil {
if !connErr.ShouldRetry {
return errors.Wrap(connErr, em.noRetryMessage())
}
return errors.Wrapf(connErr, "edge responded with RetryAfter=%v", connErr.RetryAfter)
if connErr := connResult.ConnectError(); connErr != nil {
return connErr
}
em.state.newConnection(h2muxConn)
em.logger.Infof("connected to %s", connResult.ServerInfo.LocationName)
em.logger.Infof("connected to %s", connResult.ConnectedTo())
return nil
}
@@ -282,3 +284,11 @@ func (ems *edgeManagerState) getUserCredential() []byte {
defer ems.RUnlock()
return ems.userCredential
}
func retryConnection(cause string) *pogs.ConnectError {
return &pogs.ConnectError{
Cause: cause,
RetryAfter: defaultRetryAfter,
ShouldRetry: true,
}
}
+6 -5
View File
@@ -20,11 +20,12 @@ var (
ErrUnknownStream = MuxerProtocolError{"2002 unknown stream", http2.ErrCodeProtocol}
ErrInvalidStream = MuxerProtocolError{"2003 invalid stream", http2.ErrCodeProtocol}
ErrStreamHeadersSent = MuxerApplicationError{"3000 headers already sent"}
ErrConnectionClosed = MuxerApplicationError{"3001 connection closed"}
ErrConnectionDropped = MuxerApplicationError{"3002 connection dropped"}
ErrOpenStreamTimeout = MuxerApplicationError{"3003 open stream timeout"}
ErrResponseHeadersTimeout = MuxerApplicationError{"3004 timeout waiting for initial response headers"}
ErrStreamHeadersSent = MuxerApplicationError{"3000 headers already sent"}
ErrStreamRequestConnectionClosed = MuxerApplicationError{"3001 connection closed while opening stream"}
ErrConnectionDropped = MuxerApplicationError{"3002 connection dropped"}
ErrStreamRequestTimeout = MuxerApplicationError{"3003 open stream timeout"}
ErrResponseHeadersTimeout = MuxerApplicationError{"3004 timeout waiting for initial response headers"}
ErrResponseHeadersConnectionClosed = MuxerApplicationError{"3005 connection closed while waiting for initial response headers"}
ErrClosedStream = MuxerStreamError{"4000 stream closed", http2.ErrCodeStreamClosed}
)
+36 -57
View File
@@ -1,7 +1,6 @@
package h2mux
import (
"bytes"
"context"
"io"
"strings"
@@ -20,7 +19,7 @@ const (
maxWindowSize uint32 = (1 << 31) - 1 // 2^31-1 = 2147483647, max window size in http2 spec
defaultTimeout time.Duration = 5 * time.Second
defaultRetries uint64 = 5
defaultWriteBufferMaxLen int = 1024 * 1024 * 512 // 500mb
defaultWriteBufferMaxLen int = 1024 * 1024 // 1mb
SettingMuxerMagic http2.SettingID = 0x42db
MuxerMagicOrigin uint32 = 0xa2e43c8b
@@ -388,72 +387,52 @@ func isConnectionClosedError(err error) bool {
// OpenStream opens a new data stream with the given headers.
// Called by proxy server and tunnel
func (m *Muxer) OpenStream(ctx context.Context, headers []Header, body io.Reader) (*MuxedStream, error) {
stream := &MuxedStream{
responseHeadersReceived: make(chan struct{}),
readBuffer: NewSharedBuffer(),
writeBuffer: &bytes.Buffer{},
writeBufferMaxLen: m.config.StreamWriteBufferMaxLen,
writeBufferHasSpace: make(chan struct{}, 1),
receiveWindow: m.config.DefaultWindowSize,
receiveWindowCurrentMax: m.config.DefaultWindowSize,
receiveWindowMax: m.config.MaxWindowSize,
sendWindow: m.config.DefaultWindowSize,
readyList: m.readyList,
writeHeaders: headers,
dictionaries: m.muxReader.dictionaries,
stream := m.NewStream(headers)
if err := m.MakeMuxedStreamRequest(ctx, MuxedStreamRequest{stream, body}); err != nil {
return nil, err
}
if err := m.AwaitResponseHeaders(ctx, stream); err != nil {
return nil, err
}
return stream, nil
}
func (m *Muxer) OpenRPCStream(ctx context.Context) (*MuxedStream, error) {
stream := m.NewStream(RPCHeaders())
if err := m.MakeMuxedStreamRequest(ctx, MuxedStreamRequest{stream: stream, body: nil}); err != nil {
return nil, err
}
if err := m.AwaitResponseHeaders(ctx, stream); err != nil {
return nil, err
}
return stream, nil
}
func (m *Muxer) NewStream(headers []Header) *MuxedStream {
return NewStream(m.config, headers, m.readyList, m.muxReader.dictionaries)
}
func (m *Muxer) MakeMuxedStreamRequest(ctx context.Context, request MuxedStreamRequest) error {
select {
case <-ctx.Done():
return ErrStreamRequestTimeout
case <-m.abortChan:
return ErrStreamRequestConnectionClosed
// Will be received by mux writer
case <-ctx.Done():
return nil, ErrOpenStreamTimeout
case <-m.abortChan:
return nil, ErrConnectionClosed
case m.newStreamChan <- MuxedStreamRequest{stream: stream, body: body}:
}
select {
case <-ctx.Done():
return nil, ErrResponseHeadersTimeout
case <-m.abortChan:
return nil, ErrConnectionClosed
case <-stream.responseHeadersReceived:
return stream, nil
case m.newStreamChan <- request:
return nil
}
}
func (m *Muxer) OpenRPCStream(ctx context.Context) (*MuxedStream, error) {
stream := &MuxedStream{
responseHeadersReceived: make(chan struct{}),
readBuffer: NewSharedBuffer(),
writeBuffer: &bytes.Buffer{},
writeBufferMaxLen: m.config.StreamWriteBufferMaxLen,
writeBufferHasSpace: make(chan struct{}, 1),
receiveWindow: m.config.DefaultWindowSize,
receiveWindowCurrentMax: m.config.DefaultWindowSize,
receiveWindowMax: m.config.MaxWindowSize,
sendWindow: m.config.DefaultWindowSize,
readyList: m.readyList,
writeHeaders: RPCHeaders(),
dictionaries: m.muxReader.dictionaries,
}
select {
// Will be received by mux writer
case <-ctx.Done():
return nil, ErrOpenStreamTimeout
case <-m.abortChan:
return nil, ErrConnectionClosed
case m.newStreamChan <- MuxedStreamRequest{stream: stream, body: nil}:
}
func (m *Muxer) AwaitResponseHeaders(ctx context.Context, stream *MuxedStream) error {
select {
case <-ctx.Done():
return nil, ErrResponseHeadersTimeout
return ErrResponseHeadersTimeout
case <-m.abortChan:
return nil, ErrConnectionClosed
return ErrResponseHeadersConnectionClosed
case <-stream.responseHeadersReceived:
return stream, nil
return nil
}
}
+2 -2
View File
@@ -584,8 +584,8 @@ func TestOpenAfterDisconnect(t *testing.T) {
[]Header{{Name: "test-header", Value: "headerValue"}},
nil,
)
if err != ErrConnectionClosed {
t.Fatalf("unexpected error in OpenStream: %s", err)
if err != ErrStreamRequestConnectionClosed && err != ErrResponseHeadersConnectionClosed {
t.Fatalf("case %v: unexpected error in OpenStream: %v", i, err)
}
}
}
+17
View File
@@ -88,6 +88,23 @@ func (th TunnelHostname) IsSet() bool {
return th != ""
}
func NewStream(config MuxerConfig, writeHeaders []Header, readyList *ReadyList, dictionaries h2Dictionaries) *MuxedStream {
return &MuxedStream{
responseHeadersReceived: make(chan struct{}),
readBuffer: NewSharedBuffer(),
writeBuffer: &bytes.Buffer{},
writeBufferMaxLen: config.StreamWriteBufferMaxLen,
writeBufferHasSpace: make(chan struct{}, 1),
receiveWindow: config.DefaultWindowSize,
receiveWindowCurrentMax: config.DefaultWindowSize,
receiveWindowMax: config.MaxWindowSize,
sendWindow: config.DefaultWindowSize,
readyList: readyList,
writeHeaders: writeHeaders,
dictionaries: dictionaries,
}
}
func (s *MuxedStream) Read(p []byte) (n int, err error) {
var readBuffer ReadWriteClosedCloser
if s.dictionaries.read != nil {
+9 -4
View File
@@ -87,23 +87,28 @@ func (r *MuxReader) run(parentLogger *log.Entry) error {
for {
frame, err := r.f.ReadFrame()
if err != nil {
errLogger := logger.WithError(err)
if errorDetail := r.f.ErrorDetail(); errorDetail != nil {
errLogger = errLogger.WithField("errorDetail", errorDetail)
}
switch e := err.(type) {
case http2.StreamError:
logger.WithError(err).Warn("stream error")
errLogger.Warn("stream error")
r.streamError(e.StreamID, e.Code)
case http2.ConnectionError:
logger.WithError(err).Warn("connection error")
errLogger.Warn("connection error")
return r.connectionError(err)
default:
if isConnectionClosedError(err) {
if r.streams.Len() == 0 {
// don't log the error here -- that would just be extra noise
logger.Debug("shutting down")
return nil
}
logger.Warn("connection closed unexpectedly")
errLogger.Warn("connection closed unexpectedly")
return err
} else {
logger.WithError(err).Warn("frame read error")
errLogger.Warn("frame read error")
return r.connectionError(err)
}
}
+14 -13
View File
@@ -152,7 +152,7 @@ func ServeTunnelLoop(ctx context.Context,
connectedSignal *signal.Signal,
u uuid.UUID,
) error {
logger := config.Logger
connectionLogger := config.Logger.WithField("connectionID", connectionID)
config.Metrics.incrementHaConnections()
defer config.Metrics.decrementHaConnections()
backoff := BackoffHandler{MaxRetries: config.Retries}
@@ -165,10 +165,10 @@ func ServeTunnelLoop(ctx context.Context,
// Ensure the above goroutine will terminate if we return without connecting
defer connectedFuse.Fuse(false)
for {
err, recoverable := ServeTunnel(ctx, config, addr, connectionID, connectedFuse, &backoff, u)
err, recoverable := ServeTunnel(ctx, config, connectionLogger, addr, connectionID, connectedFuse, &backoff, u)
if recoverable {
if duration, ok := backoff.GetBackoffDuration(ctx); ok {
logger.Infof("Retrying in %s seconds", duration)
connectionLogger.Infof("Retrying in %s seconds", duration)
backoff.Backoff(ctx)
continue
}
@@ -180,6 +180,7 @@ func ServeTunnelLoop(ctx context.Context,
func ServeTunnel(
ctx context.Context,
config *TunnelConfig,
logger *log.Entry,
addr *net.TCPAddr,
connectionID uint8,
connectedFuse *h2mux.BooleanFuse,
@@ -199,7 +200,6 @@ func ServeTunnel(
}()
connectionTag := uint8ToString(connectionID)
logger := config.Logger.WithField("connectionID", connectionTag)
// additional tags to send other than hostname which is set in cloudflared main package
tags := make(map[string]string)
@@ -208,7 +208,7 @@ func ServeTunnel(
// Returns error from parsing the origin URL or handshake errors
handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr.String(), connectionID)
if err != nil {
errLog := config.Logger.WithError(err)
errLog := logger.WithError(err)
switch err.(type) {
case dialError:
errLog.Error("Unable to dial edge")
@@ -224,7 +224,7 @@ func ServeTunnel(
errGroup, serveCtx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
err := RegisterTunnel(serveCtx, handler.muxer, config, connectionID, originLocalIP, u)
err := RegisterTunnel(serveCtx, handler.muxer, config, logger, connectionID, originLocalIP, u)
if err == nil {
connectedFuse.Fuse(true)
backoff.SetGracePeriod()
@@ -302,6 +302,7 @@ func RegisterTunnel(
ctx context.Context,
muxer *h2mux.Muxer,
config *TunnelConfig,
logger *log.Entry,
connectionID uint8,
originLocalIP string,
uuid uuid.UUID,
@@ -333,13 +334,13 @@ func RegisterTunnel(
config.Hostname,
config.RegistrationOptions(connectionID, originLocalIP, uuid),
)
LogServerInfo(serverInfoPromise.Result(), connectionID, config.Metrics, config.Logger)
LogServerInfo(serverInfoPromise.Result(), connectionID, config.Metrics, logger)
if err != nil {
// RegisterTunnel RPC failure
return newClientRegisterTunnelError(err, config.Metrics.regFail)
}
for _, logLine := range registration.LogLines {
config.Logger.Info(logLine)
logger.Info(logLine)
}
if regErr := processRegisterTunnelError(registration.Err, registration.PermanentFailure, config.Metrics); regErr != nil {
@@ -348,24 +349,24 @@ func RegisterTunnel(
if registration.TunnelID != "" {
config.Metrics.tunnelsHA.AddTunnelID(connectionID, registration.TunnelID)
config.Logger.Infof("Each HA connection's tunnel IDs: %v", config.Metrics.tunnelsHA.String())
logger.Infof("Each HA connection's tunnel IDs: %v", config.Metrics.tunnelsHA.String())
}
// Print out the user's trial zone URL in a nice box (if they requested and got one)
if isTrialTunnel := config.Hostname == ""; isTrialTunnel {
if url, err := url.Parse(registration.Url); err == nil {
for _, line := range asciiBox(trialZoneMsg(url.String()), 2) {
config.Logger.Infoln(line)
logger.Infoln(line)
}
} else {
config.Logger.Errorln("Failed to connect tunnel, please try again.")
logger.Errorln("Failed to connect tunnel, please try again.")
return fmt.Errorf("empty URL in response from Cloudflare edge")
}
}
config.Metrics.userHostnamesCounts.WithLabelValues(registration.Url).Inc()
config.Logger.Infof("Route propagating, it may take up to 1 minute for your new route to become functional")
logger.Infof("Route propagating, it may take up to 1 minute for your new route to become functional")
return nil
}
@@ -421,7 +422,7 @@ func LogServerInfo(
promise tunnelrpc.ServerInfo_Promise,
connectionID uint8,
metrics *TunnelMetrics,
logger *log.Logger,
logger *log.Entry,
) {
serverInfoMessage, err := promise.Struct()
if err != nil {
+14
View File
@@ -0,0 +1,14 @@
FROM python:3-buster
RUN wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.deb \
&& dpkg -i cloudflared-stable-linux-amd64.deb
RUN pip install pexpect
COPY tests.py .
COPY ssh /root/.ssh
RUN chmod 600 /root/.ssh/id_rsa
ARG SSH_HOSTNAME
RUN bash -c 'sed -i "s/{{hostname}}/${SSH_HOSTNAME}/g" /root/.ssh/authorized_keys_config'
RUN bash -c 'sed -i "s/{{hostname}}/${SSH_HOSTNAME}/g" /root/.ssh/short_lived_cert_config'
+23
View File
@@ -0,0 +1,23 @@
# Cloudflared SSH server smoke tests
Runs several tests in a docker container against a server that is started out of band of these tests.
Cloudflared token also needs to be retrieved out of band.
SSH server hostname and user need to be configured in a docker environment file
## Running tests
* Build cloudflared:
make cloudflared
* Start server:
sudo ./cloudflared tunnel --hostname HOSTNAME --ssh-server
* Fetch token:
./cloudflared access login HOSTNAME
* Create docker env file:
echo "SSH_HOSTNAME=HOSTNAME\nSSH_USER=USERNAME\n" > ssh_server_tests/.env
* Run tests:
make test-ssh-server
+18
View File
@@ -0,0 +1,18 @@
version: "3.1"
services:
ssh_test:
build:
context: .
args:
- SSH_HOSTNAME=${SSH_HOSTNAME}
volumes:
- "~/.cloudflared/:/root/.cloudflared"
env_file:
- .env
environment:
- AUTHORIZED_KEYS_SSH_CONFIG=/root/.ssh/authorized_keys_config
- SHORT_LIVED_CERT_SSH_CONFIG=/root/.ssh/short_lived_cert_config
- REMOTE_SCP_FILENAME=scp_test.txt
- ROOT_ONLY_TEST_FILE_PATH=~/permission_test.txt
entrypoint: "python tests.py"
@@ -0,0 +1,5 @@
Host *
AddressFamily inet
Host {{hostname}}
ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h
+49
View File
@@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAgEAvi26NDQ8cYTTztqPe9ZgF5HR/rIo5FoDgL5NbbZKW6h0txP9Fd8s
id9Bgmo+aGkeM327tPVVMQ6UFmdRksOCIDWQDjNLF8b6S+Fu95tvMKSbGreRoR32OvgZKV
I6KmOsF4z4GIv9naPplZswtKEUhSSI+/gPdAs9wfwalqZ77e82QJ727bYMeC3lzuoT+KBI
dYufJ4OQhLtpHrqhB5sn7s6+oCv/u85GSln5SIC18Hi2t9lW4tgb5tH8P0kEDDWGfPS5ok
qGi4kFTvwBXOCS2r4dhi5hRkpP7PqG4np0OCfvK5IRRJ27fCnj0loc+puZJAxnPMbuJr64
vwxRx78PM/V0PDUsl0P6aR/vbe0XmF9FGqbWf2Tar1p4r6C9/bMzcDz8seYT8hzLIHP3+R
l1hdlsTLm+1EzhaExKId+tjXegKGG4nU24h6qHEnRxLQDMwEsdkfj4E1pVypZJXVyNj99D
o84vi0EUnu7R4HmQb/C+Pu7qMDtLT3Zk7O5Mg4LQ+cTz9V0noYEAyG46nAB4U/nJzBnV1J
+aAdpioHmUAYhLYlQ9Kiy7LCJi92g9Wqa4wxMKxBbO5ZeH++p2p2lUi/oQNqx/2dLYFmy0
wxvJHbZIhAaFbOeCvHg1ucIAQznli2jOr2qoB+yKRRPAp/3NXnZg1v7ce2CkwiAD52wjtC
kAAAdILMJUeyzCVHsAAAAHc3NoLXJzYQAAAgEAvi26NDQ8cYTTztqPe9ZgF5HR/rIo5FoD
gL5NbbZKW6h0txP9Fd8sid9Bgmo+aGkeM327tPVVMQ6UFmdRksOCIDWQDjNLF8b6S+Fu95
tvMKSbGreRoR32OvgZKVI6KmOsF4z4GIv9naPplZswtKEUhSSI+/gPdAs9wfwalqZ77e82
QJ727bYMeC3lzuoT+KBIdYufJ4OQhLtpHrqhB5sn7s6+oCv/u85GSln5SIC18Hi2t9lW4t
gb5tH8P0kEDDWGfPS5okqGi4kFTvwBXOCS2r4dhi5hRkpP7PqG4np0OCfvK5IRRJ27fCnj
0loc+puZJAxnPMbuJr64vwxRx78PM/V0PDUsl0P6aR/vbe0XmF9FGqbWf2Tar1p4r6C9/b
MzcDz8seYT8hzLIHP3+Rl1hdlsTLm+1EzhaExKId+tjXegKGG4nU24h6qHEnRxLQDMwEsd
kfj4E1pVypZJXVyNj99Do84vi0EUnu7R4HmQb/C+Pu7qMDtLT3Zk7O5Mg4LQ+cTz9V0noY
EAyG46nAB4U/nJzBnV1J+aAdpioHmUAYhLYlQ9Kiy7LCJi92g9Wqa4wxMKxBbO5ZeH++p2
p2lUi/oQNqx/2dLYFmy0wxvJHbZIhAaFbOeCvHg1ucIAQznli2jOr2qoB+yKRRPAp/3NXn
Zg1v7ce2CkwiAD52wjtCkAAAADAQABAAACAQCbnVsyAFQ9J00Rg/HIiUATyTQlzq57O9SF
8jH1RiZOHedzLx32WaleH5rBFiJ+2RTnWUjQ57aP77fpJR2wk93UcT+w/vPBPwXsNUjRvx
Qan3ZzRCYbyiKDWiNslmYV7X0RwD36CAK8jTVDP7t48h2SXLTiSLaMY+5i3uD6yLu7k/O2
qNyw4jgN1rCmwQ8acD0aQec3NAZ7NcbsaBX/3Uutsup0scwOZtlJWZoLY5Z8cKpCgcsAz4
j1NHnNZvey7dFgSffj/ktdvf7kBH0w/GnuJ4aNF0Jte70u0kiw5TZYBQVFh74tgUu6a6SJ
qUbxIYUL5EJNjxGsDn+phHEemw3aMv0CwZG6Tqaionlna7bLsl9Bg1HTGclczVWx8uqC+M
6agLmkhYCHG0rVj8h5smjXAQXtmvIDVYDOlJZZoF9VAOCj6QfmJUH1NAGpCs1HDHbeOxGA
OLCh4d3F4rScPqhGdtSt4W13VFIvXn2Qqoz9ufepZsee1SZqpcerxywx2wN9ZAzu+X8lTN
i+TA2B3vWpqqucOEsp4JwDN+VMKZqKUGUDWcm/eHSaG6wq0q734LUlgM85TjaIg8QsNtWV
giB1nWwsYIuH4rsFNFGEwURYdGBcw6idH0GZ7I4RaIB5F9oOza1d601E0APHYrtnx9yOiK
nOtJ+5ZmVZovaDRfu1aQAAAQBU/EFaNUzoVhO04pS2L6BlByt963bOIsSJhdlEzek5AAli
eaf1S/PD6xWCc0IGY+GZE0HPbhsKYanjqOpWldcA2T7fzf4oz4vFBfUkPYo/MLSlLCYsDd
IH3wBkCssnfR5EkzNgxnOvq646Nl64BMvxwSIXGPktdq9ZALxViwricSRzCFURnh5vLHWU
wBzSgAA0UlZ9E64GtAv066+AoZCp83GhTLRC4o0naE2e/K4op4BCFHLrZ8eXmDRK3NJj80
Vkn+uhrk+SHmbjIhmS57Pv9p8TWyRvemph/nMUuZGKBUu2X+JQxggck0KigIrXjsmciCsM
BIM3mYDDfjYbyVhTAAABAQDkV8O1bWUsAIqk7RU+iDZojN5kaO+zUvj1TafX8QX1sY6pu4
Z2cfSEka1532BaehM95bQm7BCPw4cYg56XidmCQTZ9WaWqxVrOo48EKXUtZMZx6nKFOKlq
MT2XTMnGT9n7kFCfEjSVkAjuJ9ZTFLOaoXAaVRnxeHQwOKaup5KKP9GSzNIw328U+96s3V
WKHeT4pMjHBccgW/qX/tRRidZw5in5uBC9Ew5y3UACFTkNOnhUwVfyUNbBZJ2W36msQ3KD
AN7nOrQHqhd3NFyCEy2ovIAKVBacr/VEX6EsRUshIehJzz8EY9f3kXL7WT2QDoz2giPeBJ
HJdEpXt43UpszjAAABAQDVNpqNdHUlCs9XnbIvc6ZRrNh79wt65YFfvh/QEuA33KnA6Ri6
EgnV5IdUWXS/UFaYcm2udydrBpVIVifSYl3sioHBylpri23BEy38PKwVXvghUtfpN6dWGn
NZUG25fQPtIzqi+lo953ZjIj+Adi17AeVv4P4NiLrZeM9lXfWf2pEPOecxXs1IwAf9IiDQ
WepAwRLsu42eEnHA+DSJPZUkSbISfM5X345k0g6EVATX/yLL3CsqClPzPtsqjh6rbEfFg3
2OfIMcWV77gOlGWGQ+bUHc8kV6xJqV9QVacLWzfLvIqHF0wQMf8WLOVHEzkfiq4VjwhVqr
/+FFvljm5nSDAAAAEW1pa2VAQzAyWTUwVEdKR0g4AQ==
-----END OPENSSH PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC+Lbo0NDxxhNPO2o971mAXkdH+sijkWgOAvk1ttkpbqHS3E/0V3yyJ30GCaj5oaR4zfbu09VUxDpQWZ1GSw4IgNZAOM0sXxvpL4W73m28wpJsat5GhHfY6+BkpUjoqY6wXjPgYi/2do+mVmzC0oRSFJIj7+A90Cz3B/BqWpnvt7zZAnvbttgx4LeXO6hP4oEh1i58ng5CEu2keuqEHmyfuzr6gK/+7zkZKWflIgLXweLa32Vbi2Bvm0fw/SQQMNYZ89LmiSoaLiQVO/AFc4JLavh2GLmFGSk/s+obienQ4J+8rkhFEnbt8KePSWhz6m5kkDGc8xu4mvri/DFHHvw8z9XQ8NSyXQ/ppH+9t7ReYX0UaptZ/ZNqvWnivoL39szNwPPyx5hPyHMsgc/f5GXWF2WxMub7UTOFoTEoh362Nd6AoYbidTbiHqocSdHEtAMzASx2R+PgTWlXKlkldXI2P30Ojzi+LQRSe7tHgeZBv8L4+7uowO0tPdmTs7kyDgtD5xPP1XSehgQDIbjqcAHhT+cnMGdXUn5oB2mKgeZQBiEtiVD0qLLssImL3aD1aprjDEwrEFs7ll4f76nanaVSL+hA2rH/Z0tgWbLTDG8kdtkiEBoVs54K8eDW5wgBDOeWLaM6vaqgH7IpFE8Cn/c1edmDW/tx7YKTCIAPnbCO0KQ== mike@C02Y50TGJGH8
@@ -0,0 +1,11 @@
Host *
AddressFamily inet
Host {{hostname}}
ProxyCommand bash -c '/usr/local/bin/cloudflared access ssh-gen --hostname %h; ssh -F /root/.ssh/short_lived_cert_config -tt %r@cfpipe-{{hostname}} >&2 <&1'
Host cfpipe-{{hostname}}
HostName {{hostname}}
ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h
IdentityFile ~/.cloudflared/{{hostname}}-cf_key
CertificateFile ~/.cloudflared/{{hostname}}-cf_key-cert.pub
+195
View File
@@ -0,0 +1,195 @@
"""
Cloudflared Integration tests
"""
import unittest
import subprocess
import os
import tempfile
from contextlib import contextmanager
from pexpect import pxssh
class TestSSHBase(unittest.TestCase):
"""
SSH test base class containing constants and helper funcs
"""
HOSTNAME = os.environ["SSH_HOSTNAME"]
SSH_USER = os.environ["SSH_USER"]
SSH_TARGET = f"{SSH_USER}@{HOSTNAME}"
AUTHORIZED_KEYS_SSH_CONFIG = os.environ["AUTHORIZED_KEYS_SSH_CONFIG"]
SHORT_LIVED_CERT_SSH_CONFIG = os.environ["SHORT_LIVED_CERT_SSH_CONFIG"]
SSH_OPTIONS = {"StrictHostKeyChecking": "no"}
@classmethod
def get_ssh_command(cls, pty=True):
"""
Return ssh command arg list. If pty is true, a PTY is forced for the session.
"""
cmd = [
"ssh",
"-o",
"StrictHostKeyChecking=no",
"-F",
cls.AUTHORIZED_KEYS_SSH_CONFIG,
cls.SSH_TARGET,
]
if not pty:
cmd += ["-T"]
else:
cmd += ["-tt"]
return cmd
@classmethod
@contextmanager
def ssh_session_manager(cls, *args, **kwargs):
"""
Context manager for interacting with a pxssh session.
Disables pty echo on the remote server and ensures session is terminated afterward.
"""
session = pxssh.pxssh(options=cls.SSH_OPTIONS)
session.login(
cls.HOSTNAME,
username=cls.SSH_USER,
original_prompt=r"[#@$]",
ssh_config=kwargs.get("ssh_config", cls.AUTHORIZED_KEYS_SSH_CONFIG),
ssh_tunnels=kwargs.get("ssh_tunnels", {}),
)
try:
session.sendline("stty -echo")
session.prompt()
yield session
finally:
session.logout()
@staticmethod
def get_command_output(session, cmd):
"""
Executes command on remote ssh server and waits for prompt.
Returns command output
"""
session.sendline(cmd)
session.prompt()
return session.before.decode().strip()
def exec_command(self, cmd, shell=False):
"""
Executes command locally. Raises Assertion error for non-zero return code.
Returns stdout and stderr
"""
proc = subprocess.Popen(
cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=shell
)
raw_out, raw_err = proc.communicate()
out = raw_out.decode()
err = raw_err.decode()
self.assertEqual(proc.returncode, 0, msg=f"stdout: {out} stderr: {err}")
return out.strip(), err.strip()
class TestSSHCommandExec(TestSSHBase):
"""
Tests inline ssh command exec
"""
# Name of file to be downloaded over SCP on remote server.
REMOTE_SCP_FILENAME = os.environ["REMOTE_SCP_FILENAME"]
@classmethod
def get_scp_base_command(cls):
return [
"scp",
"-o",
"StrictHostKeyChecking=no",
"-v",
"-F",
cls.AUTHORIZED_KEYS_SSH_CONFIG,
]
@unittest.skip(
"This creates files on the remote. Should be skipped until server is dockerized."
)
def test_verbose_scp_sink_mode(self):
with tempfile.NamedTemporaryFile() as fl:
self.exec_command(
self.get_scp_base_command() + [fl.name, f"{self.SSH_TARGET}:"]
)
def test_verbose_scp_source_mode(self):
with tempfile.TemporaryDirectory() as tmpdirname:
self.exec_command(
self.get_scp_base_command()
+ [f"{self.SSH_TARGET}:{self.REMOTE_SCP_FILENAME}", tmpdirname]
)
local_filename = os.path.join(tmpdirname, self.REMOTE_SCP_FILENAME)
self.assertTrue(os.path.exists(local_filename))
self.assertTrue(os.path.getsize(local_filename) > 0)
def test_pty_command(self):
base_cmd = self.get_ssh_command()
out, _ = self.exec_command(base_cmd + ["whoami"])
self.assertEqual(out.strip().lower(), self.SSH_USER.lower())
out, _ = self.exec_command(base_cmd + ["tty"])
self.assertNotEqual(out, "not a tty")
def test_non_pty_command(self):
base_cmd = self.get_ssh_command(pty=False)
out, _ = self.exec_command(base_cmd + ["whoami"])
self.assertEqual(out.strip().lower(), self.SSH_USER.lower())
out, _ = self.exec_command(base_cmd + ["tty"])
self.assertEqual(out, "not a tty")
class TestSSHShell(TestSSHBase):
"""
Tests interactive SSH shell
"""
# File path to a file on the remote server with root only read privileges.
ROOT_ONLY_TEST_FILE_PATH = os.environ["ROOT_ONLY_TEST_FILE_PATH"]
def test_ssh_pty(self):
with self.ssh_session_manager() as session:
# Test shell launched as correct user
username = self.get_command_output(session, "whoami")
self.assertEqual(username.lower(), self.SSH_USER.lower())
# Test USER env variable set
user_var = self.get_command_output(session, "echo $USER")
self.assertEqual(user_var.lower(), self.SSH_USER.lower())
# Test HOME env variable set to true user home.
home_env = self.get_command_output(session, "echo $HOME")
pwd = self.get_command_output(session, "pwd")
self.assertEqual(pwd, home_env)
# Test shell launched in correct user home dir.
self.assertIn(username, pwd)
# Ensure shell launched with correct user's permissions and privs.
# Cant read root owned 0700 files.
output = self.get_command_output(
session, f"cat {self.ROOT_ONLY_TEST_FILE_PATH}"
)
self.assertIn("Permission denied", output)
def test_short_lived_cert_auth(self):
with self.ssh_session_manager(
ssh_config=self.SHORT_LIVED_CERT_SSH_CONFIG
) as session:
username = self.get_command_output(session, "whoami")
self.assertEqual(username.lower(), self.SSH_USER.lower())
unittest.main()
+22 -16
View File
@@ -8,7 +8,6 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -20,6 +19,7 @@ import (
cfpath "github.com/cloudflare/cloudflared/cmd/cloudflared/path"
"github.com/coreos/go-oidc/jose"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
gossh "golang.org/x/crypto/ssh"
)
@@ -73,48 +73,54 @@ func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
// handleCertificateGeneration takes a JWT and uses it build a signPayload
// to send to the Sign endpoint with the public key from the keypair it generated
func handleCertificateGeneration(token, fullName string) (string, error) {
pub, err := generateKeyPair(fullName)
if err != nil {
return "", err
}
return SignCert(token, string(pub))
}
func SignCert(token, pubKey string) (string, error) {
if token == "" {
return "", errors.New("invalid token")
}
jwt, err := jose.ParseJWT(token)
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to parse JWT")
}
claims, err := jwt.Claims()
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to retrieve JWT claims")
}
issuer, _, err := claims.StringClaim("iss")
if err != nil {
return "", err
}
pub, err := generateKeyPair(fullName)
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to retrieve JWT iss")
}
buf, err := json.Marshal(&signPayload{
PublicKey: string(pub),
PublicKey: pubKey,
JWT: token,
Issuer: issuer,
})
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to marshal signPayload")
}
var res *http.Response
if mockRequest != nil {
res, err = mockRequest(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
} else {
res, err = http.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
client := http.Client{
Timeout: 10 * time.Second,
}
res, err = client.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
}
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to send request")
}
defer res.Body.Close()
@@ -130,9 +136,9 @@ func handleCertificateGeneration(token, fullName string) (string, error) {
var signRes signResponse
if err := decoder.Decode(&signRes); err != nil {
return "", err
return "", errors.Wrap(err, "failed to decode HTTP response")
}
return signRes.Certificate, err
return signRes.Certificate, nil
}
// generateKeyPair creates a EC keypair (P256) and stores them in the homedir.
+37
View File
@@ -0,0 +1,37 @@
package sshlog
import (
"io"
"github.com/sirupsen/logrus"
)
//empty manager implements the Manager but does nothing (for testing and to disable logging unless the logs are set)
type emptyManager struct {
}
type emptyWriteCloser struct {
}
// NewEmptyManager creates a new instance of a log empty log manager that does nothing
func NewEmptyManager() Manager {
return &emptyManager{}
}
func (m *emptyManager) NewLogger(name string, logger *logrus.Logger) (io.WriteCloser, error) {
return &emptyWriteCloser{}, nil
}
func (m *emptyManager) NewSessionLogger(name string, logger *logrus.Logger) (io.WriteCloser, error) {
return &emptyWriteCloser{}, nil
}
// emptyWriteCloser
func (w *emptyWriteCloser) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (w *emptyWriteCloser) Close() error {
return nil
}
+15
View File
@@ -0,0 +1,15 @@
# Generate go.capnp.out with:
# capnp compile -o- go.capnp > go.capnp.out
# Must run inside this directory to preserve paths.
@0xd12a1c51fedd6c88;
annotation package(file) :Text;
annotation import(file) :Text;
annotation doc(struct, field, enum) :Text;
annotation tag(enumerant) :Text;
annotation notag(enumerant) :Void;
annotation customtype(field) :Text;
annotation name(struct, field, union, enum, enumerant, interface, method, param, annotation, const, group) :Text;
$package("capnp");
+167
View File
@@ -0,0 +1,167 @@
package sshlog
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/sirupsen/logrus"
)
const (
logTimeFormat = "2006-01-02T15-04-05.000"
megabyte = 1024 * 1024
defaultFileSizeLimit = 100 * megabyte
)
// Logger will buffer and write events to disk
type Logger struct {
sync.Mutex
filename string
file *os.File
writeBuffer *bufio.Writer
logger *logrus.Logger
flushInterval time.Duration
maxFileSize int64
done chan struct{}
once sync.Once
}
// NewLogger creates a Logger instance. A buffer is created that needs to be
// drained and closed when the caller is finished, so instances should call
// Close when finished with this Logger instance. Writes will be flushed to disk
// every second (fsync). filename is the name of the logfile to be created. The
// logger variable is a logrus that will log all i/o, filesystem error etc, that
// that shouldn't end execution of the logger, but are useful to report to the
// caller.
func NewLogger(filename string, logger *logrus.Logger, flushInterval time.Duration, maxFileSize int64) (*Logger, error) {
if logger == nil {
return nil, errors.New("logger can't be nil")
}
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return nil, err
}
l := &Logger{filename: filename,
file: f,
writeBuffer: bufio.NewWriter(f),
logger: logger,
flushInterval: flushInterval,
maxFileSize: maxFileSize,
done: make(chan struct{}),
}
go l.writer()
return l, nil
}
// Writes to a log buffer. Implements the io.Writer interface.
func (l *Logger) Write(p []byte) (n int, err error) {
l.Lock()
defer l.Unlock()
return l.writeBuffer.Write(p)
}
// Close drains anything left in the buffer and cleans up any resources still
// in use.
func (l *Logger) Close() error {
l.once.Do(func() {
close(l.done)
})
if err := l.write(); err != nil {
return err
}
return l.file.Close()
}
// writer is the run loop that handles draining the write buffer and syncing
// data to disk.
func (l *Logger) writer() {
ticker := time.NewTicker(l.flushInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := l.write(); err != nil {
l.logger.Errorln(err)
}
case <-l.done:
return
}
}
}
// write does the actual system write calls to disk and does a rotation if the
// file size limit has been reached. Since the rotation happens at the end,
// the rotation is a soft limit (aka the file can be bigger than the max limit
// because of the final buffer flush)
func (l *Logger) write() error {
l.Lock()
defer l.Unlock()
if l.writeBuffer.Buffered() <= 0 {
return nil
}
if err := l.writeBuffer.Flush(); err != nil {
return err
}
if err := l.file.Sync(); err != nil {
return err
}
if l.shouldRotate() {
return l.rotate()
}
return nil
}
// shouldRotate checks to see if the current file should be rotated to a new
// logfile.
func (l *Logger) shouldRotate() bool {
info, err := l.file.Stat()
if err != nil {
return false
}
return info.Size() >= l.maxFileSize
}
// rotate creates a new logfile with the existing filename and renames the
// existing file with a current timestamp.
func (l *Logger) rotate() error {
if err := l.file.Close(); err != nil {
return err
}
// move the existing file
newname := rotationName(l.filename)
if err := os.Rename(l.filename, newname); err != nil {
return fmt.Errorf("can't rename log file: %s", err)
}
f, err := os.OpenFile(l.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return fmt.Errorf("failed to open new logfile %s", err)
}
l.file = f
l.writeBuffer = bufio.NewWriter(f)
return nil
}
// rotationName creates a new filename from the given name, inserting a timestamp
// between the filename and the extension.
func rotationName(name string) string {
dir := filepath.Dir(name)
filename := filepath.Base(name)
ext := filepath.Ext(filename)
prefix := filename[:len(filename)-len(ext)]
t := time.Now()
timestamp := t.Format(logTimeFormat)
return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext))
}
+89
View File
@@ -0,0 +1,89 @@
package sshlog
import (
"log"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/sirupsen/logrus"
)
const logFileName = "test-logger.log"
func createLogger(t *testing.T) *Logger {
os.Remove(logFileName)
l := logrus.New()
logger, err := NewLogger(logFileName, l, time.Millisecond, 1024)
if err != nil {
t.Fatal("couldn't create the logger!", err)
}
return logger
}
// AUTH-2115 TODO: fix this test
//func TestWrite(t *testing.T) {
// testStr := "hi"
// logger := createLogger(t)
// defer func() {
// logger.Close()
// os.Remove(logFileName)
// }()
//
// logger.Write([]byte(testStr))
// time.Sleep(2 * time.Millisecond)
// data, err := ioutil.ReadFile(logFileName)
// if err != nil {
// t.Fatal("couldn't read the log file!", err)
// }
// checkStr := string(data)
// if checkStr != testStr {
// t.Fatal("file data doesn't match!")
// }
//}
func TestFilenameRotation(t *testing.T) {
newName := rotationName("dir/bob/acoolloggername.log")
dir := filepath.Dir(newName)
if dir != "dir/bob" {
t.Fatal("rotation name doesn't respect the directory filepath:", newName)
}
filename := filepath.Base(newName)
if !strings.HasPrefix(filename, "acoolloggername") {
t.Fatal("rotation filename is wrong:", filename)
}
ext := filepath.Ext(newName)
if ext != ".log" {
t.Fatal("rotation file extension is wrong:", ext)
}
}
func TestRotation(t *testing.T) {
logger := createLogger(t)
for i := 0; i < 2000; i++ {
logger.Write([]byte("a string for testing rotation\n"))
}
logger.Close()
count := 0
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
if strings.HasPrefix(info.Name(), "test-logger") {
log.Println("deleting: ", path)
os.Remove(path)
count++
}
return nil
})
if count < 2 {
t.Fatal("rotation didn't roll files:", count)
}
}
+34
View File
@@ -0,0 +1,34 @@
package sshlog
import (
"io"
"path/filepath"
"time"
"github.com/sirupsen/logrus"
)
// Manager be managing logs bruh
type Manager interface {
NewLogger(string, *logrus.Logger) (io.WriteCloser, error)
NewSessionLogger(string, *logrus.Logger) (io.WriteCloser, error)
}
type manager struct {
baseDirectory string
}
// New creates a new instance of a log manager
func New(baseDirectory string) Manager {
return &manager{
baseDirectory: baseDirectory,
}
}
func (m *manager) NewLogger(name string, logger *logrus.Logger) (io.WriteCloser, error) {
return NewLogger(filepath.Join(m.baseDirectory, name), logger, time.Second, defaultFileSizeLimit)
}
func (m *manager) NewSessionLogger(name string, logger *logrus.Logger) (io.WriteCloser, error) {
return NewSessionLogger(filepath.Join(m.baseDirectory, name), logger, time.Second, defaultFileSizeLimit)
}
+9
View File
@@ -0,0 +1,9 @@
using Go = import "go.capnp";
@0x8f43375162194466;
$Go.package("sshlog");
$Go.import("github.com/cloudflare/cloudflared/sshlog");
struct SessionLog {
timestamp @0 :Text;
content @1 :Data;
}
+110
View File
@@ -0,0 +1,110 @@
// Code generated by capnpc-go. DO NOT EDIT.
package sshlog
import (
capnp "zombiezen.com/go/capnproto2"
text "zombiezen.com/go/capnproto2/encoding/text"
schemas "zombiezen.com/go/capnproto2/schemas"
)
type SessionLog struct{ capnp.Struct }
// SessionLog_TypeID is the unique identifier for the type SessionLog.
const SessionLog_TypeID = 0xa13a07c504a5ab64
func NewSessionLog(s *capnp.Segment) (SessionLog, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return SessionLog{st}, err
}
func NewRootSessionLog(s *capnp.Segment) (SessionLog, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
return SessionLog{st}, err
}
func ReadRootSessionLog(msg *capnp.Message) (SessionLog, error) {
root, err := msg.RootPtr()
return SessionLog{root.Struct()}, err
}
func (s SessionLog) String() string {
str, _ := text.Marshal(0xa13a07c504a5ab64, s.Struct)
return str
}
func (s SessionLog) Timestamp() (string, error) {
p, err := s.Struct.Ptr(0)
return p.Text(), err
}
func (s SessionLog) HasTimestamp() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s SessionLog) TimestampBytes() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return p.TextBytes(), err
}
func (s SessionLog) SetTimestamp(v string) error {
return s.Struct.SetText(0, v)
}
func (s SessionLog) Content() ([]byte, error) {
p, err := s.Struct.Ptr(1)
return []byte(p.Data()), err
}
func (s SessionLog) HasContent() bool {
p, err := s.Struct.Ptr(1)
return p.IsValid() || err != nil
}
func (s SessionLog) SetContent(v []byte) error {
return s.Struct.SetData(1, v)
}
// SessionLog_List is a list of SessionLog.
type SessionLog_List struct{ capnp.List }
// NewSessionLog creates a new list of SessionLog.
func NewSessionLog_List(s *capnp.Segment, sz int32) (SessionLog_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
return SessionLog_List{l}, err
}
func (s SessionLog_List) At(i int) SessionLog { return SessionLog{s.List.Struct(i)} }
func (s SessionLog_List) Set(i int, v SessionLog) error { return s.List.SetStruct(i, v.Struct) }
func (s SessionLog_List) String() string {
str, _ := text.MarshalList(0xa13a07c504a5ab64, s.List)
return str
}
// SessionLog_Promise is a wrapper for a SessionLog promised by a client call.
type SessionLog_Promise struct{ *capnp.Pipeline }
func (p SessionLog_Promise) Struct() (SessionLog, error) {
s, err := p.Pipeline.Struct()
return SessionLog{s}, err
}
const schema_8f43375162194466 = "x\xda\x120q`\x12d\x8dg`\x08dae\xfb" +
"\x9f\xb2z)\xcbQv\xab\x85\x0c\x82B\x8c\xff\xd3\\" +
"$\x93\x02\xcd\x9d\xfb\x19X\x99\xd8\x19\x18\x04E_\x09" +
"*\x82h\xd9r\x06\xc6\xff\xc5\xa9\xc5\xc5\x99\xf9y\xf1" +
"L9\xf9\xe9z\xc9\x89\x05y\x05V\xc1`!\xfe<" +
"\x9f\xfc\xf4\x00F\xc6@\x0ef\x16\x06\x06\x16F\x06\x06" +
"A\xcd \x06\x86@\x0df\xc6@\x13&FAFF" +
"\x11F\x90\xa0\xa1\x13\x03C\xa0\x0e3c\xa0\x05\x13\xe3" +
"\xff\x92\xcc\xdc\xd4\xe2\x92\xc4\\\x06\xc6\x02F\x1e\x06&" +
"F\x1e\x06\xc6\xfa\xe4\xfc\xbc\x92\xd4\xbc\x12F^\x06&" +
"F^\x06F@\x00\x00\x00\xff\xff\xdaK$\x1a"
func init() {
schemas.Register(schema_8f43375162194466,
0xa13a07c504a5ab64)
}
+71
View File
@@ -0,0 +1,71 @@
package sshlog
import (
"time"
"github.com/sirupsen/logrus"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs"
)
// SessionLogger will buffer and write events to disk using capnp proto for session replay
type SessionLogger struct {
logger *Logger
encoder *capnp.Encoder
}
type sessionLogData struct {
Timestamp string // The UTC timestamp of when the log occurred
Content []byte // The shell output
}
// NewSessionLogger creates a new session logger by encapsulating a Logger object and writing capnp encoded messages to it
func NewSessionLogger(filename string, logger *logrus.Logger, flushInterval time.Duration, maxFileSize int64) (*SessionLogger, error) {
l, err := NewLogger(filename, logger, flushInterval, maxFileSize)
if err != nil {
return nil, err
}
sessionLogger := &SessionLogger{
logger: l,
encoder: capnp.NewEncoder(l),
}
return sessionLogger, nil
}
// Writes to a log buffer. Implements the io.Writer interface.
func (l *SessionLogger) Write(p []byte) (n int, err error) {
return l.writeSessionLog(&sessionLogData{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Content: p,
})
}
// Close drains anything left in the buffer and cleans up any resources still
// in use.
func (l *SessionLogger) Close() error {
return l.logger.Close()
}
func (l *SessionLogger) writeSessionLog(p *sessionLogData) (int, error) {
msg, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return 0, err
}
log, err := NewRootSessionLog(seg)
if err != nil {
return 0, err
}
log.SetTimestamp(p.Timestamp)
log.SetContent(p.Content)
if err := l.encoder.Encode(msg); err != nil {
return 0, err
}
return len(p.Content), nil
}
func unmarshalSessionLog(s SessionLog) (*sessionLogData, error) {
p := new(sessionLogData)
err := pogs.Extract(p, SessionLog_TypeID, s.Struct)
return p, err
}
+69
View File
@@ -0,0 +1,69 @@
package sshlog
import (
"os"
"testing"
"time"
"github.com/sirupsen/logrus"
capnp "zombiezen.com/go/capnproto2"
)
const sessionLogFileName = "test-session-logger.log"
func createSessionLogger(t *testing.T) *SessionLogger {
os.Remove(sessionLogFileName)
l := logrus.New()
logger, err := NewSessionLogger(sessionLogFileName, l, time.Millisecond, 1024)
if err != nil {
t.Fatal("couldn't create the logger!", err)
}
return logger
}
func TestSessionLogWrite(t *testing.T) {
testStr := "hi"
logger := createSessionLogger(t)
defer func() {
logger.Close()
os.Remove(sessionLogFileName)
}()
logger.Write([]byte(testStr))
time.Sleep(2 * time.Millisecond)
f, err := os.Open(sessionLogFileName)
if err != nil {
t.Fatal("couldn't read the log file!", err)
}
defer f.Close()
msg, err := capnp.NewDecoder(f).Decode()
if err != nil {
t.Fatal("couldn't read the capnp msg file!", err)
}
sessionLog, err := ReadRootSessionLog(msg)
if err != nil {
t.Fatal("couldn't read the session log from the msg!", err)
}
timeStr, err := sessionLog.Timestamp()
if err != nil {
t.Fatal("couldn't read the Timestamp field!", err)
}
_, terr := time.Parse(time.RFC3339, timeStr)
if terr != nil {
t.Fatal("couldn't parse the Timestamp into the expected RFC3339 format", terr)
}
data, err := sessionLog.Content()
if err != nil {
t.Fatal("couldn't read the Content field!", err)
}
checkStr := string(data)
if checkStr != testStr {
t.Fatal("file data doesn't match!")
}
}
-98
View File
@@ -1,98 +0,0 @@
//+build !windows
package sshserver
import (
"fmt"
"io/ioutil"
"os"
"path"
"github.com/gliderlabs/ssh"
"github.com/pkg/errors"
gossh "golang.org/x/crypto/ssh"
)
var (
systemConfigPath = "/etc/cloudflared/"
authorizedKeysDir = ".cloudflared/authorized_keys"
)
func (s *SSHServer) authorizedKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
sshUser, err := s.getUserFunc(ctx.User())
if err != nil {
s.logger.Debugf("Invalid user: %s", ctx.User())
return false
}
authorizedKeysPath := path.Join(sshUser.HomeDir, authorizedKeysDir)
if _, err := os.Stat(authorizedKeysPath); os.IsNotExist(err) {
s.logger.Debugf("authorized_keys file %s not found", authorizedKeysPath)
return false
}
authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeysPath)
if err != nil {
s.logger.WithError(err).Errorf("Failed to load authorized_keys %s", authorizedKeysPath)
return false
}
for len(authorizedKeysBytes) > 0 {
// Skips invalid keys. Returns error if no valid keys remain.
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
authorizedKeysBytes = rest
if err != nil {
s.logger.Errorf("Invalid key(s) found in %s", authorizedKeysPath)
return false
}
if ssh.KeysEqual(pubKey, key) {
ctx.SetValue("sshUser", sshUser)
return true
}
}
s.logger.Debugf("Matching public key not found in %s", authorizedKeysPath)
return false
}
func (s *SSHServer) shortLivedCertHandler(ctx ssh.Context, key ssh.PublicKey) bool {
userCert, ok := key.(*gossh.Certificate)
if !ok {
s.logger.Debug("Received key is not an SSH certificate")
return false
}
if !ssh.KeysEqual(s.caCert, userCert.SignatureKey) {
s.logger.Debug("CA certificate does not match user certificate signer")
return false
}
checker := gossh.CertChecker{}
if err := checker.CheckCert(ctx.User(), userCert); err != nil {
s.logger.Debug(err)
return false
} else {
sshUser, err := s.getUserFunc(ctx.User())
if err != nil {
s.logger.Debugf("Invalid user: %s", ctx.User())
return false
}
ctx.SetValue("sshUser", sshUser)
}
return true
}
func getCACert() (ssh.PublicKey, error) {
caCertPath := path.Join(systemConfigPath, "ca.pub")
caCertBytes, err := ioutil.ReadFile(caCertPath)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("Failed to load CA certertificate %s", caCertPath))
}
caCert, _, _, _, err := ssh.ParseAuthorizedKey(caCertBytes)
if err != nil {
return nil, errors.Wrap(err, "Failed to parse CA Certificate")
}
return caCert, nil
}
-192
View File
@@ -1,192 +0,0 @@
//+build !windows
package sshserver
import (
"context"
"io/ioutil"
"net"
"os"
"os/user"
"path"
"sync"
"testing"
"github.com/cloudflare/cloudflared/log"
"github.com/gliderlabs/ssh"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
validPrincipal = "testUser"
testDir = "testdata"
testUserKeyFilename = "id_rsa.pub"
testCAFilename = "ca.pub"
testOtherCAFilename = "other_ca.pub"
testUserCertFilename = "id_rsa-cert.pub"
)
var logger, hook = test.NewNullLogger()
func TestMain(m *testing.M) {
authorizedKeysDir = testUserKeyFilename
logger.SetLevel(logrus.DebugLevel)
code := m.Run()
os.Exit(code)
}
func TestPublicKeyAuth_Success(t *testing.T) {
context, cancel := newMockContext(validPrincipal)
defer cancel()
sshServer := SSHServer{getUserFunc: getMockUser}
pubKey := getKey(t, testUserKeyFilename)
assert.True(t, sshServer.authorizedKeyHandler(context, pubKey))
}
func TestPublicKeyAuth_MissingKey(t *testing.T) {
context, cancel := newMockContext(validPrincipal)
defer cancel()
sshServer := SSHServer{logger: logger, getUserFunc: getMockUser}
pubKey := getKey(t, testOtherCAFilename)
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
assert.Contains(t, hook.LastEntry().Message, "Matching public key not found in")
}
func TestPublicKeyAuth_InvalidUser(t *testing.T) {
context, cancel := newMockContext("notAUser")
defer cancel()
sshServer := SSHServer{logger: logger, getUserFunc: lookupUser}
pubKey := getKey(t, testUserKeyFilename)
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
assert.Contains(t, hook.LastEntry().Message, "Invalid user")
}
func TestPublicKeyAuth_MissingFile(t *testing.T) {
currentUser, err := user.Current()
require.Nil(t, err)
context, cancel := newMockContext(currentUser.Username)
defer cancel()
sshServer := SSHServer{Server: ssh.Server{}, logger: logger, getUserFunc: lookupUser}
pubKey := getKey(t, testUserKeyFilename)
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
assert.Contains(t, hook.LastEntry().Message, "not found")
}
func TestShortLivedCerts_Success(t *testing.T) {
context, cancel := newMockContext(validPrincipal)
defer cancel()
caCert := getKey(t, testCAFilename)
sshServer := SSHServer{logger: log.CreateLogger(), caCert: caCert, getUserFunc: getMockUser}
userCert := getKey(t, testUserCertFilename)
assert.True(t, sshServer.shortLivedCertHandler(context, userCert))
}
func TestShortLivedCerts_CAsDontMatch(t *testing.T) {
context, cancel := newMockContext(validPrincipal)
defer cancel()
caCert := getKey(t, testOtherCAFilename)
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: getMockUser}
userCert := getKey(t, testUserCertFilename)
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
assert.Equal(t, "CA certificate does not match user certificate signer", hook.LastEntry().Message)
}
func TestShortLivedCerts_UserDoesNotExist(t *testing.T) {
context, cancel := newMockContext(validPrincipal)
defer cancel()
caCert := getKey(t, testCAFilename)
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: lookupUser}
userCert := getKey(t, testUserCertFilename)
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
assert.Contains(t, hook.LastEntry().Message, "Invalid user")
}
func TestShortLivedCerts_InvalidPrincipal(t *testing.T) {
context, cancel := newMockContext("notAUser")
defer cancel()
caCert := getKey(t, testCAFilename)
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: lookupUser}
userCert := getKey(t, testUserCertFilename)
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
assert.Contains(t, hook.LastEntry().Message, "not in the set of valid principals for given certificate")
}
func getMockUser(_ string) (*User, error) {
return &User{
Username: validPrincipal,
HomeDir: testDir,
}, nil
}
func getKey(t *testing.T, filename string) ssh.PublicKey {
path := path.Join(testDir, filename)
bytes, err := ioutil.ReadFile(path)
require.Nil(t, err)
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(bytes)
require.Nil(t, err)
return pubKey
}
type mockSSHContext struct {
context.Context
*sync.Mutex
}
func newMockContext(user string) (*mockSSHContext, context.CancelFunc) {
innerCtx, cancel := context.WithCancel(context.Background())
mockCtx := &mockSSHContext{innerCtx, &sync.Mutex{}}
mockCtx.SetValue("user", user)
return mockCtx, cancel
}
func (ctx *mockSSHContext) SetValue(key, value interface{}) {
ctx.Context = context.WithValue(ctx.Context, key, value)
}
func (ctx *mockSSHContext) User() string {
return ctx.Value("user").(string)
}
func (ctx *mockSSHContext) SessionID() string {
return ""
}
func (ctx *mockSSHContext) ClientVersion() string {
return ""
}
func (ctx *mockSSHContext) ServerVersion() string {
return ""
}
func (ctx *mockSSHContext) RemoteAddr() net.Addr {
return nil
}
func (ctx *mockSSHContext) LocalAddr() net.Addr {
return nil
}
func (ctx *mockSSHContext) Permissions() *ssh.Permissions {
return nil
}
-226
View File
@@ -1,226 +0,0 @@
// Taken from https://github.com/golang/go/blob/ad644d2e86bab85787879d41c2d2aebbd7c57db8/src/os/user/user.go
// and modified to return login shell in User struct. cloudflared requires cgo for compilation because of this addition.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris
// +build cgo,!osusergo
package sshserver
import (
"fmt"
"strconv"
"strings"
"syscall"
"unsafe"
)
/*
#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>
static int mygetpwuid_r(int uid, struct passwd *pwd,
char *buf, size_t buflen, struct passwd **result) {
return getpwuid_r(uid, pwd, buf, buflen, result);
}
static int mygetpwnam_r(const char *name, struct passwd *pwd,
char *buf, size_t buflen, struct passwd **result) {
return getpwnam_r(name, pwd, buf, buflen, result);
}
static int mygetgrgid_r(int gid, struct group *grp,
char *buf, size_t buflen, struct group **result) {
return getgrgid_r(gid, grp, buf, buflen, result);
}
static int mygetgrnam_r(const char *name, struct group *grp,
char *buf, size_t buflen, struct group **result) {
return getgrnam_r(name, grp, buf, buflen, result);
}
*/
import "C"
type UnknownUserIdError int
func (e UnknownUserIdError) Error() string {
return "user: unknown userid " + strconv.Itoa(int(e))
}
// UnknownUserError is returned by Lookup when
// a user cannot be found.
type UnknownUserError string
func (e UnknownUserError) Error() string {
return "user: unknown user " + string(e)
}
// UnknownGroupIdError is returned by LookupGroupId when
// a group cannot be found.
type UnknownGroupIdError string
func (e UnknownGroupIdError) Error() string {
return "group: unknown groupid " + string(e)
}
// UnknownGroupError is returned by LookupGroup when
// a group cannot be found.
type UnknownGroupError string
func (e UnknownGroupError) Error() string {
return "group: unknown group " + string(e)
}
type User struct {
// Uid is the user ID.
// On POSIX systems, this is a decimal number representing the uid.
// On Windows, this is a security identifier (SID) in a string format.
// On Plan 9, this is the contents of /dev/user.
Uid string
// Gid is the primary group ID.
// On POSIX systems, this is a decimal number representing the gid.
// On Windows, this is a SID in a string format.
// On Plan 9, this is the contents of /dev/user.
Gid string
// Username is the login name.
Username string
// Name is the user's real or display name.
// It might be blank.
// On POSIX systems, this is the first (or only) entry in the GECOS field
// list.
// On Windows, this is the user's display name.
// On Plan 9, this is the contents of /dev/user.
Name string
// HomeDir is the path to the user's home directory (if they have one).
HomeDir string
/****************** Begin added code ******************/
// Login shell
Shell string
/****************** End added code ******************/
}
func lookupUser(username string) (*User, error) {
var pwd C.struct_passwd
var result *C.struct_passwd
nameC := make([]byte, len(username)+1)
copy(nameC, username)
buf := alloc(userBuffer)
defer buf.free()
err := retryWithBuffer(buf, func() syscall.Errno {
// mygetpwnam_r is a wrapper around getpwnam_r to avoid
// passing a size_t to getpwnam_r, because for unknown
// reasons passing a size_t to getpwnam_r doesn't work on
// Solaris.
return syscall.Errno(C.mygetpwnam_r((*C.char)(unsafe.Pointer(&nameC[0])),
&pwd,
(*C.char)(buf.ptr),
C.size_t(buf.size),
&result))
})
if err != nil {
return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
}
if result == nil {
return nil, UnknownUserError(username)
}
return buildUser(&pwd), err
}
func buildUser(pwd *C.struct_passwd) *User {
u := &User{
Uid: strconv.FormatUint(uint64(pwd.pw_uid), 10),
Gid: strconv.FormatUint(uint64(pwd.pw_gid), 10),
Username: C.GoString(pwd.pw_name),
Name: C.GoString(pwd.pw_gecos),
HomeDir: C.GoString(pwd.pw_dir),
/****************** Begin added code ******************/
Shell: C.GoString(pwd.pw_shell),
/****************** End added code ******************/
}
// The pw_gecos field isn't quite standardized. Some docs
// say: "It is expected to be a comma separated list of
// personal data where the first item is the full name of the
// user."
if i := strings.Index(u.Name, ","); i >= 0 {
u.Name = u.Name[:i]
}
return u
}
type bufferKind C.int
const (
userBuffer = bufferKind(C._SC_GETPW_R_SIZE_MAX)
)
func (k bufferKind) initialSize() C.size_t {
sz := C.sysconf(C.int(k))
if sz == -1 {
// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
// Additionally, not all Linux systems have it, either. For
// example, the musl libc returns -1.
return 1024
}
if !isSizeReasonable(int64(sz)) {
// Truncate. If this truly isn't enough, retryWithBuffer will error on the first run.
return maxBufferSize
}
return C.size_t(sz)
}
type memBuffer struct {
ptr unsafe.Pointer
size C.size_t
}
func alloc(kind bufferKind) *memBuffer {
sz := kind.initialSize()
return &memBuffer{
ptr: C.malloc(sz),
size: sz,
}
}
func (mb *memBuffer) resize(newSize C.size_t) {
mb.ptr = C.realloc(mb.ptr, newSize)
mb.size = newSize
}
func (mb *memBuffer) free() {
C.free(mb.ptr)
}
// retryWithBuffer repeatedly calls f(), increasing the size of the
// buffer each time, until f succeeds, fails with a non-ERANGE error,
// or the buffer exceeds a reasonable limit.
func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error {
for {
errno := f()
if errno == 0 {
return nil
} else if errno != syscall.ERANGE {
return errno
}
newSize := buf.size * 2
if !isSizeReasonable(int64(newSize)) {
return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
}
buf.resize(newSize)
}
}
const maxBufferSize = 1 << 20
func isSizeReasonable(sz int64) bool {
return sz > 0 && sz <= maxBufferSize
}
+35 -31
View File
@@ -19,46 +19,44 @@ import (
)
const (
rsaFilename = "ssh_host_rsa_key"
ecdsaFilename = "ssh_host_ecdsa_key"
rsaFilename = "ssh_host_rsa_key"
ecdsaFilename = "ssh_host_ecdsa_key"
)
func (s *SSHServer) configureHostKeys() error {
if _, err := os.Stat(systemConfigPath); os.IsNotExist(err) {
if err := os.MkdirAll(systemConfigPath, 0755); err != nil {
return errors.Wrap(err, fmt.Sprintf("Error creating %s directory", systemConfigPath))
var defaultHostKeyDir = filepath.Join(".cloudflared", "host_keys")
func (s *SSHProxy) configureHostKeys(hostKeyDir string) error {
if hostKeyDir == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
hostKeyDir = filepath.Join(homeDir, defaultHostKeyDir)
}
if _, err := os.Stat(hostKeyDir); os.IsNotExist(err) {
if err := os.MkdirAll(hostKeyDir, 0755); err != nil {
return errors.Wrap(err, fmt.Sprintf("Error creating %s directory", hostKeyDir))
}
}
if err := s.configureHostKey(s.ensureECDSAKeyExists); err != nil {
if err := s.configureECDSAKey(hostKeyDir); err != nil {
return err
}
if err := s.configureHostKey(s.ensureRSAKeyExists); err != nil {
if err := s.configureRSAKey(hostKeyDir); err != nil {
return err
}
return nil
}
func (s *SSHServer) configureHostKey(keyFunc func() (string, error)) error {
path, err := keyFunc()
if err != nil {
return err
}
if err := s.SetOption(ssh.HostKeyFile(path)); err != nil {
return errors.Wrap(err, "Could not set SSH host key")
}
return nil
}
func (s *SSHServer) ensureRSAKeyExists() (string, error) {
keyPath := filepath.Join(systemConfigPath, rsaFilename)
func (s *SSHProxy) configureRSAKey(basePath string) error {
keyPath := filepath.Join(basePath, rsaFilename)
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", errors.Wrap(err, "Error generating RSA host key")
return errors.Wrap(err, "Error generating RSA host key")
}
privateKey := &pem.Block{
@@ -67,25 +65,28 @@ func (s *SSHServer) ensureRSAKeyExists() (string, error) {
}
if err = writePrivateKey(keyPath, privateKey); err != nil {
return "", err
return err
}
s.logger.Debug("Created new RSA SSH host key: ", keyPath)
}
return keyPath, nil
if err := s.SetOption(ssh.HostKeyFile(keyPath)); err != nil {
return errors.Wrap(err, "Could not set SSH RSA host key")
}
return nil
}
func (s *SSHServer) ensureECDSAKeyExists() (string, error) {
keyPath := filepath.Join(systemConfigPath, ecdsaFilename)
func (s *SSHProxy) configureECDSAKey(basePath string) error {
keyPath := filepath.Join(basePath, ecdsaFilename)
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return "", errors.Wrap(err, "Error generating ECDSA host key")
return errors.Wrap(err, "Error generating ECDSA host key")
}
keyBytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
return "", errors.Wrap(err, "Error marshalling ECDSA key")
return errors.Wrap(err, "Error marshalling ECDSA key")
}
privateKey := &pem.Block{
@@ -94,12 +95,15 @@ func (s *SSHServer) ensureECDSAKeyExists() (string, error) {
}
if err = writePrivateKey(keyPath, privateKey); err != nil {
return "", err
return err
}
s.logger.Debug("Created new ECDSA SSH host key: ", keyPath)
}
return keyPath, nil
if err := s.SetOption(ssh.HostKeyFile(keyPath)); err != nil {
return errors.Wrap(err, "Could not set SSH ECDSA host key")
}
return nil
}
func writePrivateKey(keyPath string, privateKey *pem.Block) error {
+29
View File
@@ -0,0 +1,29 @@
package sshserver
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
func TestHasPort(t *testing.T) {
type testCase struct {
input string
expectedOutput string
}
tests := []testCase{
{"localhost", "localhost:22"},
{"other.addr:22", "other.addr:22"},
{"[2001:db8::1]:8080", "[2001:db8::1]:8080"},
{"[::1]", "[::1]:22"},
{"2001:0db8:3c4d:0015:0000:0000:1a2f:1234", "[2001:0db8:3c4d:0015:0000:0000:1a2f:1234]:22"},
{"::1", "[::1]:22"},
}
for _, test := range tests {
out, err := canonicalizeDest(test.input)
require.Nil(t, err)
assert.Equal(t, test.expectedOutput, out)
}
}
+408 -124
View File
@@ -3,66 +3,110 @@
package sshserver
import (
"bufio"
"errors"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"strconv"
"syscall"
"net"
"runtime"
"strings"
"time"
"unsafe"
"github.com/creack/pty"
"github.com/cloudflare/cloudflared/sshgen"
"github.com/cloudflare/cloudflared/sshlog"
"github.com/gliderlabs/ssh"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
gossh "golang.org/x/crypto/ssh"
)
type SSHServer struct {
const (
auditEventStart = "session_start"
auditEventStop = "session_stop"
auditEventExec = "exec"
auditEventScp = "scp"
auditEventResize = "resize"
auditEventShell = "shell"
sshContextSessionID = "sessionID"
sshContextEventLogger = "eventLogger"
sshContextPreamble = "sshPreamble"
sshContextSSHClient = "sshClient"
SSHPreambleLength = 2
defaultSSHPort = "22"
)
type auditEvent struct {
Event string `json:"event,omitempty"`
EventType string `json:"event_type,omitempty"`
SessionID string `json:"session_id,omitempty"`
User string `json:"user,omitempty"`
Login string `json:"login,omitempty"`
Datetime string `json:"datetime,omitempty"`
Hostname string `json:"hostname,omitempty"`
Destination string `json:"destination,omitempty"`
}
// sshConn wraps the incoming net.Conn and a cleanup function
// This is done to allow the outgoing SSH client to be retrieved and closed when the conn itself is closed.
type sshConn struct {
net.Conn
cleanupFunc func()
}
// close calls the cleanupFunc before closing the conn
func (c sshConn) Close() error {
c.cleanupFunc()
return c.Conn.Close()
}
type SSHProxy struct {
ssh.Server
logger *logrus.Logger
shutdownC chan struct{}
caCert ssh.PublicKey
getUserFunc func(string) (*User, error)
hostname string
logger *logrus.Logger
shutdownC chan struct{}
caCert ssh.PublicKey
logManager sshlog.Manager
}
func New(logger *logrus.Logger, address string, shutdownC chan struct{}, shortLivedCertAuth bool, idleTimeout, maxTimeout time.Duration) (*SSHServer, error) {
currentUser, err := user.Current()
if err != nil {
return nil, err
}
if currentUser.Uid != "0" {
return nil, errors.New("cloudflared SSH server needs to run as root")
type SSHPreamble struct {
Destination string
JWT string
}
// New creates a new SSHProxy and configures its host keys and authentication by the data provided
func New(logManager sshlog.Manager, logger *logrus.Logger, version, localAddress, hostname, hostKeyDir string, shutdownC chan struct{}, idleTimeout, maxTimeout time.Duration) (*SSHProxy, error) {
sshProxy := SSHProxy{
hostname: hostname,
logger: logger,
shutdownC: shutdownC,
logManager: logManager,
}
sshServer := SSHServer{
Server: ssh.Server{Addr: address, MaxTimeout: maxTimeout, IdleTimeout: idleTimeout},
logger: logger,
shutdownC: shutdownC,
getUserFunc: lookupUser,
sshProxy.Server = ssh.Server{
Addr: localAddress,
MaxTimeout: maxTimeout,
IdleTimeout: idleTimeout,
Version: fmt.Sprintf("SSH-2.0-Cloudflare-Access_%s_%s", version, runtime.GOOS),
PublicKeyHandler: sshProxy.proxyAuthCallback,
ConnCallback: sshProxy.connCallback,
ChannelHandlers: map[string]ssh.ChannelHandler{
"default": sshProxy.channelHandler,
},
}
if err := sshServer.configureHostKeys(); err != nil {
if err := sshProxy.configureHostKeys(hostKeyDir); err != nil {
return nil, err
}
if shortLivedCertAuth {
caCert, err := getCACert()
if err != nil {
return nil, err
}
sshServer.caCert = caCert
sshServer.PublicKeyHandler = sshServer.shortLivedCertHandler
} else {
sshServer.PublicKeyHandler = sshServer.authorizedKeyHandler
}
return &sshServer, nil
return &sshProxy, nil
}
func (s *SSHServer) Start() error {
// Start the SSH proxy listener to start handling SSH connections from clients
func (s *SSHProxy) Start() error {
s.logger.Infof("Starting SSH server at %s", s.Addr)
go func() {
@@ -72,120 +116,360 @@ func (s *SSHServer) Start() error {
}
}()
s.Handle(s.connectionHandler)
return s.ListenAndServe()
}
func (s *SSHServer) connectionHandler(session ssh.Session) {
// Get uid and gid of user attempting to login
sshUser, ok := session.Context().Value("sshUser").(*User)
if !ok || sshUser == nil {
s.logger.Error("Error retrieving credentials from session")
s.CloseSession(session)
return
// proxyAuthCallback attempts to connect to ultimate SSH destination. If successful, it allows the incoming connection
// to connect to the proxy and saves the outgoing SSH client to the context. Otherwise, no connection to the
// the proxy is allowed.
func (s *SSHProxy) proxyAuthCallback(ctx ssh.Context, key ssh.PublicKey) bool {
client, err := s.dialDestination(ctx)
if err != nil {
return false
}
ctx.SetValue(sshContextSSHClient, client)
return true
}
// Spawn shell under user
cmd := exec.Command(sshUser.Shell)
// connCallback reads the preamble sent from the proxy server and saves an audit event logger to the context.
// If any errors occur, the connection is terminated by returning nil from the callback.
func (s *SSHProxy) connCallback(ctx ssh.Context, conn net.Conn) net.Conn {
// AUTH-2050: This is a temporary workaround of a timing issue in the tunnel muxer to allow further testing.
// TODO: Remove this
time.Sleep(10 * time.Millisecond)
ptyReq, winCh, isPty := session.Pty()
if !isPty {
if _, err := io.WriteString(session, "No PTY requested.\n"); err != nil {
s.logger.WithError(err).Error("No PTY requested: Failed to write to SSH session")
preamble, err := s.readPreamble(conn)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
s.logger.Warn("Could not establish session. Client likely does not have --destination set and is using old-style ssh config")
} else if err != io.EOF {
s.logger.WithError(err).Error("failed to read SSH preamble")
}
s.CloseSession(session)
return
return nil
}
ctx.SetValue(sshContextPreamble, preamble)
uidInt, err := stringToUint32(sshUser.Uid)
logger, sessionID, err := s.auditLogger()
if err != nil {
s.logger.WithError(err).Error("Invalid user")
s.CloseSession(session)
return
s.logger.WithError(err).Error("failed to configure logger")
return nil
}
gidInt, err := stringToUint32(sshUser.Gid)
if err != nil {
s.logger.WithError(err).Error("Invalid user group")
s.CloseSession(session)
ctx.SetValue(sshContextEventLogger, logger)
ctx.SetValue(sshContextSessionID, sessionID)
// attempts to retrieve and close the outgoing ssh client when the incoming conn is closed.
// If no client exists, the conn is being closed before the PublicKeyCallback was called (where the client is created).
cleanupFunc := func() {
client, ok := ctx.Value(sshContextSSHClient).(*gossh.Client)
if ok && client != nil {
client.Close()
}
}
return sshConn{conn, cleanupFunc}
}
// channelHandler proxies incoming and outgoing SSH traffic back and forth over an SSH Channel
func (s *SSHProxy) channelHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
if newChan.ChannelType() != "session" && newChan.ChannelType() != "direct-tcpip" {
msg := fmt.Sprintf("channel type %s is not supported", newChan.ChannelType())
s.logger.Info(msg)
if err := newChan.Reject(gossh.UnknownChannelType, msg); err != nil {
s.logger.WithError(err).Error("Error rejecting SSH channel")
}
return
}
cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uidInt, Gid: gidInt}}
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", sshUser.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", sshUser.HomeDir))
cmd.Dir = sshUser.HomeDir
psuedoTTY, err := pty.Start(cmd)
localChan, localChanReqs, err := newChan.Accept()
if err != nil {
s.logger.WithError(err).Error("Failed to start pty session")
s.CloseSession(session)
close(s.shutdownC)
s.logger.WithError(err).Error("Failed to accept session channel")
return
}
defer localChan.Close()
// client will be closed when the sshConn is closed
client, ok := ctx.Value(sshContextSSHClient).(*gossh.Client)
if !ok {
s.logger.Error("Could not retrieve client from context")
return
}
// Handle terminal window size changes
remoteChan, remoteChanReqs, err := client.OpenChannel(newChan.ChannelType(), newChan.ExtraData())
if err != nil {
s.logger.WithError(err).Error("Failed to open remote channel")
return
}
defer remoteChan.Close()
// Proxy ssh traffic back and forth between client and destination
s.proxyChannel(localChan, remoteChan, localChanReqs, remoteChanReqs, conn, ctx)
}
// proxyChannel couples two SSH channels and proxies SSH traffic and channel requests back and forth.
func (s *SSHProxy) proxyChannel(localChan, remoteChan gossh.Channel, localChanReqs, remoteChanReqs <-chan *gossh.Request, conn *gossh.ServerConn, ctx ssh.Context) {
done := make(chan struct{}, 2)
go func() {
for win := range winCh {
if errNo := setWinsize(psuedoTTY, win.Width, win.Height); errNo != 0 {
s.logger.WithError(err).Error("Failed to set pty window size: ", err.Error())
s.CloseSession(session)
close(s.shutdownC)
if _, err := io.Copy(localChan, remoteChan); err != nil {
s.logger.WithError(err).Error("remote to local copy error")
}
done <- struct{}{}
}()
go func() {
if _, err := io.Copy(remoteChan, localChan); err != nil {
s.logger.WithError(err).Error("local to remote copy error")
}
done <- struct{}{}
}()
s.logAuditEvent(conn, "", auditEventStart, ctx)
defer s.logAuditEvent(conn, "", auditEventStop, ctx)
// Proxy channel requests
for {
select {
case req := <-localChanReqs:
if req == nil {
return
}
if err := s.forwardChannelRequest(remoteChan, req); err != nil {
s.logger.WithError(err).Error("Failed to forward request")
return
}
s.logChannelRequest(req, conn, ctx)
case req := <-remoteChanReqs:
if req == nil {
return
}
if err := s.forwardChannelRequest(localChan, req); err != nil {
s.logger.WithError(err).Error("Failed to forward request")
return
}
case <-done:
return
}
}
}
// readPreamble reads a preamble from the SSH connection before any SSH traffic is sent.
// This preamble is a JSON encoded struct containing the users JWT and ultimate destination.
// The first 4 bytes contain the length of the preamble which follows immediately.
func (s *SSHProxy) readPreamble(conn net.Conn) (*SSHPreamble, error) {
// Set conn read deadline while reading preamble to prevent hangs if preamble wasnt sent.
if err := conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)); err != nil {
return nil, errors.Wrap(err, "failed to set conn deadline")
}
defer func() {
if err := conn.SetReadDeadline(time.Time{}); err != nil {
s.logger.WithError(err).Error("Failed to unset conn read deadline")
}
}()
// Write incoming commands to PTY
go func() {
if _, err := io.Copy(psuedoTTY, session); err != nil {
s.logger.WithError(err).Error("Failed to write incoming command to pty")
size := make([]byte, SSHPreambleLength)
if _, err := io.ReadFull(conn, size); err != nil {
return nil, err
}
payloadLength := binary.BigEndian.Uint16(size)
payload := make([]byte, payloadLength)
if _, err := io.ReadFull(conn, payload); err != nil {
return nil, err
}
var preamble SSHPreamble
err := json.Unmarshal(payload, &preamble)
if err != nil {
return nil, err
}
preamble.Destination, err = canonicalizeDest(preamble.Destination)
if err != nil {
return nil, err
}
return &preamble, nil
}
// canonicalizeDest adds a default port if one doesnt exist
func canonicalizeDest(dest string) (string, error) {
_, _, err := net.SplitHostPort(dest)
// if host and port are split without error, a port exists.
if err != nil {
addrErr, ok := err.(*net.AddrError)
if !ok {
return "", err
}
}()
pr, pw := io.Pipe()
scanner := bufio.NewScanner(pr)
go func() {
for scanner.Scan() {
s.logger.Info(scanner.Text())
// If the port is missing, append it.
if addrErr.Err == "missing port in address" {
return fmt.Sprintf("%s:%s", dest, defaultSSHPort), nil
}
}()
// Write outgoing command output to both the command recorder, and remote user
mw := io.MultiWriter(pw, session)
if _, err := io.Copy(mw, psuedoTTY); err != nil {
s.logger.WithError(err).Error("Failed to write command output to user")
// If there are too many colons and address is IPv6, wrap in brackets and append port. Otherwise invalid address
ip := net.ParseIP(dest)
if addrErr.Err == "too many colons in address" && ip != nil && ip.To4() == nil {
return fmt.Sprintf("[%s]:%s", dest, defaultSSHPort), nil
}
return "", addrErr
}
if err := pw.Close(); err != nil {
s.logger.WithError(err).Error("Failed to close pipe writer")
}
if err := pr.Close(); err != nil {
s.logger.WithError(err).Error("Failed to close pipe reader")
}
// Wait for all resources associated with cmd to be released
// Returns error if shell exited with a non-zero status or received a signal
if err := cmd.Wait(); err != nil {
s.logger.WithError(err).Debug("Shell did not close correctly")
}
return dest, nil
}
func (s *SSHServer) CloseSession(session ssh.Session) {
if err := session.Exit(1); err != nil {
s.logger.WithError(err).Error("Failed to close SSH session")
// dialDestination creates a new SSH client and dials the destination server
func (s *SSHProxy) dialDestination(ctx ssh.Context) (*gossh.Client, error) {
preamble, ok := ctx.Value(sshContextPreamble).(*SSHPreamble)
if !ok {
msg := "failed to retrieve SSH preamble from context"
s.logger.Error(msg)
return nil, errors.New(msg)
}
signer, err := s.genSSHSigner(preamble.JWT)
if err != nil {
s.logger.WithError(err).Error("Failed to generate signed short lived cert")
return nil, err
}
clientConfig := &gossh.ClientConfig{
User: ctx.User(),
// AUTH-2103 TODO: proper host key check
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
Auth: []gossh.AuthMethod{gossh.PublicKeys(signer)},
ClientVersion: ctx.ServerVersion(),
}
client, err := gossh.Dial("tcp", preamble.Destination, clientConfig)
if err != nil {
s.logger.WithError(err).Info("Failed to connect to destination SSH server")
return nil, err
}
return client, nil
}
// Generates a key pair and sends public key to get signed by CA
func (s *SSHProxy) genSSHSigner(jwt string) (gossh.Signer, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, errors.Wrap(err, "failed to generate ecdsa key pair")
}
pub, err := gossh.NewPublicKey(&key.PublicKey)
if err != nil {
return nil, errors.Wrap(err, "failed to convert ecdsa public key to SSH public key")
}
pubBytes := gossh.MarshalAuthorizedKey(pub)
signedCertBytes, err := sshgen.SignCert(jwt, string(pubBytes))
if err != nil {
return nil, errors.Wrap(err, "failed to retrieve cert from SSHCAAPI")
}
signedPub, _, _, _, err := gossh.ParseAuthorizedKey([]byte(signedCertBytes))
if err != nil {
return nil, errors.Wrap(err, "failed to parse SSH public key")
}
cert, ok := signedPub.(*gossh.Certificate)
if !ok {
return nil, errors.Wrap(err, "failed to assert public key as certificate")
}
signer, err := gossh.NewSignerFromKey(key)
if err != nil {
return nil, errors.Wrap(err, "failed to create signer")
}
certSigner, err := gossh.NewCertSigner(cert, signer)
if err != nil {
return nil, errors.Wrap(err, "failed to create cert signer")
}
return certSigner, nil
}
// forwardChannelRequest sends request req to SSH channel sshChan, waits for reply, and sends the reply back.
func (s *SSHProxy) forwardChannelRequest(sshChan gossh.Channel, req *gossh.Request) error {
reply, err := sshChan.SendRequest(req.Type, req.WantReply, req.Payload)
if err != nil {
return errors.Wrap(err, "Failed to send request")
}
if err := req.Reply(reply, nil); err != nil {
return errors.Wrap(err, "Failed to reply to request")
}
return nil
}
// logChannelRequest creates an audit log for different types of channel requests
func (s *SSHProxy) logChannelRequest(req *gossh.Request, conn *gossh.ServerConn, ctx ssh.Context) {
var eventType string
var event string
switch req.Type {
case "exec":
var payload struct{ Value string }
if err := gossh.Unmarshal(req.Payload, &payload); err != nil {
s.logger.WithError(err).Errorf("Failed to unmarshal channel request payload: %s:%s", req.Type, req.Payload)
}
event = payload.Value
eventType = auditEventExec
if strings.HasPrefix(string(req.Payload), "scp") {
eventType = auditEventScp
}
case "shell":
eventType = auditEventShell
case "window-change":
eventType = auditEventResize
default:
return
}
s.logAuditEvent(conn, event, eventType, ctx)
}
func (s *SSHProxy) auditLogger() (io.WriteCloser, string, error) {
sessionUUID, err := uuid.NewRandom()
if err != nil {
return nil, "", errors.Wrap(err, "failed to create sessionID")
}
sessionID := sessionUUID.String()
writer, err := s.logManager.NewLogger(fmt.Sprintf("%s-event.log", sessionID), s.logger)
if err != nil {
return nil, "", errors.Wrap(err, "failed to create logger")
}
return writer, sessionID, nil
}
func (s *SSHProxy) logAuditEvent(conn *gossh.ServerConn, event, eventType string, ctx ssh.Context) {
sessionID, sessionIDOk := ctx.Value(sshContextSessionID).(string)
writer, writerOk := ctx.Value(sshContextEventLogger).(io.WriteCloser)
if !writerOk || !sessionIDOk {
s.logger.Error("Failed to retrieve audit logger from context")
return
}
var destination string
preamble, ok := ctx.Value(sshContextPreamble).(*SSHPreamble)
if ok {
destination = preamble.Destination
} else {
s.logger.Error("Failed to retrieve SSH preamble from context")
}
ae := auditEvent{
Event: event,
EventType: eventType,
SessionID: sessionID,
User: conn.User(),
Login: conn.User(),
Datetime: time.Now().UTC().Format(time.RFC3339),
Hostname: s.hostname,
Destination: destination,
}
data, err := json.Marshal(&ae)
if err != nil {
s.logger.WithError(err).Error("Failed to marshal audit event. malformed audit object")
return
}
line := string(data) + "\n"
if _, err := writer.Write([]byte(line)); err != nil {
s.logger.WithError(err).Error("Failed to write audit event.")
}
}
// Sets PTY window size for terminal
func setWinsize(f *os.File, w, h int) syscall.Errno {
_, _, errNo := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ),
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(h), uint16(w), 0, 0})))
return errNo
}
func stringToUint32(str string) (uint32, error) {
uid, err := strconv.ParseUint(str, 10, 32)
return uint32(uid), err
}
+11 -2
View File
@@ -5,13 +5,22 @@ package sshserver
import (
"errors"
"github.com/sirupsen/logrus"
"time"
"github.com/cloudflare/cloudflared/sshlog"
"github.com/sirupsen/logrus"
)
const SSHPreambleLength = 2
type SSHServer struct{}
func New(_ *logrus.Logger, _ string, _ chan struct{}, _ bool, _, _ time.Duration) (*SSHServer, error) {
type SSHPreamble struct {
Destination string
JWT string
}
func New(_ sshlog.Manager, _ *logrus.Logger, _, _, _, _ string, _ chan struct{}, _, _ time.Duration) (*SSHServer, error) {
return nil, errors.New("cloudflared ssh server is not supported on windows")
}
+9 -6
View File
@@ -45,13 +45,16 @@ func H2RequestHeadersToH1Request(h2 []h2mux.Header, h1 *http.Request) error {
// Otherwise the host header will be based on the origin URL
h1.Host = header.Value
case ":path":
// Separate the path and the (optional) query from each other
chunks := strings.SplitN(header.Value, "?", 2)
h1.URL.Path = chunks[0]
if len(chunks) > 1 {
h1.URL.RawQuery = chunks[1]
u, err := url.Parse(header.Value)
if err != nil {
return fmt.Errorf("unparseable path")
}
resolved := h1.URL.ResolveReference(u)
// prevent escaping base URL
if !strings.HasPrefix(resolved.String(), h1.URL.String()) {
return fmt.Errorf("invalid path")
}
h1.URL = resolved
case "content-length":
contentLength, err := strconv.ParseInt(header.Value, 10, 64)
if err != nil {
-104
View File
@@ -1,104 +0,0 @@
package streamhandler
import (
"net/http"
"testing"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/stretchr/testify/assert"
)
func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
assert.NoError(t, err)
headersConversionErr := H2RequestHeadersToH1Request(
[]h2mux.Header{
h2mux.Header{
Name: "Mock header 1",
Value: "Mock value 1",
},
h2mux.Header{
Name: "Mock header 2",
Value: "Mock value 2",
},
},
request,
)
assert.Equal(t, http.Header{
"Mock header 1": []string{"Mock value 1"},
"Mock header 2": []string{"Mock value 2"},
}, request.Header)
assert.NoError(t, headersConversionErr)
}
func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
assert.NoError(t, err)
headersConversionErr := H2RequestHeadersToH1Request(
[]h2mux.Header{},
request,
)
assert.Equal(t, http.Header{}, request.Header)
assert.NoError(t, headersConversionErr)
}
func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
assert.NoError(t, err)
headersConversionErr := H2RequestHeadersToH1Request(
[]h2mux.Header{
h2mux.Header{
Name: ":path",
Value: "//bad_path/",
},
h2mux.Header{
Name: "Mock header",
Value: "Mock value",
},
},
request,
)
assert.Equal(t, http.Header{
"Mock header": []string{"Mock value"},
}, request.Header)
assert.Equal(t, "http://example.com//bad_path/", request.URL.String())
assert.NoError(t, headersConversionErr)
}
func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
assert.NoError(t, err)
headersConversionErr := H2RequestHeadersToH1Request(
[]h2mux.Header{
h2mux.Header{
Name: ":path",
Value: "/?query",
},
h2mux.Header{
Name: "Mock header",
Value: "Mock value",
},
},
request,
)
assert.Equal(t, http.Header{
"Mock header": []string{"Mock value"},
}, request.Header)
assert.Equal(t, "http://example.com/?query", request.URL.String())
assert.NoError(t, headersConversionErr)
}
+1 -1
View File
@@ -93,7 +93,7 @@ func (s *StreamHandler) UpdateConfig(newConfig []*pogs.ReverseProxyConfig) (fail
toAdd := s.tunnelHostnameMapper.ToAdd(newConfig)
for _, tunnelConfig := range toAdd {
tunnelHostname := tunnelConfig.TunnelHostname
originSerice, err := tunnelConfig.OriginConfigJSONHandler.OriginConfig.Service()
originSerice, err := tunnelConfig.OriginConfig.Service()
if err != nil {
s.logger.WithField("tunnelHostname", tunnelHostname).WithError(err).Error("Invalid origin service config")
failedConfigs = append(failedConfigs, &pogs.FailedConfig{
+4 -8
View File
@@ -49,10 +49,8 @@ func TestServeRequest(t *testing.T) {
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
{
TunnelHostname: testTunnelHostname,
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{
OriginConfig: &pogs.HTTPOriginConfig{
URLString: httpServer.URL,
},
OriginConfig: &pogs.HTTPOriginConfig{
URLString: httpServer.URL,
},
},
}
@@ -99,10 +97,8 @@ func TestServeBadRequest(t *testing.T) {
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
{
TunnelHostname: testTunnelHostname,
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{
OriginConfig: &pogs.HTTPOriginConfig{
URLString: "",
},
OriginConfig: &pogs.HTTPOriginConfig{
URLString: "",
},
},
}
+3 -6
View File
@@ -5,7 +5,6 @@ import (
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"runtime"
"sync"
@@ -18,6 +17,8 @@ import (
const (
OriginCAPoolFlag = "origin-ca-pool"
CaCertFlag = "cacert"
edgeTLSServerName = "cftunnel.com"
)
// CertReloader can load and reload a TLS certificate from a particular filepath.
@@ -126,7 +127,7 @@ func CreateTunnelConfig(c *cli.Context) (*tls.Config, error) {
rootCAs = append(rootCAs, c.String(CaCertFlag))
}
userConfig := &TLSParameters{RootCAs: rootCAs}
userConfig := &TLSParameters{RootCAs: rootCAs, ServerName: edgeTLSServerName}
tlsConfig, err := GetConfig(userConfig)
if err != nil {
return nil, err
@@ -142,10 +143,6 @@ func CreateTunnelConfig(c *cli.Context) (*tls.Config, error) {
rootCAPool.AddCert(cert)
}
tlsConfig.RootCAs = rootCAPool
tlsConfig.ServerName = "cftunnel.com"
} else if edgeAddrs := c.StringSlice("edge"); len(edgeAddrs) > 0 {
// Set for development environments and for testing specific origintunneld instances
tlsConfig.ServerName, _, _ = net.SplitHostPort(edgeAddrs[0])
}
if tlsConfig.ServerName == "" && !tlsConfig.InsecureSkipVerify {
-2
View File
@@ -1,5 +1,3 @@
// TODO: Remove the above build tag and include this test when we start compiling with Golang 1.10.0+
package tlsconfig
import (
@@ -193,20 +193,20 @@ func TestTunnelHostnameMapper_ToRemove(t *testing.T) {
func sampleConfig1() *pogs.ReverseProxyConfig {
return &pogs.ReverseProxyConfig{
TunnelHostname: "mock.example.com",
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{OriginConfig: &pogs.HelloWorldOriginConfig{}},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
TunnelHostname: "mock.example.com",
OriginConfig: &pogs.HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
}
func sampleConfig2() *pogs.ReverseProxyConfig {
return &pogs.ReverseProxyConfig{
TunnelHostname: "mock2.example.com",
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{OriginConfig: &pogs.HelloWorldOriginConfig{}},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
TunnelHostname: "mock2.example.com",
OriginConfig: &pogs.HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
}
+103 -130
View File
@@ -4,7 +4,6 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net"
"net/http"
@@ -29,11 +28,43 @@ import (
// ClientConfig is a collection of FallibleConfig that determines how cloudflared should function
type ClientConfig struct {
Version Version `json:"version"`
SupervisorConfig *SupervisorConfig `json:"supervisor_config"`
EdgeConnectionConfig *EdgeConnectionConfig `json:"edge_connection_config"`
DoHProxyConfigs []*DoHProxyConfig `json:"doh_proxy_configs" capnp:"dohProxyConfigs"`
ReverseProxyConfigs []*ReverseProxyConfig `json:"reverse_proxy_configs"`
Version Version
SupervisorConfig *SupervisorConfig
EdgeConnectionConfig *EdgeConnectionConfig
DoHProxyConfigs []*DoHProxyConfig `capnp:"dohProxyConfigs"`
ReverseProxyConfigs []*ReverseProxyConfig
}
func (c *ClientConfig) MarshalBytes() ([]byte, error) {
msg, firstSeg, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return nil, err
}
capnpEntity, err := tunnelrpc.NewRootClientConfig(firstSeg)
if err != nil {
return nil, err
}
err = MarshalClientConfig(capnpEntity, c)
if err != nil {
return nil, err
}
return msg.Marshal()
}
func UnmarshalClientConfigFromBytes(clientConfigBytes []byte) (*ClientConfig, error) {
msg, err := capnp.Unmarshal(clientConfigBytes)
if err != nil {
return nil, err
}
capnpClientConfig, err := tunnelrpc.ReadRootClientConfig(msg)
if err != nil {
return nil, err
}
pogsClientConfig, err := UnmarshalClientConfig(capnpClientConfig)
if err != nil {
return nil, err
}
return pogsClientConfig, nil
}
// Version type models the version of a ClientConfig
@@ -52,16 +83,17 @@ func (v Version) String() string {
}
// FallibleConfig is an interface implemented by configs that cloudflared might not be able to apply
//go-sumtype:decl FallibleConfig
type FallibleConfig interface {
FailReason(err error) string
jsonType() string
isFallibleConfig()
}
// SupervisorConfig specifies config of components managed by Supervisor other than ConnectionManager
type SupervisorConfig struct {
AutoUpdateFrequency time.Duration `json:"auto_update_frequency"`
MetricsUpdateFrequency time.Duration `json:"metrics_update_frequency"`
GracePeriod time.Duration `json:"grace_period"`
AutoUpdateFrequency time.Duration
MetricsUpdateFrequency time.Duration
GracePeriod time.Duration
}
// FailReason impelents FallibleConfig interface for SupervisorConfig
@@ -69,23 +101,15 @@ func (sc *SupervisorConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply SupervisorConfig, err: %v", err)
}
func (sc *SupervisorConfig) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]SupervisorConfig, 1)
marshaler[sc.jsonType()] = *sc
return json.Marshal(marshaler)
}
func (sc *SupervisorConfig) jsonType() string {
return "supervisor_config"
}
func (_ *SupervisorConfig) isFallibleConfig() {}
// EdgeConnectionConfig specifies what parameters and how may connections should ConnectionManager establish with edge
type EdgeConnectionConfig struct {
NumHAConnections uint8 `json:"num_ha_connections"`
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
Timeout time.Duration `json:"timeout"`
MaxFailedHeartbeats uint64 `json:"max_failed_heartbeats"`
UserCredentialPath string `json:"user_credential_path"`
NumHAConnections uint8
HeartbeatInterval time.Duration
Timeout time.Duration
MaxFailedHeartbeats uint64
UserCredentialPath string
}
// FailReason impelents FallibleConfig interface for EdgeConnectionConfig
@@ -93,21 +117,13 @@ func (cmc *EdgeConnectionConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply EdgeConnectionConfig, err: %v", err)
}
func (cmc *EdgeConnectionConfig) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]EdgeConnectionConfig, 1)
marshaler[cmc.jsonType()] = *cmc
return json.Marshal(marshaler)
}
func (cmc *EdgeConnectionConfig) jsonType() string {
return "edge_connection_config"
}
func (_ *EdgeConnectionConfig) isFallibleConfig() {}
// DoHProxyConfig is configuration for DNS over HTTPS service
type DoHProxyConfig struct {
ListenHost string `json:"listen_host"`
ListenPort uint16 `json:"listen_port"`
Upstreams []string `json:"upstreams"`
ListenHost string
ListenPort uint16
Upstreams []string
}
// FailReason impelents FallibleConfig interface for DoHProxyConfig
@@ -115,23 +131,15 @@ func (dpc *DoHProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply DoHProxyConfig, err: %v", err)
}
func (dpc *DoHProxyConfig) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]DoHProxyConfig, 1)
marshaler[dpc.jsonType()] = *dpc
return json.Marshal(marshaler)
}
func (dpc *DoHProxyConfig) jsonType() string {
return "doh_proxy_config"
}
func (_ *DoHProxyConfig) isFallibleConfig() {}
// ReverseProxyConfig how and for what hostnames can this cloudflared proxy
type ReverseProxyConfig struct {
TunnelHostname h2mux.TunnelHostname `json:"tunnel_hostname"`
OriginConfigJSONHandler *OriginConfigJSONHandler `json:"origin_config"`
Retries uint64 `json:"retries"`
ConnectionTimeout time.Duration `json:"connection_timeout"`
CompressionQuality uint64 `json:"compression_quality"`
TunnelHostname h2mux.TunnelHostname
OriginConfig OriginConfig
Retries uint64
ConnectionTimeout time.Duration
CompressionQuality uint64
}
func NewReverseProxyConfig(
@@ -145,11 +153,11 @@ func NewReverseProxyConfig(
return nil, fmt.Errorf("NewReverseProxyConfig: originConfigUnmarshaler was null")
}
return &ReverseProxyConfig{
TunnelHostname: h2mux.TunnelHostname(tunnelHostname),
OriginConfigJSONHandler: &OriginConfigJSONHandler{originConfig},
Retries: retries,
ConnectionTimeout: connectionTimeout,
CompressionQuality: compressionQuality,
TunnelHostname: h2mux.TunnelHostname(tunnelHostname),
OriginConfig: originConfig,
Retries: retries,
ConnectionTimeout: connectionTimeout,
CompressionQuality: compressionQuality,
}, nil
}
@@ -158,58 +166,29 @@ func (rpc *ReverseProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply ReverseProxyConfig, err: %v", err)
}
func (rpc *ReverseProxyConfig) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]ReverseProxyConfig, 1)
marshaler[rpc.jsonType()] = *rpc
return json.Marshal(marshaler)
}
func (rpc *ReverseProxyConfig) jsonType() string {
return "reverse_proxy_config"
}
func (_ *ReverseProxyConfig) isFallibleConfig() {}
//go-sumtype:decl OriginConfig
type OriginConfig interface {
// Service returns a OriginService used to proxy to the origin
Service() (originservice.OriginService, error)
// go-sumtype requires at least one unexported method, otherwise it will complain that interface is not sealed
jsonType() string
}
type originType int
const (
httpType originType = iota
wsType
helloWorldType
)
func (ot originType) String() string {
switch ot {
case httpType:
return "Http"
case wsType:
return "WebSocket"
case helloWorldType:
return "HelloWorld"
default:
return "unknown"
}
isOriginConfig()
}
type HTTPOriginConfig struct {
URLString string `capnp:"urlString" json:"url_string" mapstructure:"url_string"`
TCPKeepAlive time.Duration `capnp:"tcpKeepAlive" json:"tcp_keep_alive" mapstructure:"tcp_keep_alive"`
DialDualStack bool `json:"dial_dual_stack" mapstructure:"dial_dual_stack"`
TLSHandshakeTimeout time.Duration `capnp:"tlsHandshakeTimeout" json:"tls_handshake_timeout" mapstructure:"tls_handshake_timeout"`
TLSVerify bool `capnp:"tlsVerify" json:"tls_verify" mapstructure:"tls_verify"`
OriginCAPool string `json:"origin_ca_pool" mapstructure:"origin_ca_pool"`
OriginServerName string `json:"origin_server_name" mapstructure:"origin_server_name"`
MaxIdleConnections uint64 `json:"max_idle_connections" mapstructure:"max_idle_connections"`
IdleConnectionTimeout time.Duration `json:"idle_connection_timeout" mapstructure:"idle_connection_timeout"`
ProxyConnectionTimeout time.Duration `json:"proxy_connection_timeout" mapstructure:"proxy_connection_timeout"`
ExpectContinueTimeout time.Duration `json:"expect_continue_timeout" mapstructure:"expect_continue_timeout"`
ChunkedEncoding bool `json:"chunked_encoding" mapstructure:"chunked_encoding"`
URLString string `capnp:"urlString"`
TCPKeepAlive time.Duration `capnp:"tcpKeepAlive"`
DialDualStack bool
TLSHandshakeTimeout time.Duration `capnp:"tlsHandshakeTimeout"`
TLSVerify bool `capnp:"tlsVerify"`
OriginCAPool string
OriginServerName string
MaxIdleConnections uint64
IdleConnectionTimeout time.Duration
ProxyConnectionTimeout time.Duration
ExpectContinueTimeout time.Duration
ChunkedEncoding bool
}
func (hc *HTTPOriginConfig) Service() (originservice.OriginService, error) {
@@ -248,15 +227,13 @@ func (hc *HTTPOriginConfig) Service() (originservice.OriginService, error) {
return originservice.NewHTTPService(transport, url, hc.ChunkedEncoding), nil
}
func (_ *HTTPOriginConfig) jsonType() string {
return httpType.String()
}
func (*HTTPOriginConfig) isOriginConfig() {}
type WebSocketOriginConfig struct {
URLString string `capnp:"urlString" json:"url_string" mapstructure:"url_string"`
TLSVerify bool `capnp:"tlsVerify" json:"tls_verify" mapstructure:"tls_verify"`
OriginCAPool string `json:"origin_ca_pool" mapstructure:"origin_ca_pool"`
OriginServerName string `json:"origin_server_name" mapstructure:"origin_server_name"`
URLString string `capnp:"urlString"`
TLSVerify bool `capnp:"tlsVerify"`
OriginCAPool string
OriginServerName string
}
func (wsc *WebSocketOriginConfig) Service() (originservice.OriginService, error) {
@@ -277,13 +254,11 @@ func (wsc *WebSocketOriginConfig) Service() (originservice.OriginService, error)
return originservice.NewWebSocketService(tlsConfig, url)
}
func (_ *WebSocketOriginConfig) jsonType() string {
return wsType.String()
}
func (*WebSocketOriginConfig) isOriginConfig() {}
type HelloWorldOriginConfig struct{}
func (_ *HelloWorldOriginConfig) Service() (originservice.OriginService, error) {
func (*HelloWorldOriginConfig) Service() (originservice.OriginService, error) {
helloCert, err := tlsconfig.GetHelloCertificateX509()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Hello World server certificate")
@@ -308,9 +283,7 @@ func (_ *HelloWorldOriginConfig) Service() (originservice.OriginService, error)
return originservice.NewHelloWorldService(transport)
}
func (_ *HelloWorldOriginConfig) jsonType() string {
return helloWorldType.String()
}
func (*HelloWorldOriginConfig) isOriginConfig() {}
/*
* Boilerplate to convert between these structs and the primitive structs
@@ -519,9 +492,9 @@ func UnmarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig) (*DoHProxyConfig, error
func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyConfig) error {
s.SetTunnelHostname(p.TunnelHostname.String())
switch config := p.OriginConfigJSONHandler.OriginConfig.(type) {
switch config := p.OriginConfig.(type) {
case *HTTPOriginConfig:
ss, err := s.Origin().NewHttp()
ss, err := s.OriginConfig().NewHttp()
if err != nil {
return err
}
@@ -529,7 +502,7 @@ func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyCo
return err
}
case *WebSocketOriginConfig:
ss, err := s.Origin().NewWebsocket()
ss, err := s.OriginConfig().NewWebsocket()
if err != nil {
return err
}
@@ -537,7 +510,7 @@ func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyCo
return err
}
case *HelloWorldOriginConfig:
ss, err := s.Origin().NewHelloWorld()
ss, err := s.OriginConfig().NewHelloWorld()
if err != nil {
return err
}
@@ -560,9 +533,9 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
return nil, err
}
p.TunnelHostname = h2mux.TunnelHostname(tunnelHostname)
switch s.Origin().Which() {
case tunnelrpc.ReverseProxyConfig_origin_Which_http:
ss, err := s.Origin().Http()
switch s.OriginConfig().Which() {
case tunnelrpc.ReverseProxyConfig_originConfig_Which_http:
ss, err := s.OriginConfig().Http()
if err != nil {
return nil, err
}
@@ -570,9 +543,9 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
if err != nil {
return nil, err
}
p.OriginConfigJSONHandler = &OriginConfigJSONHandler{config}
case tunnelrpc.ReverseProxyConfig_origin_Which_websocket:
ss, err := s.Origin().Websocket()
p.OriginConfig = config
case tunnelrpc.ReverseProxyConfig_originConfig_Which_websocket:
ss, err := s.OriginConfig().Websocket()
if err != nil {
return nil, err
}
@@ -580,9 +553,9 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
if err != nil {
return nil, err
}
p.OriginConfigJSONHandler = &OriginConfigJSONHandler{config}
case tunnelrpc.ReverseProxyConfig_origin_Which_helloWorld:
ss, err := s.Origin().HelloWorld()
p.OriginConfig = config
case tunnelrpc.ReverseProxyConfig_originConfig_Which_helloWorld:
ss, err := s.OriginConfig().HelloWorld()
if err != nil {
return nil, err
}
@@ -590,7 +563,7 @@ func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyC
if err != nil {
return nil, err
}
p.OriginConfigJSONHandler = &OriginConfigJSONHandler{config}
p.OriginConfig = config
}
p.Retries = s.Retries()
p.ConnectionTimeout = time.Duration(s.ConnectionTimeout())
@@ -690,13 +663,13 @@ func (i ClientService_PogsImpl) UseConfiguration(p tunnelrpc.ClientService_useCo
}
type UseConfigurationResult struct {
Success bool `json:"success"`
FailedConfigs []*FailedConfig `json:"failed_configs"`
Success bool
FailedConfigs []*FailedConfig
}
type FailedConfig struct {
Config FallibleConfig `json:"config"`
Reason string `json:"reason"`
Config FallibleConfig
Reason string
}
func MarshalFailedConfig(s tunnelrpc.FailedConfig, p *FailedConfig) error {
+19 -26
View File
@@ -61,13 +61,13 @@ func ClientConfigTestCases() []*ClientConfig {
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleHTTPOriginConfig()}
c.OriginConfig = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleHTTPOriginConfigUnixPath()}
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleWebSocketOriginConfig()}
c.OriginConfig = sampleWebSocketOriginConfig()
}),
}
}
@@ -83,21 +83,14 @@ func ClientConfigTestCases() []*ClientConfig {
}
func TestClientConfig(t *testing.T) {
for i, testCase := range ClientConfigTestCases() {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewClientConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalClientConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalClientConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
for _, testCase := range ClientConfigTestCases() {
b, err := testCase.MarshalBytes()
assert.NoError(t, err)
clientConfig, err := UnmarshalClientConfigFromBytes(b)
assert.NoError(t, err)
assert.Equal(t, testCase, clientConfig)
}
}
@@ -167,13 +160,13 @@ func TestReverseProxyConfig(t *testing.T) {
testCases := []*ReverseProxyConfig{
sampleReverseProxyConfig(),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleHTTPOriginConfig()}
c.OriginConfig = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleHTTPOriginConfigUnixPath()}
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleWebSocketOriginConfig()}
c.OriginConfig = sampleWebSocketOriginConfig()
}),
}
for i, testCase := range testCases {
@@ -323,11 +316,11 @@ func sampleDoHProxyConfig(overrides ...func(*DoHProxyConfig)) *DoHProxyConfig {
// applies any number of overrides to it, and returns it.
func sampleReverseProxyConfig(overrides ...func(*ReverseProxyConfig)) *ReverseProxyConfig {
sample := &ReverseProxyConfig{
TunnelHostname: "mock-non-lb-tunnel.example.com",
OriginConfigJSONHandler: &OriginConfigJSONHandler{&HelloWorldOriginConfig{}},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
TunnelHostname: "mock-non-lb-tunnel.example.com",
OriginConfig: &HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
-101
View File
@@ -1,101 +0,0 @@
package pogs
import (
"encoding/json"
"fmt"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)
// ScopeUnmarshaler can marshal a Scope pog from JSON.
type ScopeUnmarshaler struct {
Scope Scope
}
// UnmarshalJSON takes in a JSON string, and attempts to marshal it into a Scope.
// If successful, the Scope member of this ScopeUnmarshaler is set and nil is returned.
// If unsuccessful, returns an error.
func (su *ScopeUnmarshaler) UnmarshalJSON(b []byte) error {
var scopeJSON map[string]interface{}
if err := json.Unmarshal(b, &scopeJSON); err != nil {
return errors.Wrapf(err, "cannot unmarshal %s into scopeJSON", string(b))
}
if group, ok := scopeJSON["group"]; ok {
if val, ok := group.(string); ok {
su.Scope = NewGroup(val)
return nil
}
return fmt.Errorf("JSON should have been a Scope, but the 'group' key contained %v", group)
}
if systemName, ok := scopeJSON["system_name"]; ok {
if val, ok := systemName.(string); ok {
su.Scope = NewSystemName(val)
return nil
}
return fmt.Errorf("JSON should have been a Scope, but the 'system_name' key contained %v", systemName)
}
return fmt.Errorf("JSON should have been an object with one root key, either 'system_name' or 'group'")
}
// OriginConfigJSONHandler is a wrapper to serialize OriginConfig with type information, and deserialize JSON
// into an OriginConfig.
type OriginConfigJSONHandler struct {
OriginConfig OriginConfig
}
func (ocjh *OriginConfigJSONHandler) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]OriginConfig, 1)
marshaler[ocjh.OriginConfig.jsonType()] = ocjh.OriginConfig
return json.Marshal(marshaler)
}
func (ocjh *OriginConfigJSONHandler) UnmarshalJSON(b []byte) error {
var originJSON map[string]interface{}
if err := json.Unmarshal(b, &originJSON); err != nil {
return errors.Wrapf(err, "cannot unmarshal %s into originJSON", string(b))
}
if originConfig, ok := originJSON[httpType.String()]; ok {
httpOriginConfig := &HTTPOriginConfig{}
if err := mapstructure.Decode(originConfig, httpOriginConfig); err != nil {
return errors.Wrapf(err, "cannot decode %+v into HTTPOriginConfig", originConfig)
}
ocjh.OriginConfig = httpOriginConfig
return nil
}
if originConfig, ok := originJSON[wsType.String()]; ok {
wsOriginConfig := &WebSocketOriginConfig{}
if err := mapstructure.Decode(originConfig, wsOriginConfig); err != nil {
return errors.Wrapf(err, "cannot decode %+v into WebSocketOriginConfig", originConfig)
}
ocjh.OriginConfig = wsOriginConfig
return nil
}
if originConfig, ok := originJSON[helloWorldType.String()]; ok {
helloWorldOriginConfig := &HelloWorldOriginConfig{}
if err := mapstructure.Decode(originConfig, helloWorldOriginConfig); err != nil {
return errors.Wrapf(err, "cannot decode %+v into HelloWorldOriginConfig", originConfig)
}
ocjh.OriginConfig = helloWorldOriginConfig
return nil
}
return fmt.Errorf("cannot unmarshal %s into OriginConfig", string(b))
}
// FallibleConfigMarshaler is a wrapper for FallibleConfig to implement custom marshal logic
type FallibleConfigMarshaler struct {
FallibleConfig FallibleConfig
}
func (fcm *FallibleConfigMarshaler) MarshalJSON() ([]byte, error) {
marshaler := make(map[string]FallibleConfig, 1)
marshaler[fcm.FallibleConfig.jsonType()] = fcm.FallibleConfig
return json.Marshal(marshaler)
}
-342
View File
@@ -1,342 +0,0 @@
package pogs
import (
"encoding/json"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestScopeUnmarshaler_UnmarshalJSON(t *testing.T) {
type fields struct {
Scope Scope
}
type args struct {
b []byte
}
tests := []struct {
name string
fields fields
args args
wantErr bool
wantScope Scope
}{
{
name: "group_successful",
args: args{b: []byte(`{"group": "my-group"}`)},
wantScope: NewGroup("my-group"),
},
{
name: "system_name_successful",
args: args{b: []byte(`{"system_name": "my-computer"}`)},
wantScope: NewSystemName("my-computer"),
},
{
name: "not_a_scope",
args: args{b: []byte(`{"x": "y"}`)},
wantErr: true,
},
{
name: "malformed_group",
args: args{b: []byte(`{"group": ["a", "b"]}`)},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
su := &ScopeUnmarshaler{
Scope: tt.fields.Scope,
}
err := su.UnmarshalJSON(tt.args.b)
if !tt.wantErr {
if err != nil {
t.Errorf("ScopeUnmarshaler.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
if !eqScope(tt.wantScope, su.Scope) {
t.Errorf("Wanted scope %v but got scope %v", tt.wantScope, su.Scope)
}
}
})
}
}
func TestUnmarshalOrigin(t *testing.T) {
tests := []struct {
jsonLiteral string
exceptedOriginConfig OriginConfig
}{
{
jsonLiteral: `{
"Http":{
"url_string":"https.example.com",
"tcp_keep_alive":7000000000,
"dial_dual_stack":true,
"tls_handshake_timeout":11000000000,
"tls_verify":true,
"origin_ca_pool":"/etc/cert.pem",
"origin_server_name":"secure.example.com",
"max_idle_connections":19,
"idle_connection_timeout":17000000000,
"proxy_connection_timeout":15000000000,
"expect_continue_timeout":21000000000,
"chunked_encoding":true
}
}`,
exceptedOriginConfig: sampleHTTPOriginConfig(),
},
{
jsonLiteral: `{
"WebSocket":{
"url_string":"ssh://example.com",
"tls_verify":true,
"origin_ca_pool":"/etc/cert.pem",
"origin_server_name":"secure.example.com"
}
}`,
exceptedOriginConfig: sampleWebSocketOriginConfig(),
},
{
jsonLiteral: `{
"HelloWorld": {}
}`,
exceptedOriginConfig: &HelloWorldOriginConfig{},
},
}
for _, test := range tests {
originConfigJSON := prettyToValidJSON(test.jsonLiteral)
var OriginConfigJSONHandler OriginConfigJSONHandler
err := json.Unmarshal([]byte(originConfigJSON), &OriginConfigJSONHandler)
assert.NoError(t, err)
assert.Equal(t, test.exceptedOriginConfig, OriginConfigJSONHandler.OriginConfig)
}
}
func TestUnmarshalClientConfig(t *testing.T) {
prettyClientConfigJSON := `{
"version":10,
"supervisor_config":{
"auto_update_frequency":86400000000000,
"metrics_update_frequency":300000000000,
"grace_period":30000000000
},
"edge_connection_config":{
"num_ha_connections":4,
"heartbeat_interval":5000000000,
"timeout":30000000000,
"max_failed_heartbeats":5,
"user_credential_path":"~/.cloudflared/cert.pem"
},
"doh_proxy_configs":[{
"listen_host": "localhost",
"listen_port": 53,
"upstreams": ["https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"]
}],
"reverse_proxy_configs":[{
"tunnel_hostname":"sdfjadk33.cftunnel.com",
"origin_config":{
"Http":{
"url_string":"https://127.0.0.1:8080",
"tcp_keep_alive":30000000000,
"dial_dual_stack":true,
"tls_handshake_timeout":10000000000,
"tls_verify":true,
"origin_ca_pool":"",
"origin_server_name":"",
"max_idle_connections":100,
"idle_connection_timeout":90000000000,
"proxy_connection_timeout":90000000000,
"expect_continue_timeout":90000000000,
"chunked_encoding":true
}
},
"retries":5,
"connection_timeout":30,
"compression_quality":0
}]
}`
// replace new line and tab
clientConfigJSON := prettyToValidJSON(prettyClientConfigJSON)
var clientConfig ClientConfig
err := json.Unmarshal([]byte(clientConfigJSON), &clientConfig)
assert.NoError(t, err)
assert.Equal(t, Version(10), clientConfig.Version)
supervisorConfig := SupervisorConfig{
AutoUpdateFrequency: time.Hour * 24,
MetricsUpdateFrequency: time.Second * 300,
GracePeriod: time.Second * 30,
}
assert.Equal(t, supervisorConfig, *clientConfig.SupervisorConfig)
edgeConnectionConfig := EdgeConnectionConfig{
NumHAConnections: 4,
HeartbeatInterval: time.Second * 5,
Timeout: time.Second * 30,
MaxFailedHeartbeats: 5,
UserCredentialPath: "~/.cloudflared/cert.pem",
}
assert.Equal(t, edgeConnectionConfig, *clientConfig.EdgeConnectionConfig)
dohProxyConfig := DoHProxyConfig{
ListenHost: "localhost",
ListenPort: 53,
Upstreams: []string{"https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"},
}
assert.Len(t, clientConfig.DoHProxyConfigs, 1)
assert.Equal(t, dohProxyConfig, *clientConfig.DoHProxyConfigs[0])
reverseProxyConfig := ReverseProxyConfig{
TunnelHostname: "sdfjadk33.cftunnel.com",
OriginConfigJSONHandler: &OriginConfigJSONHandler{
OriginConfig: &HTTPOriginConfig{
URLString: "https://127.0.0.1:8080",
TCPKeepAlive: time.Second * 30,
DialDualStack: true,
TLSHandshakeTimeout: time.Second * 10,
TLSVerify: true,
OriginCAPool: "",
OriginServerName: "",
MaxIdleConnections: 100,
IdleConnectionTimeout: time.Second * 90,
ProxyConnectionTimeout: time.Second * 90,
ExpectContinueTimeout: time.Second * 90,
ChunkedEncoding: true,
},
},
Retries: 5,
ConnectionTimeout: 30,
CompressionQuality: 0,
}
assert.Len(t, clientConfig.ReverseProxyConfigs, 1)
assert.Equal(t, reverseProxyConfig, *clientConfig.ReverseProxyConfigs[0])
}
func TestMarshalFallibleConfig(t *testing.T) {
tests := []struct {
fallibleConfig FallibleConfig
expctedJSONLiteral string
}{
{
fallibleConfig: sampleSupervisorConfig(),
expctedJSONLiteral: `{
"supervisor_config":{
"auto_update_frequency":75600000000000,
"metrics_update_frequency":660000000000,
"grace_period":31000000000
}
}`,
},
{
fallibleConfig: sampleEdgeConnectionConfig(),
expctedJSONLiteral: `{
"edge_connection_config":{
"num_ha_connections":49,
"heartbeat_interval":5000000000,
"timeout":9000000000,
"max_failed_heartbeats":9001,
"user_credential_path":"/Users/example/.cloudflared/cert.pem"
}
}`,
},
{
fallibleConfig: sampleDoHProxyConfig(),
expctedJSONLiteral: `{
"doh_proxy_config":{
"listen_host":"127.0.0.1",
"listen_port":53,
"upstreams":["1.1.1.1","1.0.0.1"]
}
}`,
},
{
fallibleConfig: sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleHTTPOriginConfig()}
}),
expctedJSONLiteral: `{
"reverse_proxy_config":{
"tunnel_hostname":"mock-non-lb-tunnel.example.com",
"origin_config":{
"Http":{
"url_string":"https.example.com",
"tcp_keep_alive":7000000000,
"dial_dual_stack":true,
"tls_handshake_timeout":11000000000,
"tls_verify":true,
"origin_ca_pool":"/etc/cert.pem",
"origin_server_name":"secure.example.com",
"max_idle_connections":19,
"idle_connection_timeout":17000000000,
"proxy_connection_timeout":15000000000,
"expect_continue_timeout":21000000000,
"chunked_encoding":true
}
},
"retries":18,
"connection_timeout":5000000000,
"compression_quality":3
}
}`,
},
{
fallibleConfig: sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{sampleWebSocketOriginConfig()}
}),
expctedJSONLiteral: `{
"reverse_proxy_config":{
"tunnel_hostname":"mock-non-lb-tunnel.example.com",
"origin_config":{
"WebSocket":{
"url_string":"ssh://example.com",
"tls_verify":true,
"origin_ca_pool":"/etc/cert.pem",
"origin_server_name":"secure.example.com"
}
},
"retries":18,
"connection_timeout":5000000000,
"compression_quality":3
}
}`,
},
{
fallibleConfig: sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfigJSONHandler = &OriginConfigJSONHandler{&HelloWorldOriginConfig{}}
}),
expctedJSONLiteral: `{
"reverse_proxy_config":{
"tunnel_hostname":"mock-non-lb-tunnel.example.com",
"origin_config":{
"HelloWorld":{}
},
"retries":18,
"connection_timeout":5000000000,
"compression_quality":3
}
}`,
},
}
for _, test := range tests {
b, err := json.Marshal(test.fallibleConfig)
assert.NoError(t, err)
assert.Equal(t, prettyToValidJSON(test.expctedJSONLiteral), string(b))
}
}
type prettyJSON string
func prettyToValidJSON(prettyJSON string) string {
return strings.ReplaceAll(strings.ReplaceAll(prettyJSON, "\n", ""), "\t", "")
}
func eqScope(s1, s2 Scope) bool {
return s1.Value() == s2.Value() && s1.PostgresType() == s2.PostgresType()
}
+121 -100
View File
@@ -7,6 +7,7 @@ import (
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/google/uuid"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
capnp "zombiezen.com/go/capnproto2"
@@ -74,28 +75,129 @@ func UnmarshalRegistrationOptions(s tunnelrpc.RegistrationOptions) (*Registratio
return p, err
}
type ConnectResult struct {
Err *ConnectError
ServerInfo ServerInfo
ClientConfig ClientConfig
// ConnectResult models the result of Connect RPC, implemented by ConnectError and ConnectSuccess.
type ConnectResult interface {
ConnectError() *ConnectError
ConnectedTo() string
ClientConfig() *ClientConfig
Marshal(s tunnelrpc.ConnectResult) error
}
func MarshalConnectResult(s tunnelrpc.ConnectResult, p *ConnectResult) error {
return pogs.Insert(tunnelrpc.ConnectResult_TypeID, s.Struct, p)
func MarshalConnectResult(s tunnelrpc.ConnectResult, p ConnectResult) error {
return p.Marshal(s)
}
func UnmarshalConnectResult(s tunnelrpc.ConnectResult) (*ConnectResult, error) {
p := new(ConnectResult)
err := pogs.Extract(p, tunnelrpc.ConnectResult_TypeID, s.Struct)
return p, err
func UnmarshalConnectResult(s tunnelrpc.ConnectResult) (ConnectResult, error) {
switch s.Result().Which() {
case tunnelrpc.ConnectResult_result_Which_err:
capnpConnectError, err := s.Result().Err()
if err != nil {
return nil, err
}
return UnmarshalConnectError(capnpConnectError)
case tunnelrpc.ConnectResult_result_Which_success:
capnpConnectSuccess, err := s.Result().Success()
if err != nil {
return nil, err
}
return UnmarshalConnectSuccess(capnpConnectSuccess)
default:
return nil, fmt.Errorf("Unmarshal %v not implemented yet", s.Result().Which().String())
}
}
// ConnectSuccess is the concrete returned type when Connect RPC succeed
type ConnectSuccess struct {
ServerLocationName string
Config *ClientConfig
}
func (*ConnectSuccess) ConnectError() *ConnectError {
return nil
}
func (cs *ConnectSuccess) ConnectedTo() string {
return cs.ServerLocationName
}
func (cs *ConnectSuccess) ClientConfig() *ClientConfig {
return cs.Config
}
func (cs *ConnectSuccess) Marshal(s tunnelrpc.ConnectResult) error {
capnpConnectSuccess, err := s.Result().NewSuccess()
if err != nil {
return err
}
err = capnpConnectSuccess.SetServerLocationName(cs.ServerLocationName)
if err != nil {
return errors.Wrap(err, "failed to set ConnectSuccess.ServerLocationName")
}
if cs.Config != nil {
capnpClientConfig, err := capnpConnectSuccess.NewClientConfig()
if err != nil {
return errors.Wrap(err, "failed to initialize ConnectSuccess.ClientConfig")
}
if err := MarshalClientConfig(capnpClientConfig, cs.Config); err != nil {
return errors.Wrap(err, "failed to marshal ClientConfig")
}
}
return nil
}
func UnmarshalConnectSuccess(s tunnelrpc.ConnectSuccess) (*ConnectSuccess, error) {
p := new(ConnectSuccess)
serverLocationName, err := s.ServerLocationName()
if err != nil {
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ServerLocationName")
}
p.ServerLocationName = serverLocationName
if s.HasClientConfig() {
capnpClientConfig, err := s.ClientConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ClientConfig")
}
p.Config, err = UnmarshalClientConfig(capnpClientConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to get unmarshal ClientConfig")
}
}
return p, nil
}
// ConnectError is the concrete returned type when Connect RPC encounters some error
type ConnectError struct {
Cause string
RetryAfter time.Duration
ShouldRetry bool
}
func (ce *ConnectError) ConnectError() *ConnectError {
return ce
}
func (*ConnectError) ConnectedTo() string {
return ""
}
func (*ConnectError) ClientConfig() *ClientConfig {
return nil
}
func (ce *ConnectError) Marshal(s tunnelrpc.ConnectResult) error {
capnpConnectError, err := s.Result().NewErr()
if err != nil {
return err
}
return MarshalConnectError(capnpConnectError, ce)
}
func MarshalConnectError(s tunnelrpc.ConnectError, p *ConnectError) error {
return pogs.Insert(tunnelrpc.ConnectError_TypeID, s.Struct, p)
}
@@ -129,82 +231,13 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
return p, err
}
//go-sumtype:decl Scope
type Scope interface {
Value() string
PostgresType() string
GraphQLType() string
isScope()
}
type SystemName struct {
systemName string
}
func NewSystemName(systemName string) *SystemName {
return &SystemName{systemName: systemName}
}
func (s *SystemName) Value() string { return s.systemName }
func (_ *SystemName) PostgresType() string { return "system_name" }
func (_ *SystemName) GraphQLType() string { return "SYSTEM_NAME" }
func (_ *SystemName) isScope() {}
type Group struct {
group string
}
func NewGroup(group string) *Group {
return &Group{group: group}
}
func (g *Group) Value() string { return g.group }
func (_ *Group) PostgresType() string { return "group" }
func (_ *Group) GraphQLType() string { return "GROUP" }
func (_ *Group) isScope() {}
func MarshalScope(s tunnelrpc.Scope, p Scope) error {
ss := s.Value()
switch scope := p.(type) {
case *SystemName:
ss.SetSystemName(scope.systemName)
case *Group:
ss.SetGroup(scope.group)
default:
return fmt.Errorf("unexpected Scope value: %v", p)
}
return nil
}
func UnmarshalScope(s tunnelrpc.Scope) (Scope, error) {
ss := s.Value()
switch ss.Which() {
case tunnelrpc.Scope_value_Which_systemName:
systemName, err := ss.SystemName()
if err != nil {
return nil, err
}
return NewSystemName(systemName), nil
case tunnelrpc.Scope_value_Which_group:
group, err := ss.Group()
if err != nil {
return nil, err
}
return NewGroup(group), nil
default:
return nil, fmt.Errorf("unexpected Scope tag: %v", ss.Which())
}
}
type ConnectParameters struct {
OriginCert []byte
CloudflaredID uuid.UUID
NumPreviousAttempts uint8
Tags []Tag
CloudflaredVersion string
Scope Scope
IntentLabel string
}
func MarshalConnectParameters(s tunnelrpc.CapnpConnectParameters, p *ConnectParameters) error {
@@ -237,11 +270,7 @@ func MarshalConnectParameters(s tunnelrpc.CapnpConnectParameters, p *ConnectPara
if err := s.SetCloudflaredVersion(p.CloudflaredVersion); err != nil {
return err
}
scope, err := s.NewScope()
if err != nil {
return err
}
return MarshalScope(scope, p.Scope)
return s.SetIntentLabel(p.IntentLabel)
}
func UnmarshalConnectParameters(s tunnelrpc.CapnpConnectParameters) (*ConnectParameters, error) {
@@ -282,22 +311,14 @@ func UnmarshalConnectParameters(s tunnelrpc.CapnpConnectParameters) (*ConnectPar
return nil, err
}
scopeCapnp, err := s.Scope()
if err != nil {
return nil, err
}
scope, err := UnmarshalScope(scopeCapnp)
if err != nil {
return nil, err
}
intentLabel, err := s.IntentLabel()
return &ConnectParameters{
OriginCert: originCert,
CloudflaredID: cloudflaredID,
NumPreviousAttempts: s.NumPreviousAttempts(),
Tags: tags,
CloudflaredVersion: cloudflaredVersion,
Scope: scope,
IntentLabel: intentLabel,
}, nil
}
@@ -305,7 +326,7 @@ type TunnelServer interface {
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
GetServerInfo(ctx context.Context) (*ServerInfo, error)
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
Connect(ctx context.Context, paramaters *ConnectParameters) (*ConnectResult, error)
Connect(ctx context.Context, parameters *ConnectParameters) (ConnectResult, error)
}
func TunnelServer_ServerToClient(s TunnelServer) tunnelrpc.TunnelServer {
@@ -366,11 +387,11 @@ func (i TunnelServer_PogsImpl) UnregisterTunnel(p tunnelrpc.TunnelServer_unregis
}
func (i TunnelServer_PogsImpl) Connect(p tunnelrpc.TunnelServer_connect) error {
paramaters, err := p.Params.Parameters()
parameters, err := p.Params.Parameters()
if err != nil {
return err
}
pogsParameters, err := UnmarshalConnectParameters(paramaters)
pogsParameters, err := UnmarshalConnectParameters(parameters)
if err != nil {
return err
}
@@ -383,7 +404,7 @@ func (i TunnelServer_PogsImpl) Connect(p tunnelrpc.TunnelServer_connect) error {
if err != nil {
return err
}
return MarshalConnectResult(result, connectResult)
return connectResult.Marshal(result)
}
type TunnelServer_PogsClient struct {
@@ -447,7 +468,7 @@ func (c TunnelServer_PogsClient) UnregisterTunnel(ctx context.Context, gracePeri
func (c TunnelServer_PogsClient) Connect(ctx context.Context,
parameters *ConnectParameters,
) (*ConnectResult, error) {
) (ConnectResult, error) {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.Connect(ctx, func(p tunnelrpc.TunnelServer_connect_Params) error {
connectParameters, err := p.NewParameters()
+13 -42
View File
@@ -11,50 +11,21 @@ import (
capnp "zombiezen.com/go/capnproto2"
)
// Assert *SystemName implements Scope
var _ Scope = (*SystemName)(nil)
// Assert *Group implements Scope
var _ Scope = (*Group)(nil)
func TestScope(t *testing.T) {
testCases := []Scope{
&SystemName{systemName: "my_system"},
&Group{group: "my_group"},
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewScope(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalScope(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalScope(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func sampleTestConnectResult() *ConnectResult {
return &ConnectResult{
Err: &ConnectError{
func TestConnectResult(t *testing.T) {
testCases := []ConnectResult{
&ConnectError{
Cause: "it broke",
ShouldRetry: false,
RetryAfter: 2 * time.Second,
},
ServerInfo: ServerInfo{LocationName: "computer"},
ClientConfig: *sampleClientConfig(),
}
}
func TestConnectResult(t *testing.T) {
testCases := []*ConnectResult{
sampleTestConnectResult(),
&ConnectSuccess{
ServerLocationName: "SFO",
Config: sampleClientConfig(),
},
&ConnectSuccess{
ServerLocationName: "",
Config: nil,
},
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
@@ -78,7 +49,7 @@ func TestConnectParameters(t *testing.T) {
testCases := []*ConnectParameters{
sampleConnectParameters(),
sampleConnectParameters(func(c *ConnectParameters) {
c.Scope = &SystemName{systemName: "my_system"}
c.IntentLabel = "my_intent"
}),
sampleConnectParameters(func(c *ConnectParameters) {
c.Tags = nil
@@ -118,7 +89,7 @@ func sampleConnectParameters(overrides ...func(*ConnectParameters)) *ConnectPara
},
},
CloudflaredVersion: "7.0",
Scope: &Group{group: "my_group"},
IntentLabel: "my_intent",
}
sample.ensureNoZeroFields()
for _, f := range overrides {
+14 -17
View File
@@ -57,25 +57,15 @@ struct CapnpConnectParameters {
tags @3 :List(Tag);
# release version of cloudflared
cloudflaredVersion @4 :Text;
# identifier for this cloudflared instance
scope @5 :Scope;
}
struct Scope {
value :union {
# Standalone instance
systemName @0 :Text;
# Associated with a group of identical cloudflared instances
group @1 :Text;
}
# which intent this cloudflared instance should get its behaviour from
intentLabel @5 :Text;
}
struct ConnectResult {
err @0 :ConnectError;
# Information about the server this connection is established with
serverInfo @1 :ServerInfo;
# How this cloudflared instance should be configured
clientConfig @2 :ClientConfig;
result :union {
err @0 :ConnectError;
success @1 :ConnectSuccess;
}
}
struct ConnectError {
@@ -85,6 +75,13 @@ struct ConnectError {
shouldRetry @2 :Bool;
}
struct ConnectSuccess {
# Information about the server this connection is established with
serverLocationName @0 :Text;
# How this cloudflared instance should be configured. This can be null if there isn't an intent for this origin yet
clientConfig @1 :ClientConfig;
}
struct ClientConfig {
# Version of this configuration. This value is opaque, but is guaranteed
# to monotonically increase in value. Any configuration supplied to
@@ -136,7 +133,7 @@ struct EdgeConnectionConfig {
struct ReverseProxyConfig {
tunnelHostname @0 :Text;
origin :union {
originConfig :union {
http @1 :HTTPOriginConfig;
websocket @2 :WebSocketOriginConfig;
helloWorld @3 :HelloWorldOriginConfig;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -64,7 +64,7 @@ func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, op
default:
maxRetries := aws.IntValue(cfg.MaxRetries)
if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries {
maxRetries = 3
maxRetries = DefaultRetryerMaxNumRetries
}
svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries}
}
+103 -29
View File
@@ -1,6 +1,7 @@
package client
import (
"math"
"strconv"
"time"
@@ -9,69 +10,142 @@ import (
)
// DefaultRetryer implements basic retry logic using exponential backoff for
// most services. If you want to implement custom retry logic, implement the
// request.Retryer interface or create a structure type that composes this
// struct and override the specific methods. For example, to override only
// the MaxRetries method:
// most services. If you want to implement custom retry logic, you can implement the
// request.Retryer interface.
//
// type retryer struct {
// client.DefaultRetryer
// }
//
// // This implementation always has 100 max retries
// func (d retryer) MaxRetries() int { return 100 }
type DefaultRetryer struct {
NumMaxRetries int
// Num max Retries is the number of max retries that will be performed.
// By default, this is zero.
NumMaxRetries int
// MinRetryDelay is the minimum retry delay after which retry will be performed.
// If not set, the value is 0ns.
MinRetryDelay time.Duration
// MinThrottleRetryDelay is the minimum retry delay when throttled.
// If not set, the value is 0ns.
MinThrottleDelay time.Duration
// MaxRetryDelay is the maximum retry delay before which retry must be performed.
// If not set, the value is 0ns.
MaxRetryDelay time.Duration
// MaxThrottleDelay is the maximum retry delay when throttled.
// If not set, the value is 0ns.
MaxThrottleDelay time.Duration
}
const (
// DefaultRetryerMaxNumRetries sets maximum number of retries
DefaultRetryerMaxNumRetries = 3
// DefaultRetryerMinRetryDelay sets minimum retry delay
DefaultRetryerMinRetryDelay = 30 * time.Millisecond
// DefaultRetryerMinThrottleDelay sets minimum delay when throttled
DefaultRetryerMinThrottleDelay = 500 * time.Millisecond
// DefaultRetryerMaxRetryDelay sets maximum retry delay
DefaultRetryerMaxRetryDelay = 300 * time.Second
// DefaultRetryerMaxThrottleDelay sets maximum delay when throttled
DefaultRetryerMaxThrottleDelay = 300 * time.Second
)
// MaxRetries returns the number of maximum returns the service will use to make
// an individual API request.
func (d DefaultRetryer) MaxRetries() int {
return d.NumMaxRetries
}
// setRetryerDefaults sets the default values of the retryer if not set
func (d *DefaultRetryer) setRetryerDefaults() {
if d.MinRetryDelay == 0 {
d.MinRetryDelay = DefaultRetryerMinRetryDelay
}
if d.MaxRetryDelay == 0 {
d.MaxRetryDelay = DefaultRetryerMaxRetryDelay
}
if d.MinThrottleDelay == 0 {
d.MinThrottleDelay = DefaultRetryerMinThrottleDelay
}
if d.MaxThrottleDelay == 0 {
d.MaxThrottleDelay = DefaultRetryerMaxThrottleDelay
}
}
// RetryRules returns the delay duration before retrying this request again
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
// Set the upper limit of delay in retrying at ~five minutes
minTime := 30
// if number of max retries is zero, no retries will be performed.
if d.NumMaxRetries == 0 {
return 0
}
// Sets default value for retryer members
d.setRetryerDefaults()
// minDelay is the minimum retryer delay
minDelay := d.MinRetryDelay
var initialDelay time.Duration
isThrottle := r.IsErrorThrottle()
if isThrottle {
if delay, ok := getRetryDelay(r); ok {
return delay
if delay, ok := getRetryAfterDelay(r); ok {
initialDelay = delay
}
minTime = 500
minDelay = d.MinThrottleDelay
}
retryCount := r.RetryCount
if isThrottle && retryCount > 8 {
retryCount = 8
} else if retryCount > 13 {
retryCount = 13
// maxDelay the maximum retryer delay
maxDelay := d.MaxRetryDelay
if isThrottle {
maxDelay = d.MaxThrottleDelay
}
delay := (1 << uint(retryCount)) * (sdkrand.SeededRand.Intn(minTime) + minTime)
return time.Duration(delay) * time.Millisecond
var delay time.Duration
// Logic to cap the retry count based on the minDelay provided
actualRetryCount := int(math.Log2(float64(minDelay))) + 1
if actualRetryCount < 63-retryCount {
delay = time.Duration(1<<uint64(retryCount)) * getJitterDelay(minDelay)
if delay > maxDelay {
delay = getJitterDelay(maxDelay / 2)
}
} else {
delay = getJitterDelay(maxDelay / 2)
}
return delay + initialDelay
}
// getJitterDelay returns a jittered delay for retry
func getJitterDelay(duration time.Duration) time.Duration {
return time.Duration(sdkrand.SeededRand.Int63n(int64(duration)) + int64(duration))
}
// ShouldRetry returns true if the request should be retried.
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
// ShouldRetry returns false if number of max retries is 0.
if d.NumMaxRetries == 0 {
return false
}
// If one of the other handlers already set the retry state
// we don't want to override it based on the service's state
if r.Retryable != nil {
return *r.Retryable
}
if r.HTTPResponse.StatusCode >= 500 && r.HTTPResponse.StatusCode != 501 {
return true
}
return r.IsErrorRetryable() || r.IsErrorThrottle()
}
// This will look in the Retry-After header, RFC 7231, for how long
// it will wait before attempting another request
func getRetryDelay(r *request.Request) (time.Duration, bool) {
func getRetryAfterDelay(r *request.Request) (time.Duration, bool) {
if !canUseRetryAfterHeader(r) {
return 0, false
}
+28
View File
@@ -0,0 +1,28 @@
package client
import (
"time"
"github.com/aws/aws-sdk-go/aws/request"
)
// NoOpRetryer provides a retryer that performs no retries.
// It should be used when we do not want retries to be performed.
type NoOpRetryer struct{}
// MaxRetries returns the number of maximum returns the service will use to make
// an individual API; For NoOpRetryer the MaxRetries will always be zero.
func (d NoOpRetryer) MaxRetries() int {
return 0
}
// ShouldRetry will always return false for NoOpRetryer, as it should never retry.
func (d NoOpRetryer) ShouldRetry(_ *request.Request) bool {
return false
}
// RetryRules returns the delay duration before retrying this request again;
// since NoOpRetryer does not retry, RetryRules always returns 0.
func (d NoOpRetryer) RetryRules(_ *request.Request) time.Duration {
return 0
}
+531
View File
@@ -179,6 +179,242 @@ func IntValueMap(src map[string]*int) map[string]int {
return dst
}
// Uint returns a pointer to the uint value passed in.
func Uint(v uint) *uint {
return &v
}
// UintValue returns the value of the uint pointer passed in or
// 0 if the pointer is nil.
func UintValue(v *uint) uint {
if v != nil {
return *v
}
return 0
}
// UintSlice converts a slice of uint values uinto a slice of
// uint pointers
func UintSlice(src []uint) []*uint {
dst := make([]*uint, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// UintValueSlice converts a slice of uint pointers uinto a slice of
// uint values
func UintValueSlice(src []*uint) []uint {
dst := make([]uint, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// UintMap converts a string map of uint values uinto a string
// map of uint pointers
func UintMap(src map[string]uint) map[string]*uint {
dst := make(map[string]*uint)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// UintValueMap converts a string map of uint pointers uinto a string
// map of uint values
func UintValueMap(src map[string]*uint) map[string]uint {
dst := make(map[string]uint)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int8 returns a pointer to the int8 value passed in.
func Int8(v int8) *int8 {
return &v
}
// Int8Value returns the value of the int8 pointer passed in or
// 0 if the pointer is nil.
func Int8Value(v *int8) int8 {
if v != nil {
return *v
}
return 0
}
// Int8Slice converts a slice of int8 values into a slice of
// int8 pointers
func Int8Slice(src []int8) []*int8 {
dst := make([]*int8, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int8ValueSlice converts a slice of int8 pointers into a slice of
// int8 values
func Int8ValueSlice(src []*int8) []int8 {
dst := make([]int8, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Int8Map converts a string map of int8 values into a string
// map of int8 pointers
func Int8Map(src map[string]int8) map[string]*int8 {
dst := make(map[string]*int8)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Int8ValueMap converts a string map of int8 pointers into a string
// map of int8 values
func Int8ValueMap(src map[string]*int8) map[string]int8 {
dst := make(map[string]int8)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int16 returns a pointer to the int16 value passed in.
func Int16(v int16) *int16 {
return &v
}
// Int16Value returns the value of the int16 pointer passed in or
// 0 if the pointer is nil.
func Int16Value(v *int16) int16 {
if v != nil {
return *v
}
return 0
}
// Int16Slice converts a slice of int16 values into a slice of
// int16 pointers
func Int16Slice(src []int16) []*int16 {
dst := make([]*int16, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int16ValueSlice converts a slice of int16 pointers into a slice of
// int16 values
func Int16ValueSlice(src []*int16) []int16 {
dst := make([]int16, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Int16Map converts a string map of int16 values into a string
// map of int16 pointers
func Int16Map(src map[string]int16) map[string]*int16 {
dst := make(map[string]*int16)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Int16ValueMap converts a string map of int16 pointers into a string
// map of int16 values
func Int16ValueMap(src map[string]*int16) map[string]int16 {
dst := make(map[string]int16)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int32 returns a pointer to the int32 value passed in.
func Int32(v int32) *int32 {
return &v
}
// Int32Value returns the value of the int32 pointer passed in or
// 0 if the pointer is nil.
func Int32Value(v *int32) int32 {
if v != nil {
return *v
}
return 0
}
// Int32Slice converts a slice of int32 values into a slice of
// int32 pointers
func Int32Slice(src []int32) []*int32 {
dst := make([]*int32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int32ValueSlice converts a slice of int32 pointers into a slice of
// int32 values
func Int32ValueSlice(src []*int32) []int32 {
dst := make([]int32, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Int32Map converts a string map of int32 values into a string
// map of int32 pointers
func Int32Map(src map[string]int32) map[string]*int32 {
dst := make(map[string]*int32)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Int32ValueMap converts a string map of int32 pointers into a string
// map of int32 values
func Int32ValueMap(src map[string]*int32) map[string]int32 {
dst := make(map[string]int32)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int64 returns a pointer to the int64 value passed in.
func Int64(v int64) *int64 {
return &v
@@ -238,6 +474,301 @@ func Int64ValueMap(src map[string]*int64) map[string]int64 {
return dst
}
// Uint8 returns a pointer to the uint8 value passed in.
func Uint8(v uint8) *uint8 {
return &v
}
// Uint8Value returns the value of the uint8 pointer passed in or
// 0 if the pointer is nil.
func Uint8Value(v *uint8) uint8 {
if v != nil {
return *v
}
return 0
}
// Uint8Slice converts a slice of uint8 values into a slice of
// uint8 pointers
func Uint8Slice(src []uint8) []*uint8 {
dst := make([]*uint8, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint8ValueSlice converts a slice of uint8 pointers into a slice of
// uint8 values
func Uint8ValueSlice(src []*uint8) []uint8 {
dst := make([]uint8, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Uint8Map converts a string map of uint8 values into a string
// map of uint8 pointers
func Uint8Map(src map[string]uint8) map[string]*uint8 {
dst := make(map[string]*uint8)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Uint8ValueMap converts a string map of uint8 pointers into a string
// map of uint8 values
func Uint8ValueMap(src map[string]*uint8) map[string]uint8 {
dst := make(map[string]uint8)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Uint16 returns a pointer to the uint16 value passed in.
func Uint16(v uint16) *uint16 {
return &v
}
// Uint16Value returns the value of the uint16 pointer passed in or
// 0 if the pointer is nil.
func Uint16Value(v *uint16) uint16 {
if v != nil {
return *v
}
return 0
}
// Uint16Slice converts a slice of uint16 values into a slice of
// uint16 pointers
func Uint16Slice(src []uint16) []*uint16 {
dst := make([]*uint16, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint16ValueSlice converts a slice of uint16 pointers into a slice of
// uint16 values
func Uint16ValueSlice(src []*uint16) []uint16 {
dst := make([]uint16, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Uint16Map converts a string map of uint16 values into a string
// map of uint16 pointers
func Uint16Map(src map[string]uint16) map[string]*uint16 {
dst := make(map[string]*uint16)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Uint16ValueMap converts a string map of uint16 pointers into a string
// map of uint16 values
func Uint16ValueMap(src map[string]*uint16) map[string]uint16 {
dst := make(map[string]uint16)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Uint32 returns a pointer to the uint32 value passed in.
func Uint32(v uint32) *uint32 {
return &v
}
// Uint32Value returns the value of the uint32 pointer passed in or
// 0 if the pointer is nil.
func Uint32Value(v *uint32) uint32 {
if v != nil {
return *v
}
return 0
}
// Uint32Slice converts a slice of uint32 values into a slice of
// uint32 pointers
func Uint32Slice(src []uint32) []*uint32 {
dst := make([]*uint32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint32ValueSlice converts a slice of uint32 pointers into a slice of
// uint32 values
func Uint32ValueSlice(src []*uint32) []uint32 {
dst := make([]uint32, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Uint32Map converts a string map of uint32 values into a string
// map of uint32 pointers
func Uint32Map(src map[string]uint32) map[string]*uint32 {
dst := make(map[string]*uint32)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Uint32ValueMap converts a string map of uint32 pointers into a string
// map of uint32 values
func Uint32ValueMap(src map[string]*uint32) map[string]uint32 {
dst := make(map[string]uint32)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Uint64 returns a pointer to the uint64 value passed in.
func Uint64(v uint64) *uint64 {
return &v
}
// Uint64Value returns the value of the uint64 pointer passed in or
// 0 if the pointer is nil.
func Uint64Value(v *uint64) uint64 {
if v != nil {
return *v
}
return 0
}
// Uint64Slice converts a slice of uint64 values into a slice of
// uint64 pointers
func Uint64Slice(src []uint64) []*uint64 {
dst := make([]*uint64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Uint64ValueSlice converts a slice of uint64 pointers into a slice of
// uint64 values
func Uint64ValueSlice(src []*uint64) []uint64 {
dst := make([]uint64, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Uint64Map converts a string map of uint64 values into a string
// map of uint64 pointers
func Uint64Map(src map[string]uint64) map[string]*uint64 {
dst := make(map[string]*uint64)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Uint64ValueMap converts a string map of uint64 pointers into a string
// map of uint64 values
func Uint64ValueMap(src map[string]*uint64) map[string]uint64 {
dst := make(map[string]uint64)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Float32 returns a pointer to the float32 value passed in.
func Float32(v float32) *float32 {
return &v
}
// Float32Value returns the value of the float32 pointer passed in or
// 0 if the pointer is nil.
func Float32Value(v *float32) float32 {
if v != nil {
return *v
}
return 0
}
// Float32Slice converts a slice of float32 values into a slice of
// float32 pointers
func Float32Slice(src []float32) []*float32 {
dst := make([]*float32, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float32ValueSlice converts a slice of float32 pointers into a slice of
// float32 values
func Float32ValueSlice(src []*float32) []float32 {
dst := make([]float32, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Float32Map converts a string map of float32 values into a string
// map of float32 pointers
func Float32Map(src map[string]float32) map[string]*float32 {
dst := make(map[string]*float32)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Float32ValueMap converts a string map of float32 pointers into a string
// map of float32 values
func Float32ValueMap(src map[string]*float32) map[string]float32 {
dst := make(map[string]float32)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Float64 returns a pointer to the float64 value passed in.
func Float64(v float64) *float64 {
return &v
+6 -5
View File
@@ -16,25 +16,26 @@ var (
type metricChan struct {
ch chan metric
paused int64
paused *int64
}
func newMetricChan(size int) metricChan {
return metricChan{
ch: make(chan metric, size),
ch: make(chan metric, size),
paused: new(int64),
}
}
func (ch *metricChan) Pause() {
atomic.StoreInt64(&ch.paused, pausedEnum)
atomic.StoreInt64(ch.paused, pausedEnum)
}
func (ch *metricChan) Continue() {
atomic.StoreInt64(&ch.paused, runningEnum)
atomic.StoreInt64(ch.paused, runningEnum)
}
func (ch *metricChan) IsPaused() bool {
v := atomic.LoadInt64(&ch.paused)
v := atomic.LoadInt64(ch.paused)
return v == pausedEnum
}
+15 -14
View File
@@ -152,18 +152,19 @@ type EC2IAMInfo struct {
// An EC2InstanceIdentityDocument provides the shape for unmarshaling
// an instance identity document
type EC2InstanceIdentityDocument struct {
DevpayProductCodes []string `json:"devpayProductCodes"`
AvailabilityZone string `json:"availabilityZone"`
PrivateIP string `json:"privateIp"`
Version string `json:"version"`
Region string `json:"region"`
InstanceID string `json:"instanceId"`
BillingProducts []string `json:"billingProducts"`
InstanceType string `json:"instanceType"`
AccountID string `json:"accountId"`
PendingTime time.Time `json:"pendingTime"`
ImageID string `json:"imageId"`
KernelID string `json:"kernelId"`
RamdiskID string `json:"ramdiskId"`
Architecture string `json:"architecture"`
DevpayProductCodes []string `json:"devpayProductCodes"`
MarketplaceProductCodes []string `json:"marketplaceProductCodes"`
AvailabilityZone string `json:"availabilityZone"`
PrivateIP string `json:"privateIp"`
Version string `json:"version"`
Region string `json:"region"`
InstanceID string `json:"instanceId"`
BillingProducts []string `json:"billingProducts"`
InstanceType string `json:"instanceType"`
AccountID string `json:"accountId"`
PendingTime time.Time `json:"pendingTime"`
ImageID string `json:"imageId"`
KernelID string `json:"kernelId"`
RamdiskID string `json:"ramdiskId"`
Architecture string `json:"architecture"`
}
+747 -15
View File
@@ -11,6 +11,8 @@ const (
AwsPartitionID = "aws" // AWS Standard partition.
AwsCnPartitionID = "aws-cn" // AWS China partition.
AwsUsGovPartitionID = "aws-us-gov" // AWS GovCloud (US) partition.
AwsIsoPartitionID = "aws-iso" // AWS ISO (US) partition.
AwsIsoBPartitionID = "aws-iso-b" // AWS ISOB (US) partition.
)
// AWS Standard partition's regions.
@@ -47,8 +49,18 @@ const (
UsGovWest1RegionID = "us-gov-west-1" // AWS GovCloud (US).
)
// AWS ISO (US) partition's regions.
const (
UsIsoEast1RegionID = "us-iso-east-1" // US ISO East.
)
// AWS ISOB (US) partition's regions.
const (
UsIsobEast1RegionID = "us-isob-east-1" // US ISOB East (Ohio).
)
// DefaultResolver returns an Endpoint resolver that will be able
// to resolve endpoints for: AWS Standard, AWS China, and AWS GovCloud (US).
// to resolve endpoints for: AWS Standard, AWS China, AWS GovCloud (US), AWS ISO (US), and AWS ISOB (US).
//
// Use DefaultPartitions() to get the list of the default partitions.
func DefaultResolver() Resolver {
@@ -56,7 +68,7 @@ func DefaultResolver() Resolver {
}
// DefaultPartitions returns a list of the partitions the SDK is bundled
// with. The available partitions are: AWS Standard, AWS China, and AWS GovCloud (US).
// with. The available partitions are: AWS Standard, AWS China, AWS GovCloud (US), AWS ISO (US), and AWS ISOB (US).
//
// partitions := endpoints.DefaultPartitions
// for _, p := range partitions {
@@ -70,6 +82,8 @@ var defaultPartitions = partitions{
awsPartition,
awscnPartition,
awsusgovPartition,
awsisoPartition,
awsisobPartition,
}
// AwsPartition returns the Resolver for AWS Standard.
@@ -467,8 +481,14 @@ var awsPartition = partition{
"ap-southeast-2": endpoint{},
"eu-central-1": endpoint{},
"eu-west-1": endpoint{},
"us-east-1": endpoint{},
"us-west-2": endpoint{},
"fips": endpoint{
Hostname: "appstream2-fips.us-west-2.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-west-2",
},
},
"us-east-1": endpoint{},
"us-west-2": endpoint{},
},
},
"appsync": service{
@@ -490,6 +510,7 @@ var awsPartition = partition{
"athena": service{
Endpoints: endpoints{
"ap-east-1": endpoint{},
"ap-northeast-1": endpoint{},
"ap-northeast-2": endpoint{},
"ap-south-1": endpoint{},
@@ -910,6 +931,7 @@ var awsPartition = partition{
"ap-southeast-2": endpoint{},
"ca-central-1": endpoint{},
"eu-central-1": endpoint{},
"eu-north-1": endpoint{},
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
@@ -1038,6 +1060,16 @@ var awsPartition = partition{
"us-west-2": endpoint{},
},
},
"connect": service{
Endpoints: endpoints{
"ap-northeast-1": endpoint{},
"ap-southeast-2": endpoint{},
"eu-central-1": endpoint{},
"us-east-1": endpoint{},
"us-west-2": endpoint{},
},
},
"cur": service{
Endpoints: endpoints{
@@ -1074,8 +1106,11 @@ var awsPartition = partition{
"ap-northeast-2": endpoint{},
"ap-southeast-1": endpoint{},
"ap-southeast-2": endpoint{},
"ca-central-1": endpoint{},
"eu-central-1": endpoint{},
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"fips-us-east-1": endpoint{
Hostname: "datasync-fips.us-east-1.amazonaws.com",
CredentialScope: credentialScope{
@@ -1100,10 +1135,11 @@ var awsPartition = partition{
Region: "us-west-2",
},
},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-1": endpoint{},
"us-west-2": endpoint{},
"me-south-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-1": endpoint{},
"us-west-2": endpoint{},
},
},
"dax": service{
@@ -1131,6 +1167,7 @@ var awsPartition = partition{
"directconnect": service{
Endpoints: endpoints{
"ap-east-1": endpoint{},
"ap-northeast-1": endpoint{},
"ap-northeast-2": endpoint{},
"ap-south-1": endpoint{},
@@ -1706,6 +1743,7 @@ var awsPartition = partition{
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
@@ -1757,6 +1795,7 @@ var awsPartition = partition{
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-1-fips": endpoint{
@@ -1846,6 +1885,7 @@ var awsPartition = partition{
},
},
Endpoints: endpoints{
"ap-east-1": endpoint{},
"ap-northeast-1": endpoint{},
"ap-northeast-2": endpoint{},
"ap-south-1": endpoint{},
@@ -1857,6 +1897,7 @@ var awsPartition = partition{
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
@@ -1942,6 +1983,7 @@ var awsPartition = partition{
},
Endpoints: endpoints{
"ap-northeast-1": endpoint{},
"ap-northeast-2": endpoint{},
"ap-southeast-2": endpoint{},
"eu-west-1": endpoint{},
"us-east-1": endpoint{},
@@ -2217,6 +2259,7 @@ var awsPartition = partition{
"ap-southeast-2": endpoint{},
"eu-central-1": endpoint{},
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
@@ -2514,6 +2557,16 @@ var awsPartition = partition{
"us-west-2": endpoint{},
},
},
"qldb": service{
Endpoints: endpoints{
"ap-northeast-1": endpoint{},
"eu-west-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-2": endpoint{},
},
},
"ram": service{
Endpoints: endpoints{
@@ -2614,12 +2667,36 @@ var awsPartition = partition{
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-1": endpoint{},
"us-west-2": endpoint{},
"fips-us-east-1": endpoint{
Hostname: "resource-groups-fips.us-east-1.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-east-1",
},
},
"fips-us-east-2": endpoint{
Hostname: "resource-groups-fips.us-east-2.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-east-2",
},
},
"fips-us-west-1": endpoint{
Hostname: "resource-groups-fips.us-west-1.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-west-1",
},
},
"fips-us-west-2": endpoint{
Hostname: "resource-groups-fips.us-west-2.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-west-2",
},
},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-1": endpoint{},
"us-west-2": endpoint{},
},
},
"robomaker": service{
@@ -3023,6 +3100,7 @@ var awsPartition = partition{
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
@@ -3133,6 +3211,7 @@ var awsPartition = partition{
"servicediscovery": service{
Endpoints: endpoints{
"ap-east-1": endpoint{},
"ap-northeast-1": endpoint{},
"ap-northeast-2": endpoint{},
"ap-south-1": endpoint{},
@@ -3140,9 +3219,11 @@ var awsPartition = partition{
"ap-southeast-2": endpoint{},
"ca-central-1": endpoint{},
"eu-central-1": endpoint{},
"eu-north-1": endpoint{},
"eu-west-1": endpoint{},
"eu-west-2": endpoint{},
"eu-west-3": endpoint{},
"me-south-1": endpoint{},
"sa-east-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
@@ -3150,6 +3231,16 @@ var awsPartition = partition{
"us-west-2": endpoint{},
},
},
"session.qldb": service{
Endpoints: endpoints{
"ap-northeast-1": endpoint{},
"eu-west-1": endpoint{},
"us-east-1": endpoint{},
"us-east-2": endpoint{},
"us-west-2": endpoint{},
},
},
"shield": service{
IsRegionalized: boxedFalse,
Defaults: endpoint{
@@ -4164,7 +4255,7 @@ var awscnPartition = partition{
Endpoints: endpoints{
"aws-cn-global": endpoint{
Hostname: "support.cn-north-1.amazonaws.com",
Hostname: "support.cn-north-1.amazonaws.com.cn",
CredentialScope: credentialScope{
Region: "cn-north-1",
},
@@ -4698,6 +4789,7 @@ var awsusgovPartition = partition{
"ram": service{
Endpoints: endpoints{
"us-gov-east-1": endpoint{},
"us-gov-west-1": endpoint{},
},
},
@@ -4721,6 +4813,25 @@ var awsusgovPartition = partition{
"us-gov-west-1": endpoint{},
},
},
"resource-groups": service{
Endpoints: endpoints{
"fips-us-gov-east-1": endpoint{
Hostname: "resource-groups.us-gov-east-1.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-gov-east-1",
},
},
"fips-us-gov-west-1": endpoint{
Hostname: "resource-groups.us-gov-west-1.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-gov-west-1",
},
},
"us-gov-east-1": endpoint{},
"us-gov-west-1": endpoint{},
},
},
"route53": service{
PartitionEndpoint: "aws-us-gov-global",
IsRegionalized: boxedFalse,
@@ -4822,6 +4933,18 @@ var awsusgovPartition = partition{
},
},
},
"servicecatalog": service{
Endpoints: endpoints{
"us-gov-west-1": endpoint{},
"us-gov-west-1-fips": endpoint{
Hostname: "servicecatalog-fips.us-gov-west-1.amazonaws.com",
CredentialScope: credentialScope{
Region: "us-gov-west-1",
},
},
},
},
"sms": service{
Endpoints: endpoints{
@@ -4947,3 +5070,612 @@ var awsusgovPartition = partition{
},
},
}
// AwsIsoPartition returns the Resolver for AWS ISO (US).
func AwsIsoPartition() Partition {
return awsisoPartition.Partition()
}
var awsisoPartition = partition{
ID: "aws-iso",
Name: "AWS ISO (US)",
DNSSuffix: "c2s.ic.gov",
RegionRegex: regionRegex{
Regexp: func() *regexp.Regexp {
reg, _ := regexp.Compile("^us\\-iso\\-\\w+\\-\\d+$")
return reg
}(),
},
Defaults: endpoint{
Hostname: "{service}.{region}.{dnsSuffix}",
Protocols: []string{"https"},
SignatureVersions: []string{"v4"},
},
Regions: regions{
"us-iso-east-1": region{
Description: "US ISO East",
},
},
Services: services{
"api.ecr": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Hostname: "api.ecr.us-iso-east-1.c2s.ic.gov",
CredentialScope: credentialScope{
Region: "us-iso-east-1",
},
},
},
},
"application-autoscaling": service{
Defaults: endpoint{
Hostname: "autoscaling.{region}.amazonaws.com",
Protocols: []string{"http", "https"},
CredentialScope: credentialScope{
Service: "application-autoscaling",
},
},
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"autoscaling": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"cloudformation": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"cloudtrail": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"codedeploy": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"config": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"datapipeline": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"directconnect": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"dms": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"ds": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"dynamodb": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"ec2": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"ec2metadata": service{
PartitionEndpoint: "aws-global",
IsRegionalized: boxedFalse,
Endpoints: endpoints{
"aws-global": endpoint{
Hostname: "169.254.169.254/latest",
Protocols: []string{"http"},
},
},
},
"ecs": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"elasticache": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"elasticloadbalancing": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"elasticmapreduce": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"https"},
},
},
},
"events": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"glacier": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"health": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"iam": service{
PartitionEndpoint: "aws-iso-global",
IsRegionalized: boxedFalse,
Endpoints: endpoints{
"aws-iso-global": endpoint{
Hostname: "iam.us-iso-east-1.c2s.ic.gov",
CredentialScope: credentialScope{
Region: "us-iso-east-1",
},
},
},
},
"kinesis": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"kms": service{
Endpoints: endpoints{
"ProdFips": endpoint{
Hostname: "kms-fips.us-iso-east-1.c2s.ic.gov",
CredentialScope: credentialScope{
Region: "us-iso-east-1",
},
},
"us-iso-east-1": endpoint{},
},
},
"lambda": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"logs": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"monitoring": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"rds": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"redshift": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"route53": service{
PartitionEndpoint: "aws-iso-global",
IsRegionalized: boxedFalse,
Endpoints: endpoints{
"aws-iso-global": endpoint{
Hostname: "route53.c2s.ic.gov",
CredentialScope: credentialScope{
Region: "us-iso-east-1",
},
},
},
},
"s3": service{
Defaults: endpoint{
SignatureVersions: []string{"s3v4"},
},
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
SignatureVersions: []string{"s3v4"},
},
},
},
"snowball": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"sns": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"sqs": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"states": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"streams.dynamodb": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
CredentialScope: credentialScope{
Service: "dynamodb",
},
},
Endpoints: endpoints{
"us-iso-east-1": endpoint{
Protocols: []string{"http", "https"},
},
},
},
"sts": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"support": service{
PartitionEndpoint: "aws-iso-global",
Endpoints: endpoints{
"aws-iso-global": endpoint{
Hostname: "support.us-iso-east-1.c2s.ic.gov",
CredentialScope: credentialScope{
Region: "us-iso-east-1",
},
},
},
},
"swf": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
"workspaces": service{
Endpoints: endpoints{
"us-iso-east-1": endpoint{},
},
},
},
}
// AwsIsoBPartition returns the Resolver for AWS ISOB (US).
func AwsIsoBPartition() Partition {
return awsisobPartition.Partition()
}
var awsisobPartition = partition{
ID: "aws-iso-b",
Name: "AWS ISOB (US)",
DNSSuffix: "sc2s.sgov.gov",
RegionRegex: regionRegex{
Regexp: func() *regexp.Regexp {
reg, _ := regexp.Compile("^us\\-isob\\-\\w+\\-\\d+$")
return reg
}(),
},
Defaults: endpoint{
Hostname: "{service}.{region}.{dnsSuffix}",
Protocols: []string{"https"},
SignatureVersions: []string{"v4"},
},
Regions: regions{
"us-isob-east-1": region{
Description: "US ISOB East (Ohio)",
},
},
Services: services{
"application-autoscaling": service{
Defaults: endpoint{
Hostname: "autoscaling.{region}.amazonaws.com",
Protocols: []string{"http", "https"},
CredentialScope: credentialScope{
Service: "application-autoscaling",
},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"autoscaling": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"cloudformation": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"cloudtrail": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"config": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"directconnect": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"dms": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"dynamodb": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"ec2": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"ec2metadata": service{
PartitionEndpoint: "aws-global",
IsRegionalized: boxedFalse,
Endpoints: endpoints{
"aws-global": endpoint{
Hostname: "169.254.169.254/latest",
Protocols: []string{"http"},
},
},
},
"elasticache": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"elasticloadbalancing": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{
Protocols: []string{"https"},
},
},
},
"elasticmapreduce": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"events": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"glacier": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"health": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"iam": service{
PartitionEndpoint: "aws-iso-b-global",
IsRegionalized: boxedFalse,
Endpoints: endpoints{
"aws-iso-b-global": endpoint{
Hostname: "iam.us-isob-east-1.sc2s.sgov.gov",
CredentialScope: credentialScope{
Region: "us-isob-east-1",
},
},
},
},
"kinesis": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"kms": service{
Endpoints: endpoints{
"ProdFips": endpoint{
Hostname: "kms-fips.us-isob-east-1.sc2s.sgov.gov",
CredentialScope: credentialScope{
Region: "us-isob-east-1",
},
},
"us-isob-east-1": endpoint{},
},
},
"logs": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"monitoring": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"rds": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"redshift": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"s3": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
SignatureVersions: []string{"s3v4"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"snowball": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"sns": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"sqs": service{
Defaults: endpoint{
SSLCommonName: "{region}.queue.{dnsSuffix}",
Protocols: []string{"http", "https"},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"states": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"streams.dynamodb": service{
Defaults: endpoint{
Protocols: []string{"http", "https"},
CredentialScope: credentialScope{
Service: "dynamodb",
},
},
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"sts": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
"support": service{
PartitionEndpoint: "aws-iso-b-global",
Endpoints: endpoints{
"aws-iso-b-global": endpoint{
Hostname: "support.us-isob-east-1.sc2s.sgov.gov",
CredentialScope: credentialScope{
Region: "us-isob-east-1",
},
},
},
},
"swf": service{
Endpoints: endpoints{
"us-isob-east-1": endpoint{},
},
},
},
}
+2 -2
View File
@@ -23,7 +23,7 @@ type Handlers struct {
Complete HandlerList
}
// Copy returns of this handler's lists.
// Copy returns a copy of this handler's lists.
func (h *Handlers) Copy() Handlers {
return Handlers{
Validate: h.Validate.copy(),
@@ -42,7 +42,7 @@ func (h *Handlers) Copy() Handlers {
}
}
// Clear removes callback functions for all handlers
// Clear removes callback functions for all handlers.
func (h *Handlers) Clear() {
h.Validate.Clear()
h.Build.Clear()
+16 -6
View File
@@ -94,10 +94,6 @@ var validParentCodes = map[string]struct{}{
ErrCodeRead: {},
}
type temporaryError interface {
Temporary() bool
}
func isNestedErrorRetryable(parentErr awserr.Error) bool {
if parentErr == nil {
return false
@@ -116,7 +112,7 @@ func isNestedErrorRetryable(parentErr awserr.Error) bool {
return isCodeRetryable(aerr.Code())
}
if t, ok := err.(temporaryError); ok {
if t, ok := err.(temporary); ok {
return t.Temporary() || isErrConnectionReset(err)
}
@@ -223,6 +219,16 @@ func (r *Request) IsErrorRetryable() bool {
return true
}
// HTTP response status code 501 should not be retried.
// 501 represents Not Implemented which means the request method is not
// supported by the server and cannot be handled.
if r.HTTPResponse != nil {
// HTTP response status code 500 represents internal server error and
// should be retried without any throttle.
if r.HTTPResponse.StatusCode == 500 {
return true
}
}
return IsErrorRetryable(r.Error)
}
@@ -237,7 +243,11 @@ func (r *Request) IsErrorThrottle() bool {
if r.HTTPResponse != nil {
switch r.HTTPResponse.StatusCode {
case 429, 502, 503, 504:
case
429, // error caused due to too many requests
502, // Bad Gateway error should be throttled
503, // caused when service is unavailable
504: // error occurred due to gateway timeout
return true
}
}
+2 -2
View File
@@ -136,7 +136,7 @@ func New(cfgs ...*aws.Config) *Session {
// to be built with retrieving credentials with AssumeRole set in the config.
//
// See the NewSessionWithOptions func for information on how to override or
// control through code how the Session will be created. Such as specifying the
// control through code how the Session will be created, such as specifying the
// config profile, and controlling if shared config is enabled or not.
func NewSession(cfgs ...*aws.Config) (*Session, error) {
opts := Options{}
@@ -571,7 +571,7 @@ func initHandlers(s *Session) {
}
}
// Copy creates and returns a copy of the current Session, coping the config
// Copy creates and returns a copy of the current Session, copying the config
// and handlers. If any additional configs are provided they will be merged
// on top of the Session's copied config.
//
+1 -1
View File
@@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "1.23.9"
const SDKVersion = "1.25.8"
+9 -2
View File
@@ -162,7 +162,7 @@ loop:
if len(tokens) == 0 {
break loop
}
// if should skip is true, we skip the tokens until should skip is set to false.
step = SkipTokenState
}
@@ -218,7 +218,7 @@ loop:
// S -> equal_expr' expr_stmt'
switch k.Kind {
case ASTKindEqualExpr:
// assiging a value to some key
// assigning a value to some key
k.AppendChild(newExpression(tok))
stack.Push(newExprStatement(k))
case ASTKindExpr:
@@ -250,6 +250,13 @@ loop:
if !runeCompare(tok.Raw(), openBrace) {
return nil, NewParseError("expected '['")
}
// If OpenScopeState is not at the start, we must mark the previous ast as complete
//
// for example: if previous ast was a skip statement;
// we should mark it as complete before we create a new statement
if k.Kind != ASTKindStart {
stack.MarkComplete(k)
}
stmt := newStatement()
stack.Push(stmt)
+3 -3
View File
@@ -22,24 +22,24 @@ func newSkipper() skipper {
}
func (s *skipper) ShouldSkip(tok Token) bool {
// should skip state will be modified only if previous token was new line (NL);
// and the current token is not WhiteSpace (WS).
if s.shouldSkip &&
s.prevTok.Type() == TokenNL &&
tok.Type() != TokenWS {
s.Continue()
return false
}
s.prevTok = tok
return s.shouldSkip
}
func (s *skipper) Skip() {
s.shouldSkip = true
s.prevTok = emptyToken
}
func (s *skipper) Continue() {
s.shouldSkip = false
// empty token is assigned as we return to default state, when should skip is false
s.prevTok = emptyToken
}
+12
View File
@@ -0,0 +1,12 @@
package sdkio
const (
// Byte is 8 bits
Byte int64 = 1
// KibiByte (KiB) is 1024 Bytes
KibiByte = Byte * 1024
// MebiByte (MiB) is 1024 KiB
MebiByte = KibiByte * 1024
// GibiByte (GiB) is 1024 MiB
GibiByte = MebiByte * 1024
)
+15
View File
@@ -0,0 +1,15 @@
// +build go1.10
package sdkmath
import "math"
// Round returns the nearest integer, rounding half away from zero.
//
// Special cases are:
// Round(±0) = ±0
// Round(±Inf) = ±Inf
// Round(NaN) = NaN
func Round(x float64) float64 {
return math.Round(x)
}
+56
View File
@@ -0,0 +1,56 @@
// +build !go1.10
package sdkmath
import "math"
// Copied from the Go standard library's (Go 1.12) math/floor.go for use in
// Go version prior to Go 1.10.
const (
uvone = 0x3FF0000000000000
mask = 0x7FF
shift = 64 - 11 - 1
bias = 1023
signMask = 1 << 63
fracMask = 1<<shift - 1
)
// Round returns the nearest integer, rounding half away from zero.
//
// Special cases are:
// Round(±0) = ±0
// Round(±Inf) = ±Inf
// Round(NaN) = NaN
//
// Copied from the Go standard library's (Go 1.12) math/floor.go for use in
// Go version prior to Go 1.10.
func Round(x float64) float64 {
// Round is a faster implementation of:
//
// func Round(x float64) float64 {
// t := Trunc(x)
// if Abs(x-t) >= 0.5 {
// return t + Copysign(1, x)
// }
// return t
// }
bits := math.Float64bits(x)
e := uint(bits>>shift) & mask
if e < bias {
// Round abs(x) < 1 including denormals.
bits &= signMask // +-0
if e == bias-1 {
bits |= uvone // +-1
}
} else if e < bias+shift {
// Round any abs(x) >= 1 containing a fractional component [0,1).
//
// Numbers with larger exponents are returned unchanged since they
// must be either an integer, infinity, or NaN.
const half = 1 << (shift - 1)
e -= bias
bits += half >> e
bits &^= fracMask >> e
}
return math.Float64frombits(bits)
}
+11
View File
@@ -0,0 +1,11 @@
// +build go1.6
package sdkrand
import "math/rand"
// Read provides the stub for math.Rand.Read method support for go version's
// 1.6 and greater.
func Read(r *rand.Rand, p []byte) (int, error) {
return r.Read(p)
}
+24
View File
@@ -0,0 +1,24 @@
// +build !go1.6
package sdkrand
import "math/rand"
// Read backfills Go 1.6's math.Rand.Reader for Go 1.5
func Read(r *rand.Rand, p []byte) (n int, err error) {
// Copy of Go standard libraries math package's read function not added to
// standard library until Go 1.6.
var pos int8
var val int64
for n = 0; n < len(p); n++ {
if pos == 0 {
val = r.Int63()
pos = 7
}
p[n] = byte(val)
val >>= 8
pos--
}
return n, err
}
+16 -4
View File
@@ -1,8 +1,11 @@
package protocol
import (
"math"
"strconv"
"time"
"github.com/aws/aws-sdk-go/internal/sdkmath"
)
// Names of time formats supported by the SDK
@@ -13,12 +16,19 @@ const (
)
// Time formats supported by the SDK
// Output time is intended to not contain decimals
const (
// RFC 7231#section-7.1.1.1 timetamp format. e.g Tue, 29 Apr 2014 18:30:38 GMT
RFC822TimeFormat = "Mon, 2 Jan 2006 15:04:05 GMT"
// This format is used for output time without seconds precision
RFC822OutputTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
ISO8601TimeFormat = "2006-01-02T15:04:05Z"
ISO8601TimeFormat = "2006-01-02T15:04:05.999999999Z"
// This format is used for output time without seconds precision
ISO8601OutputTimeFormat = "2006-01-02T15:04:05Z"
)
// IsKnownTimestampFormat returns if the timestamp format name
@@ -42,9 +52,9 @@ func FormatTime(name string, t time.Time) string {
switch name {
case RFC822TimeFormatName:
return t.Format(RFC822TimeFormat)
return t.Format(RFC822OutputTimeFormat)
case ISO8601TimeFormatName:
return t.Format(ISO8601TimeFormat)
return t.Format(ISO8601OutputTimeFormat)
case UnixTimeFormatName:
return strconv.FormatInt(t.Unix(), 10)
default:
@@ -62,10 +72,12 @@ func ParseTime(formatName, value string) (time.Time, error) {
return time.Parse(ISO8601TimeFormat, value)
case UnixTimeFormatName:
v, err := strconv.ParseFloat(value, 64)
_, dec := math.Modf(v)
dec = sdkmath.Round(dec*1e3) / 1e3 //Rounds 0.1229999 to 0.123
if err != nil {
return time.Time{}, err
}
return time.Unix(int64(v), 0), nil
return time.Unix(int64(v), int64(dec*(1e9))), nil
default:
panic("unknown timestamp format name, " + formatName)
}
+32
View File
@@ -0,0 +1,32 @@
package xmlutil
import (
"encoding/xml"
"strings"
)
type xmlAttrSlice []xml.Attr
func (x xmlAttrSlice) Len() int {
return len(x)
}
func (x xmlAttrSlice) Less(i, j int) bool {
spaceI, spaceJ := x[i].Name.Space, x[j].Name.Space
localI, localJ := x[i].Name.Local, x[j].Name.Local
valueI, valueJ := x[i].Value, x[j].Value
spaceCmp := strings.Compare(spaceI, spaceJ)
localCmp := strings.Compare(localI, localJ)
valueCmp := strings.Compare(valueI, valueJ)
if spaceCmp == -1 || (spaceCmp == 0 && (localCmp == -1 || (localCmp == 0 && valueCmp == -1))) {
return true
}
return false
}
func (x xmlAttrSlice) Swap(i, j int) {
x[i], x[j] = x[j], x[i]
}
@@ -119,7 +119,18 @@ func (n *XMLNode) findElem(name string) (string, bool) {
// StructToXML writes an XMLNode to a xml.Encoder as tokens.
func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
e.EncodeToken(xml.StartElement{Name: node.Name, Attr: node.Attr})
// Sort Attributes
attrs := node.Attr
if sorted {
sortedAttrs := make([]xml.Attr, len(attrs))
for _, k := range node.Attr {
sortedAttrs = append(sortedAttrs, k)
}
sort.Sort(xmlAttrSlice(sortedAttrs))
attrs = sortedAttrs
}
e.EncodeToken(xml.StartElement{Name: node.Name, Attr: attrs})
if node.Text != "" {
e.EncodeToken(xml.CharData([]byte(node.Text)))
+85 -85
View File
@@ -7043,7 +7043,7 @@ func (s *AbortIncompleteMultipartUpload) SetDaysAfterInitiation(v int64) *AbortI
}
type AbortMultipartUploadInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"AbortMultipartUploadRequest" type:"structure"`
// Name of the bucket to which the multipart upload was initiated.
//
@@ -8084,7 +8084,7 @@ func (s *CommonPrefix) SetPrefix(v string) *CommonPrefix {
}
type CompleteMultipartUploadInput struct {
_ struct{} `type:"structure" payload:"MultipartUpload"`
_ struct{} `locationName:"CompleteMultipartUploadRequest" type:"structure" payload:"MultipartUpload"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -8404,7 +8404,7 @@ func (s *ContinuationEvent) UnmarshalEvent(
}
type CopyObjectInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"CopyObjectRequest" type:"structure"`
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"ObjectCannedACL"`
@@ -9025,7 +9025,7 @@ func (s *CreateBucketConfiguration) SetLocationConstraint(v string) *CreateBucke
}
type CreateBucketInput struct {
_ struct{} `type:"structure" payload:"CreateBucketConfiguration"`
_ struct{} `locationName:"CreateBucketRequest" type:"structure" payload:"CreateBucketConfiguration"`
// The canned ACL to apply to the bucket.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"BucketCannedACL"`
@@ -9166,7 +9166,7 @@ func (s *CreateBucketOutput) SetLocation(v string) *CreateBucketOutput {
}
type CreateMultipartUploadInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"CreateMultipartUploadRequest" type:"structure"`
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"ObjectCannedACL"`
@@ -9708,7 +9708,7 @@ func (s *Delete) SetQuiet(v bool) *Delete {
}
type DeleteBucketAnalyticsConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketAnalyticsConfigurationRequest" type:"structure"`
// The name of the bucket from which an analytics configuration is deleted.
//
@@ -9784,7 +9784,7 @@ func (s DeleteBucketAnalyticsConfigurationOutput) GoString() string {
}
type DeleteBucketCorsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketCorsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -9844,7 +9844,7 @@ func (s DeleteBucketCorsOutput) GoString() string {
}
type DeleteBucketEncryptionInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketEncryptionRequest" type:"structure"`
// The name of the bucket containing the server-side encryption configuration
// to delete.
@@ -9907,7 +9907,7 @@ func (s DeleteBucketEncryptionOutput) GoString() string {
}
type DeleteBucketInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -9953,7 +9953,7 @@ func (s *DeleteBucketInput) getBucket() (v string) {
}
type DeleteBucketInventoryConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketInventoryConfigurationRequest" type:"structure"`
// The name of the bucket containing the inventory configuration to delete.
//
@@ -10029,7 +10029,7 @@ func (s DeleteBucketInventoryConfigurationOutput) GoString() string {
}
type DeleteBucketLifecycleInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketLifecycleRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10089,7 +10089,7 @@ func (s DeleteBucketLifecycleOutput) GoString() string {
}
type DeleteBucketMetricsConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketMetricsConfigurationRequest" type:"structure"`
// The name of the bucket containing the metrics configuration to delete.
//
@@ -10179,7 +10179,7 @@ func (s DeleteBucketOutput) GoString() string {
}
type DeleteBucketPolicyInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketPolicyRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10239,7 +10239,7 @@ func (s DeleteBucketPolicyOutput) GoString() string {
}
type DeleteBucketReplicationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketReplicationRequest" type:"structure"`
// The bucket name.
//
@@ -10304,7 +10304,7 @@ func (s DeleteBucketReplicationOutput) GoString() string {
}
type DeleteBucketTaggingInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketTaggingRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10364,7 +10364,7 @@ func (s DeleteBucketTaggingOutput) GoString() string {
}
type DeleteBucketWebsiteInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteBucketWebsiteRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10510,7 +10510,7 @@ func (s *DeleteMarkerReplication) SetStatus(v string) *DeleteMarkerReplication {
}
type DeleteObjectInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteObjectRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10656,7 +10656,7 @@ func (s *DeleteObjectOutput) SetVersionId(v string) *DeleteObjectOutput {
}
type DeleteObjectTaggingInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeleteObjectTaggingRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10749,7 +10749,7 @@ func (s *DeleteObjectTaggingOutput) SetVersionId(v string) *DeleteObjectTaggingO
}
type DeleteObjectsInput struct {
_ struct{} `type:"structure" payload:"Delete"`
_ struct{} `locationName:"DeleteObjectsRequest" type:"structure" payload:"Delete"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -10885,7 +10885,7 @@ func (s *DeleteObjectsOutput) SetRequestCharged(v string) *DeleteObjectsOutput {
}
type DeletePublicAccessBlockInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"DeletePublicAccessBlockRequest" type:"structure"`
// The Amazon S3 bucket whose PublicAccessBlock configuration you want to delete.
//
@@ -11341,7 +11341,7 @@ func (s *FilterRule) SetValue(v string) *FilterRule {
}
type GetBucketAccelerateConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketAccelerateConfigurationRequest" type:"structure"`
// Name of the bucket for which the accelerate configuration is retrieved.
//
@@ -11412,7 +11412,7 @@ func (s *GetBucketAccelerateConfigurationOutput) SetStatus(v string) *GetBucketA
}
type GetBucketAclInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketAclRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -11489,7 +11489,7 @@ func (s *GetBucketAclOutput) SetOwner(v *Owner) *GetBucketAclOutput {
}
type GetBucketAnalyticsConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketAnalyticsConfigurationRequest" type:"structure"`
// The name of the bucket from which an analytics configuration is retrieved.
//
@@ -11574,7 +11574,7 @@ func (s *GetBucketAnalyticsConfigurationOutput) SetAnalyticsConfiguration(v *Ana
}
type GetBucketCorsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketCorsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -11642,7 +11642,7 @@ func (s *GetBucketCorsOutput) SetCORSRules(v []*CORSRule) *GetBucketCorsOutput {
}
type GetBucketEncryptionInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketEncryptionRequest" type:"structure"`
// The name of the bucket from which the server-side encryption configuration
// is retrieved.
@@ -11714,7 +11714,7 @@ func (s *GetBucketEncryptionOutput) SetServerSideEncryptionConfiguration(v *Serv
}
type GetBucketInventoryConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketInventoryConfigurationRequest" type:"structure"`
// The name of the bucket containing the inventory configuration to retrieve.
//
@@ -11799,7 +11799,7 @@ func (s *GetBucketInventoryConfigurationOutput) SetInventoryConfiguration(v *Inv
}
type GetBucketLifecycleConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketLifecycleConfigurationRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -11867,7 +11867,7 @@ func (s *GetBucketLifecycleConfigurationOutput) SetRules(v []*LifecycleRule) *Ge
}
type GetBucketLifecycleInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketLifecycleRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -11935,7 +11935,7 @@ func (s *GetBucketLifecycleOutput) SetRules(v []*Rule) *GetBucketLifecycleOutput
}
type GetBucketLocationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketLocationRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12003,7 +12003,7 @@ func (s *GetBucketLocationOutput) SetLocationConstraint(v string) *GetBucketLoca
}
type GetBucketLoggingInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketLoggingRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12075,7 +12075,7 @@ func (s *GetBucketLoggingOutput) SetLoggingEnabled(v *LoggingEnabled) *GetBucket
}
type GetBucketMetricsConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketMetricsConfigurationRequest" type:"structure"`
// The name of the bucket containing the metrics configuration to retrieve.
//
@@ -12160,7 +12160,7 @@ func (s *GetBucketMetricsConfigurationOutput) SetMetricsConfiguration(v *Metrics
}
type GetBucketNotificationConfigurationRequest struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketNotificationConfigurationRequest" type:"structure"`
// Name of the bucket to get the notification configuration for.
//
@@ -12208,7 +12208,7 @@ func (s *GetBucketNotificationConfigurationRequest) getBucket() (v string) {
}
type GetBucketPolicyInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketPolicyRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12277,7 +12277,7 @@ func (s *GetBucketPolicyOutput) SetPolicy(v string) *GetBucketPolicyOutput {
}
type GetBucketPolicyStatusInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketPolicyStatusRequest" type:"structure"`
// The name of the Amazon S3 bucket whose policy status you want to retrieve.
//
@@ -12348,7 +12348,7 @@ func (s *GetBucketPolicyStatusOutput) SetPolicyStatus(v *PolicyStatus) *GetBucke
}
type GetBucketReplicationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketReplicationRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12418,7 +12418,7 @@ func (s *GetBucketReplicationOutput) SetReplicationConfiguration(v *ReplicationC
}
type GetBucketRequestPaymentInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketRequestPaymentRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12487,7 +12487,7 @@ func (s *GetBucketRequestPaymentOutput) SetPayer(v string) *GetBucketRequestPaym
}
type GetBucketTaggingInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketTaggingRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12556,7 +12556,7 @@ func (s *GetBucketTaggingOutput) SetTagSet(v []*Tag) *GetBucketTaggingOutput {
}
type GetBucketVersioningInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketVersioningRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12636,7 +12636,7 @@ func (s *GetBucketVersioningOutput) SetStatus(v string) *GetBucketVersioningOutp
}
type GetBucketWebsiteInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetBucketWebsiteRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12730,7 +12730,7 @@ func (s *GetBucketWebsiteOutput) SetRoutingRules(v []*RoutingRule) *GetBucketWeb
}
type GetObjectAclInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectAclRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -12853,7 +12853,7 @@ func (s *GetObjectAclOutput) SetRequestCharged(v string) *GetObjectAclOutput {
}
type GetObjectInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -13090,7 +13090,7 @@ func (s *GetObjectInput) SetVersionId(v string) *GetObjectInput {
}
type GetObjectLegalHoldInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectLegalHoldRequest" type:"structure"`
// The bucket containing the object whose Legal Hold status you want to retrieve.
//
@@ -13199,7 +13199,7 @@ func (s *GetObjectLegalHoldOutput) SetLegalHold(v *ObjectLockLegalHold) *GetObje
}
type GetObjectLockConfigurationInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectLockConfigurationRequest" type:"structure"`
// The bucket whose object lock configuration you want to retrieve.
//
@@ -13581,7 +13581,7 @@ func (s *GetObjectOutput) SetWebsiteRedirectLocation(v string) *GetObjectOutput
}
type GetObjectRetentionInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectRetentionRequest" type:"structure"`
// The bucket containing the object whose retention settings you want to retrieve.
//
@@ -13690,7 +13690,7 @@ func (s *GetObjectRetentionOutput) SetRetention(v *ObjectLockRetention) *GetObje
}
type GetObjectTaggingInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectTaggingRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -13790,7 +13790,7 @@ func (s *GetObjectTaggingOutput) SetVersionId(v string) *GetObjectTaggingOutput
}
type GetObjectTorrentInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetObjectTorrentRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -13895,7 +13895,7 @@ func (s *GetObjectTorrentOutput) SetRequestCharged(v string) *GetObjectTorrentOu
}
type GetPublicAccessBlockInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"GetPublicAccessBlockRequest" type:"structure"`
// The name of the Amazon S3 bucket whose PublicAccessBlock configuration you
// want to retrieve.
@@ -14126,7 +14126,7 @@ func (s *Grantee) SetURI(v string) *Grantee {
}
type HeadBucketInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"HeadBucketRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -14186,7 +14186,7 @@ func (s HeadBucketOutput) GoString() string {
}
type HeadObjectInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"HeadObjectRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -15661,7 +15661,7 @@ func (s *LifecycleRuleFilter) SetTag(v *Tag) *LifecycleRuleFilter {
}
type ListBucketAnalyticsConfigurationsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListBucketAnalyticsConfigurationsRequest" type:"structure"`
// The name of the bucket from which analytics configurations are retrieved.
//
@@ -15773,7 +15773,7 @@ func (s *ListBucketAnalyticsConfigurationsOutput) SetNextContinuationToken(v str
}
type ListBucketInventoryConfigurationsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListBucketInventoryConfigurationsRequest" type:"structure"`
// The name of the bucket containing the inventory configurations to retrieve.
//
@@ -15887,7 +15887,7 @@ func (s *ListBucketInventoryConfigurationsOutput) SetNextContinuationToken(v str
}
type ListBucketMetricsConfigurationsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListBucketMetricsConfigurationsRequest" type:"structure"`
// The name of the bucket containing the metrics configurations to retrieve.
//
@@ -16047,7 +16047,7 @@ func (s *ListBucketsOutput) SetOwner(v *Owner) *ListBucketsOutput {
}
type ListMultipartUploadsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListMultipartUploadsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -16291,7 +16291,7 @@ func (s *ListMultipartUploadsOutput) SetUploads(v []*MultipartUpload) *ListMulti
}
type ListObjectVersionsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListObjectVersionsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -16524,7 +16524,7 @@ func (s *ListObjectVersionsOutput) SetVersions(v []*ObjectVersion) *ListObjectVe
}
type ListObjectsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListObjectsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -16736,7 +16736,7 @@ func (s *ListObjectsOutput) SetPrefix(v string) *ListObjectsOutput {
}
type ListObjectsV2Input struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListObjectsV2Request" type:"structure"`
// Name of the bucket to list.
//
@@ -16997,7 +16997,7 @@ func (s *ListObjectsV2Output) SetStartAfter(v string) *ListObjectsV2Output {
}
type ListPartsInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"ListPartsRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -18622,7 +18622,7 @@ func (s *PublicAccessBlockConfiguration) SetRestrictPublicBuckets(v bool) *Publi
}
type PutBucketAccelerateConfigurationInput struct {
_ struct{} `type:"structure" payload:"AccelerateConfiguration"`
_ struct{} `locationName:"PutBucketAccelerateConfigurationRequest" type:"structure" payload:"AccelerateConfiguration"`
// Specifies the Accelerate Configuration you want to set for the bucket.
//
@@ -18698,7 +18698,7 @@ func (s PutBucketAccelerateConfigurationOutput) GoString() string {
}
type PutBucketAclInput struct {
_ struct{} `type:"structure" payload:"AccessControlPolicy"`
_ struct{} `locationName:"PutBucketAclRequest" type:"structure" payload:"AccessControlPolicy"`
// The canned ACL to apply to the bucket.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"BucketCannedACL"`
@@ -18827,7 +18827,7 @@ func (s PutBucketAclOutput) GoString() string {
}
type PutBucketAnalyticsConfigurationInput struct {
_ struct{} `type:"structure" payload:"AnalyticsConfiguration"`
_ struct{} `locationName:"PutBucketAnalyticsConfigurationRequest" type:"structure" payload:"AnalyticsConfiguration"`
// The configuration and any analyses for the analytics filter.
//
@@ -18922,7 +18922,7 @@ func (s PutBucketAnalyticsConfigurationOutput) GoString() string {
}
type PutBucketCorsInput struct {
_ struct{} `type:"structure" payload:"CORSConfiguration"`
_ struct{} `locationName:"PutBucketCorsRequest" type:"structure" payload:"CORSConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19004,7 +19004,7 @@ func (s PutBucketCorsOutput) GoString() string {
}
type PutBucketEncryptionInput struct {
_ struct{} `type:"structure" payload:"ServerSideEncryptionConfiguration"`
_ struct{} `locationName:"PutBucketEncryptionRequest" type:"structure" payload:"ServerSideEncryptionConfiguration"`
// Specifies default encryption for a bucket using server-side encryption with
// Amazon S3-managed keys (SSE-S3) or AWS KMS-managed keys (SSE-KMS). For information
@@ -19089,7 +19089,7 @@ func (s PutBucketEncryptionOutput) GoString() string {
}
type PutBucketInventoryConfigurationInput struct {
_ struct{} `type:"structure" payload:"InventoryConfiguration"`
_ struct{} `locationName:"PutBucketInventoryConfigurationRequest" type:"structure" payload:"InventoryConfiguration"`
// The name of the bucket where the inventory configuration will be stored.
//
@@ -19184,7 +19184,7 @@ func (s PutBucketInventoryConfigurationOutput) GoString() string {
}
type PutBucketLifecycleConfigurationInput struct {
_ struct{} `type:"structure" payload:"LifecycleConfiguration"`
_ struct{} `locationName:"PutBucketLifecycleConfigurationRequest" type:"structure" payload:"LifecycleConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19260,7 +19260,7 @@ func (s PutBucketLifecycleConfigurationOutput) GoString() string {
}
type PutBucketLifecycleInput struct {
_ struct{} `type:"structure" payload:"LifecycleConfiguration"`
_ struct{} `locationName:"PutBucketLifecycleRequest" type:"structure" payload:"LifecycleConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19333,7 +19333,7 @@ func (s PutBucketLifecycleOutput) GoString() string {
}
type PutBucketLoggingInput struct {
_ struct{} `type:"structure" payload:"BucketLoggingStatus"`
_ struct{} `locationName:"PutBucketLoggingRequest" type:"structure" payload:"BucketLoggingStatus"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19410,7 +19410,7 @@ func (s PutBucketLoggingOutput) GoString() string {
}
type PutBucketMetricsConfigurationInput struct {
_ struct{} `type:"structure" payload:"MetricsConfiguration"`
_ struct{} `locationName:"PutBucketMetricsConfigurationRequest" type:"structure" payload:"MetricsConfiguration"`
// The name of the bucket for which the metrics configuration is set.
//
@@ -19505,7 +19505,7 @@ func (s PutBucketMetricsConfigurationOutput) GoString() string {
}
type PutBucketNotificationConfigurationInput struct {
_ struct{} `type:"structure" payload:"NotificationConfiguration"`
_ struct{} `locationName:"PutBucketNotificationConfigurationRequest" type:"structure" payload:"NotificationConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19585,7 +19585,7 @@ func (s PutBucketNotificationConfigurationOutput) GoString() string {
}
type PutBucketNotificationInput struct {
_ struct{} `type:"structure" payload:"NotificationConfiguration"`
_ struct{} `locationName:"PutBucketNotificationRequest" type:"structure" payload:"NotificationConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19657,7 +19657,7 @@ func (s PutBucketNotificationOutput) GoString() string {
}
type PutBucketPolicyInput struct {
_ struct{} `type:"structure" payload:"Policy"`
_ struct{} `locationName:"PutBucketPolicyRequest" type:"structure" payload:"Policy"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19741,7 +19741,7 @@ func (s PutBucketPolicyOutput) GoString() string {
}
type PutBucketReplicationInput struct {
_ struct{} `type:"structure" payload:"ReplicationConfiguration"`
_ struct{} `locationName:"PutBucketReplicationRequest" type:"structure" payload:"ReplicationConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19830,7 +19830,7 @@ func (s PutBucketReplicationOutput) GoString() string {
}
type PutBucketRequestPaymentInput struct {
_ struct{} `type:"structure" payload:"RequestPaymentConfiguration"`
_ struct{} `locationName:"PutBucketRequestPaymentRequest" type:"structure" payload:"RequestPaymentConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19907,7 +19907,7 @@ func (s PutBucketRequestPaymentOutput) GoString() string {
}
type PutBucketTaggingInput struct {
_ struct{} `type:"structure" payload:"Tagging"`
_ struct{} `locationName:"PutBucketTaggingRequest" type:"structure" payload:"Tagging"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -19984,7 +19984,7 @@ func (s PutBucketTaggingOutput) GoString() string {
}
type PutBucketVersioningInput struct {
_ struct{} `type:"structure" payload:"VersioningConfiguration"`
_ struct{} `locationName:"PutBucketVersioningRequest" type:"structure" payload:"VersioningConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -20070,7 +20070,7 @@ func (s PutBucketVersioningOutput) GoString() string {
}
type PutBucketWebsiteInput struct {
_ struct{} `type:"structure" payload:"WebsiteConfiguration"`
_ struct{} `locationName:"PutBucketWebsiteRequest" type:"structure" payload:"WebsiteConfiguration"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -20149,7 +20149,7 @@ func (s PutBucketWebsiteOutput) GoString() string {
}
type PutObjectAclInput struct {
_ struct{} `type:"structure" payload:"AccessControlPolicy"`
_ struct{} `locationName:"PutObjectAclRequest" type:"structure" payload:"AccessControlPolicy"`
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"ObjectCannedACL"`
@@ -20324,7 +20324,7 @@ func (s *PutObjectAclOutput) SetRequestCharged(v string) *PutObjectAclOutput {
}
type PutObjectInput struct {
_ struct{} `type:"structure" payload:"Body"`
_ struct{} `locationName:"PutObjectRequest" type:"structure" payload:"Body"`
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"ObjectCannedACL"`
@@ -20671,7 +20671,7 @@ func (s *PutObjectInput) SetWebsiteRedirectLocation(v string) *PutObjectInput {
}
type PutObjectLegalHoldInput struct {
_ struct{} `type:"structure" payload:"LegalHold"`
_ struct{} `locationName:"PutObjectLegalHoldRequest" type:"structure" payload:"LegalHold"`
// The bucket containing the object that you want to place a Legal Hold on.
//
@@ -20791,7 +20791,7 @@ func (s *PutObjectLegalHoldOutput) SetRequestCharged(v string) *PutObjectLegalHo
}
type PutObjectLockConfigurationInput struct {
_ struct{} `type:"structure" payload:"ObjectLockConfiguration"`
_ struct{} `locationName:"PutObjectLockConfigurationRequest" type:"structure" payload:"ObjectLockConfiguration"`
// The bucket whose object lock configuration you want to create or replace.
//
@@ -20998,7 +20998,7 @@ func (s *PutObjectOutput) SetVersionId(v string) *PutObjectOutput {
}
type PutObjectRetentionInput struct {
_ struct{} `type:"structure" payload:"Retention"`
_ struct{} `locationName:"PutObjectRetentionRequest" type:"structure" payload:"Retention"`
// The bucket that contains the object you want to apply this Object Retention
// configuration to.
@@ -21129,7 +21129,7 @@ func (s *PutObjectRetentionOutput) SetRequestCharged(v string) *PutObjectRetenti
}
type PutObjectTaggingInput struct {
_ struct{} `type:"structure" payload:"Tagging"`
_ struct{} `locationName:"PutObjectTaggingRequest" type:"structure" payload:"Tagging"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -21237,7 +21237,7 @@ func (s *PutObjectTaggingOutput) SetVersionId(v string) *PutObjectTaggingOutput
}
type PutPublicAccessBlockInput struct {
_ struct{} `type:"structure" payload:"PublicAccessBlockConfiguration"`
_ struct{} `locationName:"PutPublicAccessBlockRequest" type:"structure" payload:"PublicAccessBlockConfiguration"`
// The name of the Amazon S3 bucket whose PublicAccessBlock configuration you
// want to set.
@@ -21999,7 +21999,7 @@ func (s *RequestProgress) SetEnabled(v bool) *RequestProgress {
}
type RestoreObjectInput struct {
_ struct{} `type:"structure" payload:"RestoreRequest"`
_ struct{} `locationName:"RestoreObjectRequest" type:"structure" payload:"RestoreRequest"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -23715,7 +23715,7 @@ func (s *Transition) SetStorageClass(v string) *Transition {
}
type UploadPartCopyInput struct {
_ struct{} `type:"structure"`
_ struct{} `locationName:"UploadPartCopyRequest" type:"structure"`
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
@@ -24045,7 +24045,7 @@ func (s *UploadPartCopyOutput) SetServerSideEncryption(v string) *UploadPartCopy
}
type UploadPartInput struct {
_ struct{} `type:"structure" payload:"Body"`
_ struct{} `locationName:"UploadPartRequest" type:"structure" payload:"Body"`
// Object data.
Body io.ReadSeeker `type:"blob"`
-4
View File
@@ -1,4 +0,0 @@
[568].out
_go*
_test*
_obj
-14
View File
@@ -1,14 +0,0 @@
FROM golang:1.12
# Clone and complie a riscv compatible version of the go compiler.
RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
# riscvdev branch HEAD as of 2019-06-29.
RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
ENV GOROOT=/riscv-go
# Make sure we compile.
WORKDIR pty
ADD . .
RUN GOOS=linux GOARCH=riscv go build
-23
View File
@@ -1,23 +0,0 @@
Copyright (c) 2011 Keith Rarick
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-100
View File
@@ -1,100 +0,0 @@
# pty
Pty is a Go package for using unix pseudo-terminals.
## Install
go get github.com/creack/pty
## Example
### Command
```go
package main
import (
"github.com/creack/pty"
"io"
"os"
"os/exec"
)
func main() {
c := exec.Command("grep", "--color=auto", "bar")
f, err := pty.Start(c)
if err != nil {
panic(err)
}
go func() {
f.Write([]byte("foo\n"))
f.Write([]byte("bar\n"))
f.Write([]byte("baz\n"))
f.Write([]byte{4}) // EOT
}()
io.Copy(os.Stdout, f)
}
```
### Shell
```go
package main
import (
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/creack/pty"
"golang.org/x/crypto/ssh/terminal"
)
func test() error {
// Create arbitrary command.
c := exec.Command("bash")
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
// Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, ptmx)
return nil
}
func main() {
if err := test(); err != nil {
log.Fatal(err)
}
}
```
-16
View File
@@ -1,16 +0,0 @@
// Package pty provides functions for working with Unix terminals.
package pty
import (
"errors"
"os"
)
// ErrUnsupported is returned if a function is not
// available on the current platform.
var ErrUnsupported = errors.New("unsupported")
// Opens a pty and its corresponding tty.
func Open() (pty, tty *os.File, err error) {
return open()
}
-13
View File
@@ -1,13 +0,0 @@
// +build !windows,!solaris
package pty
import "syscall"
func ioctl(fd, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if e != 0 {
return e
}
return nil
}
-39
View File
@@ -1,39 +0,0 @@
// +build darwin dragonfly freebsd netbsd openbsd
package pty
// from <sys/ioccom.h>
const (
_IOC_VOID uintptr = 0x20000000
_IOC_OUT uintptr = 0x40000000
_IOC_IN uintptr = 0x80000000
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
_IOC_PARAM_SHIFT = 13
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
)
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
return (ioctl >> 16) & _IOC_PARAM_MASK
}
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
}
func _IO(group byte, ioctl_num uintptr) uintptr {
return _IOC(_IOC_VOID, group, ioctl_num, 0)
}
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
}
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN, group, ioctl_num, param_len)
}
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
}
-30
View File
@@ -1,30 +0,0 @@
package pty
import (
"golang.org/x/sys/unix"
"unsafe"
)
const (
// see /usr/include/sys/stropts.h
I_PUSH = uintptr((int32('S')<<8 | 002))
I_STR = uintptr((int32('S')<<8 | 010))
I_FIND = uintptr((int32('S')<<8 | 013))
// see /usr/include/sys/ptms.h
ISPTM = (int32('P') << 8) | 1
UNLKPT = (int32('P') << 8) | 2
PTSSTTY = (int32('P') << 8) | 3
ZONEPT = (int32('P') << 8) | 4
OWNERPT = (int32('P') << 8) | 5
)
type strioctl struct {
ic_cmd int32
ic_timout int32
ic_len int32
ic_dp unsafe.Pointer
}
func ioctl(fd, cmd, ptr uintptr) error {
return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr))
}
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env bash
GOOSARCH="${GOOS}_${GOARCH}"
case "$GOOSARCH" in
_* | *_ | _)
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
exit 1
;;
esac
GODEFS="go tool cgo -godefs"
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
case $GOOS in
freebsd|dragonfly|openbsd)
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
;;
esac
-65
View File
@@ -1,65 +0,0 @@
package pty
import (
"errors"
"os"
"syscall"
"unsafe"
)
func open() (pty, tty *os.File, err error) {
pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, nil, err
}
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
return nil, nil, err
}
t, err := os.OpenFile(sname, os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func ptsname(f *os.File) (string, error) {
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
if err != nil {
return "", err
}
for i, c := range n {
if c == 0 {
return string(n[:i]), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}
func grantpt(f *os.File) error {
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
}
func unlockpt(f *os.File) error {
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
}
-80
View File
@@ -1,80 +0,0 @@
package pty
import (
"errors"
"os"
"strings"
"syscall"
"unsafe"
)
// same code as pty_darwin.go
func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
// In case of error after this point, make sure we close the ptmx fd.
defer func() {
if err != nil {
_ = p.Close() // Best effort.
}
}()
sname, err := ptsname(p)
if err != nil {
return nil, nil, err
}
if err := grantpt(p); err != nil {
return nil, nil, err
}
if err := unlockpt(p); err != nil {
return nil, nil, err
}
t, err := os.OpenFile(sname, os.O_RDWR, 0)
if err != nil {
return nil, nil, err
}
return p, t, nil
}
func grantpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func unlockpt(f *os.File) error {
_, err := isptmaster(f.Fd())
return err
}
func isptmaster(fd uintptr) (bool, error) {
err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
return err == nil, err
}
var (
emptyFiodgnameArg fiodgnameArg
ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) {
name := make([]byte, _C_SPECNAMELEN)
fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
if err != nil {
return "", err
}
for i, c := range name {
if c == 0 {
s := "/dev/" + string(name[:i])
return strings.Replace(s, "ptm", "pts", -1), nil
}
}
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}

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