diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 33259856..6ce913b4 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -9,10 +9,10 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Test run: make test diff --git a/.teamcity/build-macos.sh b/.teamcity/build-macos.sh index 7f9fc3df..58cd3308 100755 --- a/.teamcity/build-macos.sh +++ b/.teamcity/build-macos.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -exo pipefail + if [[ "$(uname)" != "Darwin" ]] ; then echo "This should be run on macOS" exit 1 @@ -33,7 +35,9 @@ if [[ ! -z "$CFD_CODE_SIGN_KEY" ]]; then if [[ ! -z "$CFD_CODE_SIGN_PASS" ]]; then # write private key to disk and then import it keychain echo -n -e ${CFD_CODE_SIGN_KEY} | base64 -D > ${CODE_SIGN_PRIV} - out=$(security import ${CODE_SIGN_PRIV} -A -P "${CFD_CODE_SIGN_PASS}" 2>&1) + # we set || true here and for every `security import invoke` because the "duplicate SecKeychainItemImport" error + # will cause set -e to exit 1. It is okay we do this because we deliberately handle this error in the lines below. + out=$(security import ${CODE_SIGN_PRIV} -A -P "${CFD_CODE_SIGN_PASS}" 2>&1) || true exitcode=$? if [ -n "$out" ]; then if [ $exitcode -eq 0 ]; then @@ -53,7 +57,7 @@ fi if [[ ! -z "$CFD_CODE_SIGN_CERT" ]]; then # write certificate to disk and then import it keychain echo -n -e ${CFD_CODE_SIGN_CERT} | base64 -D > ${CODE_SIGN_CERT} - out1=$(security import ${CODE_SIGN_CERT} -A 2>&1) + out1=$(security import ${CODE_SIGN_CERT} -A 2>&1) || true exitcode1=$? if [ -n "$out1" ]; then if [ $exitcode1 -eq 0 ]; then @@ -75,7 +79,7 @@ if [[ ! -z "$CFD_INSTALLER_KEY" ]]; then if [[ ! -z "$CFD_INSTALLER_PASS" ]]; then # write private key to disk and then import it into the keychain echo -n -e ${CFD_INSTALLER_KEY} | base64 -D > ${INSTALLER_PRIV} - out2=$(security import ${INSTALLER_PRIV} -A -P "${CFD_INSTALLER_PASS}" 2>&1) + out2=$(security import ${INSTALLER_PRIV} -A -P "${CFD_INSTALLER_PASS}" 2>&1) || true exitcode2=$? if [ -n "$out2" ]; then if [ $exitcode2 -eq 0 ]; then @@ -95,7 +99,7 @@ fi if [[ ! -z "$CFD_INSTALLER_CERT" ]]; then # write certificate to disk and then import it keychain echo -n -e ${CFD_INSTALLER_CERT} | base64 -D > ${INSTALLER_CERT} - out3=$(security import ${INSTALLER_CERT} -A 2>&1) + out3=$(security import ${INSTALLER_CERT} -A 2>&1) || true exitcode3=$? if [ -n "$out3" ]; then if [ $exitcode3 -eq 0 ]; then @@ -138,14 +142,10 @@ fi if [[ ! -z "$CODE_SIGN_NAME" ]]; then codesign -s "${CODE_SIGN_NAME}" -f -v --timestamp --options runtime ${BINARY_NAME} - # notarize the binary - if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then - zip "${BINARY_NAME}.zip" ${BINARY_NAME} - xcrun altool --notarize-app -f "${BINARY_NAME}.zip" -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID} - fi + # notarize the binary + # TODO: https://jira.cfdata.org/browse/TUN-5789 fi - # creating build directory rm -rf $TARGET_DIRECTORY mkdir "${TARGET_DIRECTORY}" @@ -169,10 +169,7 @@ if [[ ! -z "$PKG_SIGN_NAME" ]]; then ${PKGNAME} # notarize the package - if [[ ! -z "$CFD_NOTE_PASSWORD" ]]; then - xcrun altool --notarize-app -f ${PKGNAME} -t osx -u ${CFD_NOTE_USERNAME} -p ${CFD_NOTE_PASSWORD} --primary-bundle-id ${BUNDLE_ID} - xcrun stapler staple ${PKGNAME} - fi + # TODO: https://jira.cfdata.org/browse/TUN-5789 else pkgbuild --identifier com.cloudflare.${PRODUCT} \ --version ${VERSION} \ diff --git a/.teamcity/update-homebrew.sh b/.teamcity/update-homebrew.sh index 6450f0ef..f228b78a 100755 --- a/.teamcity/update-homebrew.sh +++ b/.teamcity/update-homebrew.sh @@ -3,7 +3,6 @@ set -euo pipefail FILENAME="${PWD}/artifacts/cloudflared-darwin-amd64.tgz" - if ! VERSION="$(git describe --tags --exact-match 2>/dev/null)" ; then echo "Skipping public release for an untagged commit." echo "##teamcity[buildStatus status='SUCCESS' text='Skipped due to lack of tag']" @@ -15,24 +14,24 @@ if [[ ! -f "$FILENAME" ]] ; then exit 1 fi -if [[ "${GITHUB_PRIVATE_KEY:-}" == "" ]] ; then - echo "Missing GITHUB_PRIVATE_KEY" +if [[ "${GITHUB_PRIVATE_KEY_B64:-}" == "" ]] ; then + echo "Missing GITHUB_PRIVATE_KEY_B64" exit 1 fi # upload to s3 bucket for use by Homebrew formula s3cmd \ - --acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \ + --acl-public --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \ put "$FILENAME" "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz" s3cmd \ - --acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \ + --acl-public --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \ cp "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz" "s3://cftunnel-docs/dl/cloudflared-stable-darwin-amd64.tgz" SHA256=$(sha256sum "$FILENAME" | cut -b1-64) # set up git (note that UserKnownHostsFile is an absolute path so we can cd wherever) mkdir -p tmp ssh-keyscan -t rsa github.com > tmp/github.txt -echo "$GITHUB_PRIVATE_KEY" > tmp/private.key +echo "$GITHUB_PRIVATE_KEY_B64" | base64 --decode > tmp/private.key chmod 0400 tmp/private.key export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=$PWD/tmp/github.txt -i $PWD/tmp/private.key -o IdentitiesOnly=yes" diff --git a/CHANGES.md b/CHANGES.md index 146703e6..8f193881 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,38 @@ +## 2023.4.1 +### New Features +- You can now stream your logs from your remote cloudflared to your local terminal with `cloudflared tail `. This new feature requires the remote cloudflared to be version 2023.4.1 or higher. + +## 2023.3.2 +### Notices +- Due to the nature of QuickTunnels (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/do-more-with-tunnels/trycloudflare/) and its intended usage for testing and experiment of Cloudflare Tunnels, starting from 2023.3.2, QuickTunnels only make a single connection to the edge. If users want to use Tunnels in a production environment, they should move to Named Tunnels instead. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/remote/#set-up-a-tunnel-remotely-dashboard-setup) + +## 2023.3.1 +### Breaking Change +- Running a tunnel without ingress rules defined in configuration file nor from the CLI flags will no longer provide a default ingress rule to localhost:8080 and instead will return HTTP response code 503 for all incoming HTTP requests. + +### Security Fixes +- Windows 32 bit machines MSI now defaults to Program Files to install cloudflared. (See CVE-2023-1314). The cloudflared client itself is unaffected. This just changes how the installer works on 32 bit windows machines. + +### Bug Fixes +- Fixed a bug that would cause running tunnel on Bastion mode and without ingress rules to crash. + +## 2023.2.2 +### Notices +- Legacy tunnels were officially deprecated on December 1, 2022. Starting with this version, cloudflared no longer supports connecting legacy tunnels. +- h2mux tunnel connection protocol is no longer supported. Any tunnels still configured to use this protocol will alert and use http2 tunnel protocol instead. We recommend using quic protocol for all tunnels going forward. + +## 2023.2.1 +### Bug fixes +- Fixed a bug in TCP connection proxy that could result in the connection being closed before all data was written. +- cloudflared now correctly aborts body write if connection to origin service fails after response headers were sent already. +- Fixed a bug introduced in the previous release where debug endpoints were removed. + +## 2022.12.0 +### Improvements +- cloudflared now attempts to try other edge addresses before falling back to a lower protocol. +- cloudflared tunnel no longer spins up a quick tunnel. The call has to be explicit and provide a --url flag. +- cloudflared will now randomly pick the first or second region to connect to instead of always connecting to region2 first. + ## 2022.9.0 ### New Features - cloudflared now rejects ingress rules with invalid http status codes for http_status. diff --git a/Makefile b/Makefile index 74fd858c..06fa0050 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,8 @@ else ifeq ($(LOCAL_OS),windows) TARGET_OS ?= windows else ifeq ($(LOCAL_OS),freebsd) TARGET_OS ?= freebsd +else ifeq ($(LOCAL_OS),openbsd) + TARGET_OS ?= openbsd else $(error This system's OS $(LOCAL_OS) isn't supported) endif @@ -108,6 +110,9 @@ else PACKAGE_ARCH := $(TARGET_ARCH) endif +#for FIPS compliance, FPM defaults to MD5. +RPM_DIGEST := --rpm-digest sha256 + .PHONY: all all: cloudflared test @@ -163,13 +168,13 @@ define build_package mkdir -p $(PACKAGE_DIR) cp cloudflared $(PACKAGE_DIR)/cloudflared cp cloudflared.1 $(PACKAGE_DIR)/cloudflared.1 - fakeroot fpm -C $(PACKAGE_DIR) -s dir -t $(1) \ + fpm -C $(PACKAGE_DIR) -s dir -t $(1) \ --description 'Cloudflare Tunnel daemon' \ --vendor 'Cloudflare' \ --license 'Apache License Version 2.0' \ --url 'https://github.com/cloudflare/cloudflared' \ -m 'Cloudflare ' \ - -a $(PACKAGE_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \ + -a $(PACKAGE_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(RPM_DIGEST) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \ cloudflared=$(INSTALL_BINDIR) cloudflared.1=$(INSTALL_MANDIR) endef diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 4a664149..9b618bae 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,116 @@ +2023.4.2 +- 2023-04-24 TUN-7133: Add sampling support for streaming logs +- 2023-04-21 TUN-7141: Add component tests for streaming logs +- 2023-04-21 TUN-7373: Streaming logs override for same actor +- 2023-04-20 TUN-7383: Bump requirements.txt +- 2023-04-19 TUN-7361: Add a label to override hostname +- 2023-04-19 TUN-7378: Remove RPC debug logs +- 2023-04-18 TUN-7360: Add Get Host Details handler in management service +- 2023-04-17 AUTH-3122 Verify that Access tokens are still valid in curl command +- 2023-04-17 TUN-7129: Categorize TCP logs for streaming logs +- 2023-04-17 TUN-7130: Categorize UDP logs for streaming logs +- 2023-04-10 AUTH-4887 Add aud parameter to token transfer url + +2023.4.1 +- 2023-04-13 TUN-7368: Report destination address for TCP requests in logs +- 2023-04-12 TUN-7134: Acquire token for cloudflared tail +- 2023-04-12 TUN-7131: Add cloudflared log event to connection messages and enable streaming logs +- 2023-04-11 TUN-7132 TUN-7136: Add filter support for streaming logs +- 2023-04-06 TUN-7354: Don't warn for empty ingress rules when using --token +- 2023-04-06 TUN-7128: Categorize logs from public hostname locations +- 2023-04-06 TUN-7351: Add streaming logs session ping and timeout +- 2023-04-06 TUN-7335: Fix cloudflared update not working in windows + +2023.4.0 +- 2023-04-07 TUN-7356: Bump golang.org/x/net package to 0.7.0 +- 2023-04-07 TUN-7357: Bump to go 1.19.6 +- 2023-04-06 TUN-7127: Disconnect logger level requirement for management +- 2023-04-05 TUN-7332: Remove legacy tunnel force flag +- 2023-04-05 TUN-7135: Add cloudflared tail +- 2023-04-04 Add suport for OpenBSD (#916) +- 2023-04-04 Fix typo (#918) +- 2023-04-04 TUN-7125: Add management streaming logs WebSocket protocol +- 2023-03-30 TUN-9999: Remove classic tunnel component tests +- 2023-03-30 TUN-7126: Add Management logger io.Writer +- 2023-03-29 TUN-7324: Add http.Hijacker to connection.ResponseWriter +- 2023-03-29 TUN-7333: Default features checkable at runtime across all packages +- 2023-03-21 TUN-7124: Add intercept ingress rule for management requests + +2023.3.1 +- 2023-03-13 TUN-7271: Return 503 status code when no ingress rules configured +- 2023-03-10 TUN-7272: Fix cloudflared returning non supported status service which breaks configuration migration +- 2023-03-09 TUN-7259: Add warning for missing ingress rules +- 2023-03-09 TUN-7268: Default to Program Files as location for win32 +- 2023-03-07 TUN-7252: Remove h2mux connection +- 2023-03-07 TUN-7253: Adopt http.ResponseWriter for connection.ResponseWriter +- 2023-03-06 TUN-7245: Add bastion flag to origin service check +- 2023-03-06 EDGESTORE-108: Remove deprecated s3v2 signature +- 2023-03-02 TUN-7226: Fixed a missed rename + +2023.3.0 +- 2023-03-01 GH-352: Add Tunnel CLI option "edge-bind-address" (#870) +- 2023-03-01 Fixed WIX template to allow MSI upgrades (#838) +- 2023-02-28 TUN-7213: Decode Base64 encoded key before writing it +- 2023-02-28 check.yaml: update actions to v3 (#876) +- 2023-02-27 TUN-7213: Debug homebrew-cloudflare build +- 2023-02-15 RTG-2476 Add qtls override for Go 1.20 + +2023.2.2 +- 2023-02-22 TUN-7197: Add connIndex tag to debug messages of incoming requests +- 2023-02-08 TUN-7167: Respect protocol overrides with --token +- 2023-02-06 TUN-7065: Remove classic tunnel creation +- 2023-02-06 TUN-6938: Force h2mux protocol to http2 for named tunnels +- 2023-02-06 TUN-6938: Provide QUIC as first in protocol list +- 2023-02-03 TUN-7158: Correct TCP tracing propagation +- 2023-02-01 TUN-7151: Update changes file with latest release notices + +2023.2.1 +- 2023-02-01 TUN-7065: Revert Ingress Rule check for named tunnel configurations +- 2023-02-01 Revert "TUN-7065: Revert Ingress Rule check for named tunnel configurations" +- 2023-02-01 Revert "TUN-7065: Remove classic tunnel creation" +2023.1.0 +- 2023-01-10 TUN-7064: RPM digests are now sha256 instead of md5sum +- 2023-01-04 RTG-2418 Update qtls +- 2022-12-24 TUN-7057: Remove dependency github.com/gorilla/mux +- 2022-12-24 TUN-6724: Migrate to sentry-go from raven-go + +2022.12.1 +- 2022-12-20 TUN-7021: Fix proxy-dns not starting when cloudflared tunnel is run +- 2022-12-15 TUN-7010: Changelog for release 2022.12.0 + +2022.12.0 +- 2022-12-14 TUN-6999: cloudflared should attempt other edge addresses before falling back on protocol +- 2022-12-13 TUN-7004: Dont show local config dirs for remotely configured tuns +- 2022-12-12 TUN-7003: Tempoarily disable erroneous notarize-app +- 2022-12-12 TUN-7003: Add back a missing fi +- 2022-12-07 TUN-7000: Reduce metric cardinality of closedConnections metric by removing error as tag +- 2022-12-07 TUN-6994: Improve logging config file not found +- 2022-12-07 TUN-7002: Randomise first region selection +- 2022-12-07 TUN-6995: Disable quick-tunnels spin up by default +- 2022-12-05 TUN-6984: Add bash set x to improve visibility during builds +- 2022-12-05 TUN-6984: [CI] Ignore security import errors for code_sigining +- 2022-12-05 TUN-6984: [CI] Don't fail on unset. +- 2022-11-30 TUN-6984: Set euo pipefile for homebrew builds + +2022.11.1 +- 2022-11-29 TUN-6981: We should close UDP socket if failed to connecto to edge +- 2022-11-25 CUSTESC-23757: Fix a bug where a wildcard ingress rule would match an host without starting with a dot +- 2022-11-24 TUN-6970: Print newline when printing tunnel token +- 2022-11-22 TUN-6963: Refactor Metrics service setup +2022.11.0 +- 2022-11-16 Revert "TUN-6935: Cloudflared should use APIToken instead of serviceKey" +- 2022-11-16 TUN-6929: Use same protocol for other connections as first one +- 2022-11-14 TUN-6941: Reduce log level to debug when failing to proxy ICMP reply +- 2022-11-14 TUN-6935: Cloudflared should use APIToken instead of serviceKey +- 2022-11-14 TUN-6935: Cloudflared should use APIToken instead of serviceKey +- 2022-11-11 TUN-6937: Bump golang.org/x/* packages to new release tags +- 2022-11-10 ZTC-234: macOS tests +- 2022-11-09 TUN-6927: Refactor validate access configuration to allow empty audTags only +- 2022-11-08 ZTC-234: Replace ICMP funnels when ingress connection changes +- 2022-11-04 TUN-6917: Bump go to 1.19.3 +- 2022-11-02 Issue #574: Better ssh config for short-lived cert (#763) +- 2022-10-28 TUN-6898: Fix bug handling IPv6 based ingresses with missing port +- 2022-10-28 TUN-6898: Refactor addPortIfMissing 2022.10.3 - 2022-10-24 TUN-6871: Add default feature to cloudflared to support EOF on QUIC connections - 2022-10-19 TUN-6876: Fix flaky TestTraceICMPRouterEcho by taking account request span can return before reply diff --git a/carrier/websocket.go b/carrier/websocket.go index a26e0737..31075210 100644 --- a/carrier/websocket.go +++ b/carrier/websocket.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/websocket" "github.com/rs/zerolog" + "github.com/cloudflare/cloudflared/stream" "github.com/cloudflare/cloudflared/token" cfwebsocket "github.com/cloudflare/cloudflared/websocket" ) @@ -37,7 +38,7 @@ func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) erro } defer wsConn.Close() - cfwebsocket.Stream(wsConn, conn, ws.log) + stream.Pipe(wsConn, conn, ws.log) return nil } diff --git a/certutil/certutil.go b/certutil/certutil.go deleted file mode 100644 index 0e90ed4b..00000000 --- a/certutil/certutil.go +++ /dev/null @@ -1,94 +0,0 @@ -package certutil - -import ( - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "strings" -) - -type namedTunnelToken struct { - ZoneID string `json:"zoneID"` - AccountID string `json:"accountID"` - ServiceKey string `json:"serviceKey"` -} - -type OriginCert struct { - PrivateKey interface{} - Cert *x509.Certificate - ZoneID string - ServiceKey string - AccountID string -} - -func DecodeOriginCert(blocks []byte) (*OriginCert, error) { - if len(blocks) == 0 { - return nil, fmt.Errorf("Cannot decode empty certificate") - } - originCert := OriginCert{} - block, rest := pem.Decode(blocks) - for { - if block == nil { - break - } - switch block.Type { - case "PRIVATE KEY": - if originCert.PrivateKey != nil { - return nil, fmt.Errorf("Found multiple private key in the certificate") - } - // RSA private key - privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("Cannot parse private key") - } - originCert.PrivateKey = privateKey - case "CERTIFICATE": - if originCert.Cert != nil { - return nil, fmt.Errorf("Found multiple certificates in the certificate") - } - cert, err := x509.ParseCertificates(block.Bytes) - if err != nil { - return nil, fmt.Errorf("Cannot parse certificate") - } else if len(cert) > 1 { - return nil, fmt.Errorf("Found multiple certificates in the certificate") - } - originCert.Cert = cert[0] - case "WARP TOKEN", "ARGO TUNNEL TOKEN": - if originCert.ZoneID != "" || originCert.ServiceKey != "" { - return nil, fmt.Errorf("Found multiple tokens in the certificate") - } - // The token is a string, - // Try the newer JSON format - ntt := namedTunnelToken{} - if err := json.Unmarshal(block.Bytes, &ntt); err == nil { - originCert.ZoneID = ntt.ZoneID - originCert.ServiceKey = ntt.ServiceKey - originCert.AccountID = ntt.AccountID - } else { - // Try the older format, where the zoneID and service key are separated by - // a new line character - token := string(block.Bytes) - s := strings.Split(token, "\n") - if len(s) != 2 { - return nil, fmt.Errorf("Cannot parse token") - } - originCert.ZoneID = s[0] - originCert.ServiceKey = s[1] - } - default: - return nil, fmt.Errorf("Unknown block %s in the certificate", block.Type) - } - block, rest = pem.Decode(rest) - } - - if originCert.PrivateKey == nil { - return nil, fmt.Errorf("Missing private key in the certificate") - } else if originCert.Cert == nil { - return nil, fmt.Errorf("Missing certificate in the certificate") - } else if originCert.ZoneID == "" || originCert.ServiceKey == "" { - return nil, fmt.Errorf("Missing token in the certificate") - } - - return &originCert, nil -} diff --git a/certutil/certutil_test.go b/certutil/certutil_test.go deleted file mode 100644 index 26b13f5d..00000000 --- a/certutil/certutil_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package certutil - -import ( - "fmt" - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLoadOriginCert(t *testing.T) { - cert, err := DecodeOriginCert([]byte{}) - assert.Equal(t, fmt.Errorf("Cannot decode empty certificate"), err) - assert.Nil(t, cert) - - blocks, err := ioutil.ReadFile("test-cert-no-key.pem") - assert.Nil(t, err) - cert, err = DecodeOriginCert(blocks) - assert.Equal(t, fmt.Errorf("Missing private key in the certificate"), err) - assert.Nil(t, cert) - - blocks, err = ioutil.ReadFile("test-cert-two-certificates.pem") - assert.Nil(t, err) - cert, err = DecodeOriginCert(blocks) - assert.Equal(t, fmt.Errorf("Found multiple certificates in the certificate"), err) - assert.Nil(t, cert) - - blocks, err = ioutil.ReadFile("test-cert-unknown-block.pem") - assert.Nil(t, err) - cert, err = DecodeOriginCert(blocks) - assert.Equal(t, fmt.Errorf("Unknown block RSA PRIVATE KEY in the certificate"), err) - assert.Nil(t, cert) - - blocks, err = ioutil.ReadFile("test-cert.pem") - assert.Nil(t, err) - cert, err = DecodeOriginCert(blocks) - assert.Nil(t, err) - assert.NotNil(t, cert) - assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID) - key := "v1.0-58bd4f9e28f7b3c28e05a35ff3e80ab4fd9644ef3fece537eb0d12e2e9258217-183442fbb0bbdb3e571558fec9b5589ebd77aafc87498ee3f09f64a4ad79ffe8791edbae08b36c1d8f1d70a8670de56922dff92b15d214a524f4ebfa1958859e-7ce80f79921312a6022c5d25e2d380f82ceaefe3fbdc43dd13b080e3ef1e26f7" - assert.Equal(t, key, cert.ServiceKey) -} - -func TestNewlineArgoTunnelToken(t *testing.T) { - ArgoTunnelTokenTest(t, "test-argo-tunnel-cert.pem") -} - -func TestJSONArgoTunnelToken(t *testing.T) { - // The given cert's Argo Tunnel Token was generated by base64 encoding this JSON: - // { - // "zoneID": "7b0a4d77dfb881c1a3b7d61ea9443e19", - // "serviceKey": "test-service-key", - // "accountID": "abcdabcdabcdabcd1234567890abcdef" - // } - ArgoTunnelTokenTest(t, "test-argo-tunnel-cert-json.pem") -} - -func ArgoTunnelTokenTest(t *testing.T, path string) { - blocks, err := ioutil.ReadFile(path) - assert.Nil(t, err) - cert, err := DecodeOriginCert(blocks) - assert.Nil(t, err) - assert.NotNil(t, cert) - assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID) - key := "test-service-key" - assert.Equal(t, key, cert.ServiceKey) -} diff --git a/certutil/test-cert-no-key.pem b/certutil/test-cert-no-key.pem deleted file mode 100644 index aae69fc9..00000000 --- a/certutil/test-cert-no-key.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw -gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD -VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv -cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p -YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx -MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL -ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln -aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf -GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng -6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx -tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX -PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ -AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl -HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq -zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw -RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs -YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK -YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh -cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj -K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ -x+Yo/cL8fGfVpPt4UM8= ------END CERTIFICATE----- ------BEGIN WARP TOKEN----- -N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4 -ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5 -MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4 -NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2 -NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5 -OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz -ZWYxZTI2Zjc= ------END WARP TOKEN----- diff --git a/certutil/test-cert-two-certificates.pem b/certutil/test-cert-two-certificates.pem deleted file mode 100644 index 214e2f8e..00000000 --- a/certutil/test-cert-two-certificates.pem +++ /dev/null @@ -1,85 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3 -sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg -1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt -z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX -6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS -x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja -1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB -IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D -xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy -sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi -2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm -sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX -Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF -AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj -m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE -QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to -P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8 -pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs -G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0 -6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0 -LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan -OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr -W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd -M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR -y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz -uQicoQq3yzeQh20wtrtaXzTNmA== ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw -gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD -VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv -cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p -YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx -MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL -ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln -aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf -GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng -6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx -tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX -PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ -AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl -HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq -zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw -RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs -YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK -YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh -cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj -K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ -x+Yo/cL8fGfVpPt4UM8= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw -gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD -VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv -cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p -YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx -MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL -ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln -aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf -GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng -6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx -tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX -PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ -AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl -HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq -zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw -RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs -YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK -YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh -cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj -K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ -x+Yo/cL8fGfVpPt4UM8= ------END CERTIFICATE----- ------BEGIN WARP TOKEN----- -N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4 -ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5 -MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4 -NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2 -NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5 -OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz -ZWYxZTI2Zjc= ------END WARP TOKEN----- diff --git a/certutil/test-cert.pem b/certutil/test-cert.pem deleted file mode 100644 index 4d1c9f89..00000000 --- a/certutil/test-cert.pem +++ /dev/null @@ -1,61 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3 -sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg -1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt -z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX -6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS -x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja -1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB -IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D -xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy -sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi -2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm -sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX -Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF -AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj -m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE -QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to -P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8 -pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs -G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0 -6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0 -LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan -OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr -W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd -M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR -y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz -uQicoQq3yzeQh20wtrtaXzTNmA== ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw -gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD -VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv -cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p -YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx -MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL -ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln -aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf -GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng -6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx -tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX -PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ -AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl -HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq -zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw -RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs -YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK -YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh -cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj -K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ -x+Yo/cL8fGfVpPt4UM8= ------END CERTIFICATE----- ------BEGIN WARP TOKEN----- -N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4 -ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5 -MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4 -NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2 -NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5 -OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz -ZWYxZTI2Zjc= ------END WARP TOKEN----- diff --git a/cfapi/base_client.go b/cfapi/base_client.go index 48b349c3..92544071 100644 --- a/cfapi/base_client.go +++ b/cfapi/base_client.go @@ -104,7 +104,7 @@ func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) ( if bodyReader != nil { req.Header.Set("Content-Type", jsonContentType) } - req.Header.Add("X-Auth-User-Service-Key", r.authToken) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.authToken)) req.Header.Add("Accept", "application/json;version=1") return r.client.Do(req) } diff --git a/cfapi/client.go b/cfapi/client.go index fa5e7cb2..f8c2a734 100644 --- a/cfapi/client.go +++ b/cfapi/client.go @@ -8,6 +8,7 @@ type TunnelClient interface { CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) GetTunnelToken(tunnelID uuid.UUID) (string, error) + GetManagementToken(tunnelID uuid.UUID) (string, error) DeleteTunnel(tunnelID uuid.UUID) error ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) @@ -28,7 +29,7 @@ type IPRouteClient interface { type VnetClient interface { CreateVirtualNetwork(newVnet NewVirtualNetwork) (VirtualNetwork, error) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, error) - DeleteVirtualNetwork(id uuid.UUID) error + DeleteVirtualNetwork(id uuid.UUID, force bool) error UpdateVirtualNetwork(id uuid.UUID, updates UpdateVirtualNetwork) error } diff --git a/cfapi/tunnel.go b/cfapi/tunnel.go index 87caf230..fa6f8f33 100644 --- a/cfapi/tunnel.go +++ b/cfapi/tunnel.go @@ -50,6 +50,10 @@ type newTunnel struct { TunnelSecret []byte `json:"tunnel_secret"` } +type managementRequest struct { + Resources []string `json:"resources"` +} + type CleanupParams struct { queryParams url.Values } @@ -133,6 +137,28 @@ func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error return "", r.statusCodeToError("get tunnel token", resp) } +func (r *RESTClient) GetManagementToken(tunnelID uuid.UUID) (token string, err error) { + endpoint := r.baseEndpoints.accountLevel + endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/management", tunnelID)) + + body := &managementRequest{ + Resources: []string{"logs"}, + } + + resp, err := r.sendRequest("POST", endpoint, body) + if err != nil { + return "", errors.Wrap(err, "REST request failed") + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + err = parseResponse(resp.Body, &token) + return token, err + } + + return "", r.statusCodeToError("get tunnel token", resp) +} + func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error { endpoint := r.baseEndpoints.accountLevel endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID)) diff --git a/cfapi/virtual_network.go b/cfapi/virtual_network.go index e346678a..fce5d504 100644 --- a/cfapi/virtual_network.go +++ b/cfapi/virtual_network.go @@ -80,9 +80,16 @@ func (r *RESTClient) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, return nil, r.statusCodeToError("list virtual networks", resp) } -func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID) error { +func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID, force bool) error { endpoint := r.baseEndpoints.accountVnets endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String())) + + queryParams := url.Values{} + if force { + queryParams.Set("force", strconv.FormatBool(force)) + } + endpoint.RawQuery = queryParams.Encode() + resp, err := r.sendRequest("DELETE", endpoint, nil) if err != nil { return errors.Wrap(err, "REST request failed") diff --git a/cfsetup.yaml b/cfsetup.yaml index 89d4512b..45177dcb 100644 --- a/cfsetup.yaml +++ b/cfsetup.yaml @@ -1,9 +1,9 @@ -pinned_go: &pinned_go go=1.19.3-1 -pinned_go_fips: &pinned_go_fips go-boring=1.19.3-1 +pinned_go: &pinned_go go=1.19.6-1 +pinned_go_fips: &pinned_go_fips go-boring=1.19.6-1 build_dir: &build_dir /cfsetup_build default-flavor: bullseye -stretch: &stretch +buster: &buster build: build_dir: *build_dir builddeps: &build_deps @@ -268,6 +268,5 @@ stretch: &stretch - export GOARCH=amd64 - make publish-cloudflared-junos -buster: *stretch -bullseye: *stretch -bookworm: *stretch +bullseye: *buster +bookworm: *buster diff --git a/cloudflared.wxs b/cloudflared.wxs index b53a74bd..857d3013 100644 --- a/cloudflared.wxs +++ b/cloudflared.wxs @@ -1,62 +1,64 @@ - - - + - + + + - + - + - - + - + - - - - - NOT NEWERVERSIONDETECTED + - - - - - - - + + + + + NOT NEWERVERSIONDETECTED + + + + + + + + + + + + + - - - - - - - - - + + + + diff --git a/cmd/cloudflared/access/cmd.go b/cmd/cloudflared/access/cmd.go index 8d3e6a88..72083ed6 100644 --- a/cmd/cloudflared/access/cmd.go +++ b/cmd/cloudflared/access/cmd.go @@ -12,7 +12,7 @@ import ( "text/template" "time" - "github.com/getsentry/raven-go" + "github.com/getsentry/sentry-go" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/urfave/cli/v2" @@ -213,7 +213,11 @@ func Commands() []*cli.Command { // login pops up the browser window to do the actual login and JWT generation func login(c *cli.Context) error { - if err := raven.SetDSN(sentryDSN); err != nil { + err := sentry.Init(sentry.ClientOptions{ + Dsn: sentryDSN, + Release: c.App.Version, + }) + if err != nil { return err } @@ -262,7 +266,11 @@ func ensureURLScheme(url string) string { // curl provides a wrapper around curl, passing Access JWT along in request func curl(c *cli.Context) error { - if err := raven.SetDSN(sentryDSN); err != nil { + err := sentry.Init(sentry.ClientOptions{ + Dsn: sentryDSN, + Release: c.App.Version, + }) + if err != nil { return err } log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog) @@ -283,6 +291,13 @@ func curl(c *cli.Context) error { if err != nil { return err } + + // Verify that the existing token is still good; if not fetch a new one + if err := verifyTokenAtEdge(appURL, appInfo, c, log); err != nil { + log.Err(err).Msg("Could not verify token") + return err + } + tok, err := token.GetAppTokenIfExists(appInfo) if err != nil || tok == "" { if allowRequest { @@ -325,7 +340,11 @@ func run(cmd string, args ...string) error { // token dumps provided token to stdout func generateToken(c *cli.Context) error { - if err := raven.SetDSN(sentryDSN); err != nil { + err := sentry.Init(sentry.ClientOptions{ + Dsn: sentryDSN, + Release: c.App.Version, + }) + if err != nil { return err } appURL, err := url.Parse(ensureURLScheme(c.String("app"))) diff --git a/cmd/cloudflared/cliutil/build_info.go b/cmd/cloudflared/cliutil/build_info.go index 4d73701e..fff4febf 100644 --- a/cmd/cloudflared/cliutil/build_info.go +++ b/cmd/cloudflared/cliutil/build_info.go @@ -47,3 +47,7 @@ func (bi *BuildInfo) GetBuildTypeMsg() string { } return fmt.Sprintf(" with %s", bi.BuildType) } + +func (bi *BuildInfo) UserAgent() string { + return fmt.Sprintf("cloudflared/%s", bi.CloudflaredVersion) +} diff --git a/cmd/cloudflared/cliutil/logger.go b/cmd/cloudflared/cliutil/logger.go new file mode 100644 index 00000000..01435213 --- /dev/null +++ b/cmd/cloudflared/cliutil/logger.go @@ -0,0 +1,51 @@ +package cliutil + +import ( + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" + + "github.com/cloudflare/cloudflared/logger" +) + +var ( + debugLevelWarning = "At debug level cloudflared will log request URL, method, protocol, content length, as well as, all request and response headers. " + + "This can expose sensitive information in your logs." +) + +func ConfigureLoggingFlags(shouldHide bool) []cli.Flag { + return []cli.Flag{ + altsrc.NewStringFlag(&cli.StringFlag{ + Name: logger.LogLevelFlag, + Value: "info", + Usage: "Application logging level {debug, info, warn, error, fatal}. " + debugLevelWarning, + EnvVars: []string{"TUNNEL_LOGLEVEL"}, + Hidden: shouldHide, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: logger.LogTransportLevelFlag, + Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel + Value: "info", + Usage: "Transport logging level(previously called protocol logging level) {debug, info, warn, error, fatal}", + EnvVars: []string{"TUNNEL_PROTO_LOGLEVEL", "TUNNEL_TRANSPORT_LOGLEVEL"}, + Hidden: shouldHide, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: logger.LogFileFlag, + Usage: "Save application log to this file for reporting issues.", + EnvVars: []string{"TUNNEL_LOGFILE"}, + Hidden: shouldHide, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: logger.LogDirectoryFlag, + Usage: "Save application log to this directory for reporting issues.", + EnvVars: []string{"TUNNEL_LOGDIRECTORY"}, + Hidden: shouldHide, + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "trace-output", + Usage: "Name of trace output file, generated when cloudflared stops.", + EnvVars: []string{"TUNNEL_TRACE_OUTPUT"}, + Hidden: shouldHide, + }), + } +} diff --git a/cmd/cloudflared/main.go b/cmd/cloudflared/main.go index 1ac06c3e..3ed25a62 100644 --- a/cmd/cloudflared/main.go +++ b/cmd/cloudflared/main.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/getsentry/raven-go" + "github.com/getsentry/sentry-go" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -15,6 +15,7 @@ import ( "github.com/cloudflare/cloudflared/cmd/cloudflared/access" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" "github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns" + "github.com/cloudflare/cloudflared/cmd/cloudflared/tail" "github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel" "github.com/cloudflare/cloudflared/cmd/cloudflared/updater" "github.com/cloudflare/cloudflared/config" @@ -50,7 +51,6 @@ var ( func main() { rand.Seed(time.Now().UnixNano()) metrics.RegisterBuildInfo(BuildType, BuildTime, Version) - raven.SetRelease(Version) maxprocs.Set() bInfo := cliutil.GetBuildInfo(BuildType, Version) @@ -90,6 +90,7 @@ func main() { updater.Init(Version) tracing.Init(Version) token.Init(Version) + tail.Init(bInfo) runApp(app, graceShutdownC) } @@ -139,6 +140,7 @@ To determine if an update happened in a script, check for error code 11.`, cmds = append(cmds, tunnel.Commands()...) cmds = append(cmds, proxydns.Command(false)) cmds = append(cmds, access.Commands()...) + cmds = append(cmds, tail.Command()) return cmds } @@ -156,10 +158,10 @@ func action(graceShutdownC chan struct{}) cli.ActionFunc { if isEmptyInvocation(c) { return handleServiceMode(c, graceShutdownC) } - tags := make(map[string]string) - tags["hostname"] = c.String("hostname") - raven.SetTagsContext(tags) - raven.CapturePanic(func() { err = tunnel.TunnelCommand(c) }, nil) + func() { + defer sentry.Recover() + err = tunnel.TunnelCommand(c) + }() if err != nil { captureError(err) } @@ -187,7 +189,7 @@ func captureError(err error) { return } } - raven.CaptureError(err, nil) + sentry.CaptureException(err) } // cloudflared was started without any flags diff --git a/cmd/cloudflared/proxydns/cmd.go b/cmd/cloudflared/proxydns/cmd.go index ae03d8c6..950da293 100644 --- a/cmd/cloudflared/proxydns/cmd.go +++ b/cmd/cloudflared/proxydns/cmd.go @@ -1,6 +1,7 @@ package proxydns import ( + "context" "net" "os" "os/signal" @@ -73,7 +74,7 @@ func Run(c *cli.Context) error { log.Fatal().Err(err).Msg("Failed to open the metrics listener") } - go metrics.ServeMetrics(metricsListener, nil, nil, "", nil, log) + go metrics.ServeMetrics(metricsListener, context.Background(), metrics.Config{}, log) listener, err := tunneldns.CreateListener( c.String("address"), diff --git a/cmd/cloudflared/tail/cmd.go b/cmd/cloudflared/tail/cmd.go new file mode 100644 index 00000000..7d444024 --- /dev/null +++ b/cmd/cloudflared/tail/cmd.go @@ -0,0 +1,428 @@ +package tail + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "os/signal" + "syscall" + "time" + + "github.com/google/uuid" + "github.com/mattn/go-colorable" + "github.com/rs/zerolog" + "github.com/urfave/cli/v2" + "nhooyr.io/websocket" + + "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" + "github.com/cloudflare/cloudflared/credentials" + "github.com/cloudflare/cloudflared/logger" + "github.com/cloudflare/cloudflared/management" +) + +var ( + buildInfo *cliutil.BuildInfo +) + +func Init(bi *cliutil.BuildInfo) { + buildInfo = bi +} + +func Command() *cli.Command { + subcommands := []*cli.Command{ + buildTailManagementTokenSubcommand(), + } + + return buildTailCommand(subcommands) +} + +func buildTailManagementTokenSubcommand() *cli.Command { + return &cli.Command{ + Name: "token", + Action: cliutil.ConfiguredAction(managementTokenCommand), + Usage: "Get management access jwt", + UsageText: "cloudflared tail token TUNNEL_ID", + Description: `Get management access jwt for a tunnel`, + Hidden: true, + } +} + +func managementTokenCommand(c *cli.Context) error { + log := createLogger(c) + token, err := getManagementToken(c, log) + if err != nil { + return err + } + var tokenResponse = struct { + Token string `json:"token"` + }{Token: token} + + return json.NewEncoder(os.Stdout).Encode(tokenResponse) +} + +func buildTailCommand(subcommands []*cli.Command) *cli.Command { + return &cli.Command{ + Name: "tail", + Action: Run, + Usage: "Stream logs from a remote cloudflared", + UsageText: "cloudflared tail [tail command options] [TUNNEL-ID]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "connector-id", + Usage: "Access a specific cloudflared instance by connector id (for when a tunnel has multiple cloudflared's)", + Value: "", + EnvVars: []string{"TUNNEL_MANAGEMENT_CONNECTOR"}, + }, + &cli.StringSliceFlag{ + Name: "event", + Usage: "Filter by specific Events (cloudflared, http, tcp, udp) otherwise, defaults to send all events", + EnvVars: []string{"TUNNEL_MANAGEMENT_FILTER_EVENTS"}, + }, + &cli.StringFlag{ + Name: "level", + Usage: "Filter by specific log levels (debug, info, warn, error). Filters by debug log level by default.", + EnvVars: []string{"TUNNEL_MANAGEMENT_FILTER_LEVEL"}, + Value: "debug", + }, + &cli.Float64Flag{ + Name: "sample", + Usage: "Sample log events by percentage (0.0 .. 1.0). No sampling by default.", + EnvVars: []string{"TUNNEL_MANAGEMENT_FILTER_SAMPLE"}, + Value: 1.0, + }, + &cli.StringFlag{ + Name: "token", + Usage: "Access token for a specific tunnel", + Value: "", + EnvVars: []string{"TUNNEL_MANAGEMENT_TOKEN"}, + }, + &cli.StringFlag{ + Name: "output", + Usage: "Output format for the logs (default, json)", + Value: "default", + EnvVars: []string{"TUNNEL_MANAGEMENT_OUTPUT"}, + }, + &cli.StringFlag{ + Name: "management-hostname", + Usage: "Management hostname to signify incoming management requests", + EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"}, + Hidden: true, + Value: "management.argotunnel.com", + }, + &cli.StringFlag{ + Name: "trace", + Usage: "Set a cf-trace-id for the request", + Hidden: true, + Value: "", + }, + &cli.StringFlag{ + Name: logger.LogLevelFlag, + Value: "info", + Usage: "Application logging level {debug, info, warn, error, fatal}", + EnvVars: []string{"TUNNEL_LOGLEVEL"}, + }, + &cli.StringFlag{ + Name: credentials.OriginCertFlag, + Usage: "Path to the certificate generated for your origin when you run cloudflared login.", + EnvVars: []string{"TUNNEL_ORIGIN_CERT"}, + Value: credentials.FindDefaultOriginCertPath(), + }, + }, + Subcommands: subcommands, + } +} + +// Middleware validation error struct for returning to the eyeball +type managementError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +// Middleware validation error HTTP response JSON for returning to the eyeball +type managementErrorResponse struct { + Success bool `json:"success,omitempty"` + Errors []managementError `json:"errors,omitempty"` +} + +func handleValidationError(resp *http.Response, log *zerolog.Logger) { + if resp.StatusCode == 530 { + log.Error().Msgf("no cloudflared connector available or reachable via management request (a recent version of cloudflared is required to use streaming logs)") + } + var managementErr managementErrorResponse + err := json.NewDecoder(resp.Body).Decode(&managementErr) + if err != nil { + log.Error().Msgf("unable to start management log streaming session: http response code returned %d", resp.StatusCode) + return + } + if managementErr.Success || len(managementErr.Errors) == 0 { + log.Error().Msgf("management tunnel validation returned success with invalid HTTP response code to convert to a WebSocket request") + return + } + for _, e := range managementErr.Errors { + log.Error().Msgf("management request failed validation: (%d) %s", e.Code, e.Message) + } +} + +// logger will be created to emit only against the os.Stderr as to not obstruct with normal output from +// management requests +func createLogger(c *cli.Context) *zerolog.Logger { + level, levelErr := zerolog.ParseLevel(c.String(logger.LogLevelFlag)) + if levelErr != nil { + level = zerolog.InfoLevel + } + log := zerolog.New(zerolog.ConsoleWriter{ + Out: colorable.NewColorable(os.Stderr), + TimeFormat: time.RFC3339, + }).With().Timestamp().Logger().Level(level) + return &log +} + +// parseFilters will attempt to parse provided filters to send to with the EventStartStreaming +func parseFilters(c *cli.Context) (*management.StreamingFilters, error) { + var level *management.LogLevel + var events []management.LogEventType + var sample float64 + + argLevel := c.String("level") + argEvents := c.StringSlice("event") + argSample := c.Float64("sample") + + if argLevel != "" { + l, ok := management.ParseLogLevel(argLevel) + if !ok { + return nil, fmt.Errorf("invalid --level filter provided, please use one of the following Log Levels: debug, info, warn, error") + } + level = &l + } + + for _, v := range argEvents { + t, ok := management.ParseLogEventType(v) + if !ok { + return nil, fmt.Errorf("invalid --event filter provided, please use one of the following EventTypes: cloudflared, http, tcp, udp") + } + events = append(events, t) + } + + if argSample <= 0.0 || argSample > 1.0 { + return nil, fmt.Errorf("invalid --sample value provided, please make sure it is in the range (0.0 .. 1.0)") + } + sample = argSample + + if level == nil && len(events) == 0 && argSample != 1.0 { + // When no filters are provided, do not return a StreamingFilters struct + return nil, nil + } + + return &management.StreamingFilters{ + Level: level, + Events: events, + Sampling: sample, + }, nil +} + +// getManagementToken will make a call to the Cloudflare API to acquire a management token for the requested tunnel. +func getManagementToken(c *cli.Context, log *zerolog.Logger) (string, error) { + userCreds, err := credentials.Read(c.String(credentials.OriginCertFlag), log) + if err != nil { + return "", err + } + + client, err := userCreds.Client(c.String("api-url"), buildInfo.UserAgent(), log) + if err != nil { + return "", err + } + + tunnelIDString := c.Args().First() + if tunnelIDString == "" { + return "", errors.New("no tunnel ID provided") + } + tunnelID, err := uuid.Parse(tunnelIDString) + if err != nil { + return "", errors.New("unable to parse provided tunnel id as a valid UUID") + } + + token, err := client.GetManagementToken(tunnelID) + if err != nil { + return "", err + } + + return token, nil +} + +// buildURL will build the management url to contain the required query parameters to authenticate the request. +func buildURL(c *cli.Context, log *zerolog.Logger) (url.URL, error) { + var err error + managementHostname := c.String("management-hostname") + token := c.String("token") + if token == "" { + token, err = getManagementToken(c, log) + if err != nil { + return url.URL{}, fmt.Errorf("unable to acquire management token for requested tunnel id: %w", err) + } + } + query := url.Values{} + query.Add("access_token", token) + connector := c.String("connector-id") + if connector != "" { + connectorID, err := uuid.Parse(connector) + if err != nil { + return url.URL{}, fmt.Errorf("unabled to parse 'connector-id' flag into a valid UUID: %w", err) + } + query.Add("connector_id", connectorID.String()) + } + return url.URL{Scheme: "wss", Host: managementHostname, Path: "/logs", RawQuery: query.Encode()}, nil +} + +func printLine(log *management.Log, logger *zerolog.Logger) { + fields, err := json.Marshal(log.Fields) + if err != nil { + fields = []byte("unable to parse fields") + logger.Debug().Msgf("unable to parse fields from event %+v", log) + } + fmt.Printf("%s %s %s %s %s\n", log.Time, log.Level, log.Event, log.Message, fields) +} + +func printJSON(log *management.Log, logger *zerolog.Logger) { + output, err := json.Marshal(log) + if err != nil { + logger.Debug().Msgf("unable to parse event to json %+v", log) + } else { + fmt.Println(string(output)) + } +} + +// Run implements a foreground runner +func Run(c *cli.Context) error { + log := createLogger(c) + + signals := make(chan os.Signal, 10) + signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) + defer signal.Stop(signals) + + output := "default" + switch c.String("output") { + case "default", "": + output = "default" + case "json": + output = "json" + default: + log.Err(errors.New("invalid --output value provided, please make sure it is one of: default, json")).Send() + } + + filters, err := parseFilters(c) + if err != nil { + log.Error().Err(err).Msgf("invalid filters provided") + return nil + } + + u, err := buildURL(c, log) + if err != nil { + log.Err(err).Msg("unable to construct management request URL") + return nil + } + + header := make(http.Header) + header.Add("User-Agent", buildInfo.UserAgent()) + trace := c.String("trace") + if trace != "" { + header["cf-trace-id"] = []string{trace} + } + ctx := c.Context + conn, resp, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{ + HTTPHeader: header, + }) + if err != nil { + if resp != nil && resp.StatusCode != http.StatusSwitchingProtocols { + handleValidationError(resp, log) + return nil + } + log.Error().Err(err).Msgf("unable to start management log streaming session") + return nil + } + defer conn.Close(websocket.StatusInternalError, "management connection was closed abruptly") + + // Once connection is established, send start_streaming event to begin receiving logs + err = management.WriteEvent(conn, ctx, &management.EventStartStreaming{ + ClientEvent: management.ClientEvent{Type: management.StartStreaming}, + Filters: filters, + }) + if err != nil { + log.Error().Err(err).Msg("unable to request logs from management tunnel") + return nil + } + log.Debug(). + Str("tunnel-id", c.Args().First()). + Str("connector-id", c.String("connector-id")). + Interface("filters", filters). + Msg("connected") + + readerDone := make(chan struct{}) + + go func() { + defer close(readerDone) + for { + select { + case <-ctx.Done(): + return + default: + event, err := management.ReadServerEvent(conn, ctx) + if err != nil { + if closeErr := management.AsClosed(err); closeErr != nil { + // If the client (or the server) already closed the connection, don't continue to + // attempt to read from the client. + if closeErr.Code == websocket.StatusNormalClosure { + return + } + // Only log abnormal closures + log.Error().Msgf("received remote closure: (%d) %s", closeErr.Code, closeErr.Reason) + return + } + log.Err(err).Msg("unable to read event from server") + return + } + switch event.Type { + case management.Logs: + logs, ok := management.IntoServerEvent(event, management.Logs) + if !ok { + log.Error().Msgf("invalid logs event") + continue + } + // Output all the logs received to stdout + for _, l := range logs.Logs { + if output == "json" { + printJSON(l, log) + } else { + printLine(l, log) + } + } + case management.UnknownServerEventType: + fallthrough + default: + log.Debug().Msgf("unexpected log event type: %s", event.Type) + } + } + } + }() + + for { + select { + case <-ctx.Done(): + return nil + case <-readerDone: + return nil + case <-signals: + log.Debug().Msg("closing management connection") + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + conn.Close(websocket.StatusNormalClosure, "") + select { + case <-readerDone: + case <-time.After(time.Second): + } + return nil + } + } +} diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 6acafe4b..be11f24e 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/coreos/go-systemd/daemon" "github.com/facebookgo/grace/gracenet" - "github.com/getsentry/raven-go" + "github.com/getsentry/sentry-go" "github.com/google/uuid" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" @@ -28,19 +28,27 @@ import ( "github.com/cloudflare/cloudflared/cmd/cloudflared/updater" "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/connection" + "github.com/cloudflare/cloudflared/credentials" + "github.com/cloudflare/cloudflared/edgediscovery" + "github.com/cloudflare/cloudflared/features" "github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/logger" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/metrics" "github.com/cloudflare/cloudflared/orchestration" "github.com/cloudflare/cloudflared/signal" "github.com/cloudflare/cloudflared/supervisor" "github.com/cloudflare/cloudflared/tlsconfig" "github.com/cloudflare/cloudflared/tunneldns" + "github.com/cloudflare/cloudflared/validation" ) const ( sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878" + // ha-Connections specifies how many connections to make to the edge + haConnectionsFlag = "ha-connections" + // sshPortFlag is the port on localhost the cloudflared ssh server will run on sshPortFlag = "local-ssh-port" @@ -74,14 +82,21 @@ const ( // uiFlag is to enable launching cloudflared in interactive UI mode uiFlag = "ui" - debugLevelWarning = "At debug level cloudflared will log request URL, method, protocol, content length, as well as, all request and response headers. " + - "This can expose sensitive information in your logs." - LogFieldCommand = "command" LogFieldExpandedPath = "expandedPath" LogFieldPIDPathname = "pidPathname" LogFieldTmpTraceFilename = "tmpTraceFilename" LogFieldTraceOutputFilepath = "traceOutputFilepath" + + tunnelCmdErrorMessage = `You did not specify any valid additional argument to the cloudflared tunnel command. + +If you are trying to run a Quick Tunnel then you need to explicitly pass the --url flag. +Eg. cloudflared tunnel --url localhost:8080/. + +Please note that Quick Tunnels are meant to be ephemeral and should only be used for testing purposes. +For production usage, we recommend creating Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/) +` + connectorLabelFlag = "label" ) var ( @@ -91,6 +106,7 @@ var ( routeFailMsg = fmt.Sprintf("failed to provision routing, please create it manually via Cloudflare dashboard or UI; "+ "most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+ "any existing DNS records for this hostname.", overwriteDNSFlag) + deprecatedClassicTunnelErr = fmt.Errorf("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)") ) func Flags() []cli.Flag { @@ -166,21 +182,54 @@ func TunnelCommand(c *cli.Context) error { if err != nil { return err } - if name := c.String("name"); name != "" { // Start a named tunnel + + // Run a adhoc named tunnel + // Allows for the creation, routing (optional), and startup of a tunnel in one command + // --name required + // --url or --hello-world required + // --hostname optional + if name := c.String("name"); name != "" { + hostname, err := validation.ValidateHostname(c.String("hostname")) + if err != nil { + return errors.Wrap(err, "Invalid hostname provided") + } + url := c.String("url") + if url == hostname && url != "" && hostname != "" { + return fmt.Errorf("hostname and url shouldn't match. See --help for more information") + } + return runAdhocNamedTunnel(sc, name, c.String(CredFileFlag)) } + + // Run a quick tunnel + // A unauthenticated named tunnel hosted on ..com + // We don't support running proxy-dns and a quick tunnel at the same time as the same process + shouldRunQuickTunnel := c.IsSet("url") || c.IsSet(ingress.HelloWorldFlag) + if !c.IsSet("proxy-dns") && c.String("quick-service") != "" && shouldRunQuickTunnel { + return RunQuickTunnel(sc) + } + + // If user provides a config, check to see if they meant to use `tunnel run` instead if ref := config.GetConfiguration().TunnelID; ref != "" { return fmt.Errorf("Use `cloudflared tunnel run` to start tunnel %s", ref) } - // Unauthenticated named tunnel on ..com - // For now, default to legacy setup unless quick-service is specified - if !dnsProxyStandAlone(c, nil) && c.String("hostname") == "" && c.String("quick-service") != "" { - return RunQuickTunnel(sc) + // Classic tunnel usage is no longer supported + if c.String("hostname") != "" { + return deprecatedClassicTunnelErr } - // Start a classic tunnel - return runClassicTunnel(sc) + if c.IsSet("proxy-dns") { + if shouldRunQuickTunnel { + return fmt.Errorf("running a quick tunnel with `proxy-dns` is not supported") + } + // NamedTunnelProperties are nil since proxy dns server does not need it. + // This is supported for legacy reasons: dns proxy server is not a tunnel and ideally should + // not run as part of cloudflared tunnel. + return StartServer(sc.c, buildInfo, nil, sc.log) + } + + return errors.New(tunnelCmdErrorMessage) } func Init(info *cliutil.BuildInfo, gracefulShutdown chan struct{}) { @@ -215,11 +264,6 @@ func runAdhocNamedTunnel(sc *subcommandContext, name, credentialsOutputPath stri return nil } -// runClassicTunnel creates a "classic" non-named tunnel -func runClassicTunnel(sc *subcommandContext) error { - return StartServer(sc.c, buildInfo, nil, sc.log) -} - func routeFromFlag(c *cli.Context) (route cfapi.HostnameRoute, ok bool) { if hostname := c.String("hostname"); hostname != "" { if lbPool := c.String("lb-pool"); lbPool != "" { @@ -236,12 +280,19 @@ func StartServer( namedTunnel *connection.NamedTunnelProperties, log *zerolog.Logger, ) error { - _ = raven.SetDSN(sentryDSN) + err := sentry.Init(sentry.ClientOptions{ + Dsn: sentryDSN, + Release: c.App.Version, + }) + if err != nil { + return err + } var wg sync.WaitGroup listeners := gracenet.Net{} errC := make(chan error) - if config.GetConfiguration().Source() == "" { + // Only log for locally configured tunnels (Token is blank). + if config.GetConfiguration().Source() == "" && c.String(TunnelTokenFlag) == "" { log.Info().Msg(config.ErrNoConfigFile.Error()) } @@ -314,21 +365,13 @@ func StartServer( errC <- autoupdater.Run(ctx) }() - // Serve DNS proxy stand-alone if no hostname or tag or app is going to run + // Serve DNS proxy stand-alone if no tunnel type (quick, adhoc, named) is going to run if dnsProxyStandAlone(c, namedTunnel) { connectedSignal.Notify() // no grace period, handle SIGINT/SIGTERM immediately return waitToShutdown(&wg, cancel, errC, graceShutdownC, 0, log) } - url := c.String("url") - hostname := c.String("hostname") - if url == hostname && url != "" && hostname != "" { - errText := "hostname and url shouldn't match. See --help for more information" - log.Error().Msg(errText) - return fmt.Errorf(errText) - } - logTransport := logger.CreateTransportLoggerFromContext(c, logger.EnableTerminalLog) observer := connection.NewObserver(log, logTransport) @@ -356,7 +399,26 @@ func StartServer( } } - orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, tunnelConfig.Log) + localRules := []ingress.Rule{} + if features.Contains(features.FeatureManagementLogs) { + serviceIP := c.String("service-op-ip") + if edgeAddrs, err := edgediscovery.ResolveEdge(log, tunnelConfig.Region, tunnelConfig.EdgeIPVersion); err == nil { + if serviceAddr, err := edgeAddrs.GetAddrForRPC(); err == nil { + serviceIP = serviceAddr.TCP.String() + } + } + + mgmt := management.New( + c.String("management-hostname"), + serviceIP, + clientID, + c.String(connectorLabelFlag), + logger.ManagementLogger.Log, + logger.ManagementLogger, + ) + localRules = []ingress.Rule{ingress.NewManagementRule(mgmt)} + } + orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, localRules, tunnelConfig.Log) if err != nil { return err } @@ -372,10 +434,15 @@ func StartServer( defer wg.Done() readinessServer := metrics.NewReadyServer(log, clientID) observer.RegisterSink(readinessServer) - errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log) + metricsConfig := metrics.Config{ + ReadyServer: readinessServer, + QuickTunnelHostname: quickTunnelURL, + Orchestrator: orchestrator, + } + errC <- metrics.ServeMetrics(metricsListener, ctx, metricsConfig, log) }() - reconnectCh := make(chan supervisor.ReconnectSignal, c.Int("ha-connections")) + reconnectCh := make(chan supervisor.ReconnectSignal, c.Int(haConnectionsFlag)) if c.IsSet("stdin-control") { log.Info().Msg("Enabling control through stdin") go stdinControl(reconnectCh, log) @@ -488,7 +555,7 @@ func addPortIfMissing(uri *url.URL, port int) string { func tunnelFlags(shouldHide bool) []cli.Flag { flags := configureCloudflaredFlags(shouldHide) flags = append(flags, configureProxyFlags(shouldHide)...) - flags = append(flags, configureLoggingFlags(shouldHide)...) + flags = append(flags, cliutil.ConfigureLoggingFlags(shouldHide)...) flags = append(flags, configureProxyDNSFlags(shouldHide)...) flags = append(flags, []cli.Flag{ credentialsFileFlag, @@ -511,11 +578,17 @@ func tunnelFlags(shouldHide bool) []cli.Flag { }), altsrc.NewStringFlag(&cli.StringFlag{ Name: "edge-ip-version", - Usage: "Cloudflare Edge ip address version to connect with. {4, 6, auto}", + Usage: "Cloudflare Edge IP address version to connect with. {4, 6, auto}", EnvVars: []string{"TUNNEL_EDGE_IP_VERSION"}, Value: "4", Hidden: false, }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "edge-bind-address", + Usage: "Bind to IP address for outgoing connections to Cloudflare Edge.", + EnvVars: []string{"TUNNEL_EDGE_BIND_ADDRESS"}, + Hidden: false, + }), altsrc.NewStringFlag(&cli.StringFlag{ Name: tlsconfig.CaCertFlag, Usage: "Certificate Authority authenticating connections with Cloudflare's edge network.", @@ -591,6 +664,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag { Value: 5, Hidden: true, }), + altsrc.NewIntFlag(&cli.IntFlag{ + Name: "max-edge-addr-retries", + Usage: "Maximum number of times to retry on edge addrs before falling back to a lower protocol", + Value: 8, + Hidden: true, + }), // Note TUN-3758 , we use Int because UInt is not supported with altsrc altsrc.NewIntFlag(&cli.IntFlag{ Name: "retries", @@ -600,10 +679,15 @@ func tunnelFlags(shouldHide bool) []cli.Flag { Hidden: shouldHide, }), altsrc.NewIntFlag(&cli.IntFlag{ - Name: "ha-connections", + Name: haConnectionsFlag, Value: 4, Hidden: true, }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: connectorLabelFlag, + Usage: "Use this option to give a meaningful label to a specific connector. When a tunnel starts up, a connector id unique to the tunnel is generated. This is a uuid. To make it easier to identify a connector, we will use the hostname of the machine the tunnel is running on along with the connector ID. This option exists if one wants to have more control over what their individual connectors are called.", + Value: "", + }), altsrc.NewDurationFlag(&cli.DurationFlag{ Name: "grace-period", Usage: "When cloudflared receives SIGINT/SIGTERM it will stop accepting new requests, wait for in-progress requests to terminate, then shutdown. Waiting for in-progress requests will timeout after this grace period, or when a second SIGTERM/SIGINT is received.", @@ -689,10 +773,10 @@ func configureCloudflaredFlags(shouldHide bool) []cli.Flag { Hidden: shouldHide, }, altsrc.NewStringFlag(&cli.StringFlag{ - Name: "origincert", + Name: credentials.OriginCertFlag, Usage: "Path to the certificate generated for your origin when you run cloudflared login.", EnvVars: []string{"TUNNEL_ORIGIN_CERT"}, - Value: findDefaultOriginCertPath(), + Value: credentials.FindDefaultOriginCertPath(), Hidden: shouldHide, }), altsrc.NewDurationFlag(&cli.DurationFlag{ @@ -734,7 +818,7 @@ func configureProxyFlags(shouldHide bool) []cli.Flag { Hidden: shouldHide, }), altsrc.NewBoolFlag(&cli.BoolFlag{ - Name: "hello-world", + Name: ingress.HelloWorldFlag, Value: false, Usage: "Run Hello World Server", EnvVars: []string{"TUNNEL_HELLO_WORLD"}, @@ -837,6 +921,20 @@ func configureProxyFlags(shouldHide bool) []cli.Flag { Hidden: shouldHide, Value: false, }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "management-hostname", + Usage: "Management hostname to signify incoming management requests", + EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"}, + Hidden: true, + Value: "management.argotunnel.com", + }), + altsrc.NewStringFlag(&cli.StringFlag{ + Name: "service-op-ip", + Usage: "Fallback IP for service operations run by the management service.", + EnvVars: []string{"TUNNEL_SERVICE_OP_IP"}, + Hidden: true, + Value: "198.41.200.113:80", + }), } return append(flags, sshFlags(shouldHide)...) } @@ -945,44 +1043,6 @@ func sshFlags(shouldHide bool) []cli.Flag { } } -func configureLoggingFlags(shouldHide bool) []cli.Flag { - return []cli.Flag{ - altsrc.NewStringFlag(&cli.StringFlag{ - Name: logger.LogLevelFlag, - Value: "info", - Usage: "Application logging level {debug, info, warn, error, fatal}. " + debugLevelWarning, - EnvVars: []string{"TUNNEL_LOGLEVEL"}, - Hidden: shouldHide, - }), - altsrc.NewStringFlag(&cli.StringFlag{ - Name: logger.LogTransportLevelFlag, - Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel - Value: "info", - Usage: "Transport logging level(previously called protocol logging level) {debug, info, warn, error, fatal}", - EnvVars: []string{"TUNNEL_PROTO_LOGLEVEL", "TUNNEL_TRANSPORT_LOGLEVEL"}, - Hidden: shouldHide, - }), - altsrc.NewStringFlag(&cli.StringFlag{ - Name: logger.LogFileFlag, - Usage: "Save application log to this file for reporting issues.", - EnvVars: []string{"TUNNEL_LOGFILE"}, - Hidden: shouldHide, - }), - altsrc.NewStringFlag(&cli.StringFlag{ - Name: logger.LogDirectoryFlag, - Usage: "Save application log to this directory for reporting issues.", - EnvVars: []string{"TUNNEL_LOGDIRECTORY"}, - Hidden: shouldHide, - }), - altsrc.NewStringFlag(&cli.StringFlag{ - Name: "trace-output", - Usage: "Name of trace output file, generated when cloudflared stops.", - EnvVars: []string{"TUNNEL_TRACE_OUTPUT"}, - Hidden: shouldHide, - }), - } -} - func configureProxyDNSFlags(shouldHide bool) []cli.Flag { return []cli.Flag{ altsrc.NewBoolFlag(&cli.BoolFlag{ diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index 69d272b0..89b76392 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -3,17 +3,14 @@ package tunnel import ( "crypto/tls" "fmt" - "io/ioutil" mathRand "math/rand" "net" "net/netip" "os" - "path/filepath" "strings" "time" "github.com/google/uuid" - homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/urfave/cli/v2" @@ -21,21 +18,18 @@ import ( "golang.org/x/crypto/ssh/terminal" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" - "github.com/cloudflare/cloudflared/edgediscovery/allregions" - "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/edgediscovery" - "github.com/cloudflare/cloudflared/h2mux" + "github.com/cloudflare/cloudflared/edgediscovery/allregions" + "github.com/cloudflare/cloudflared/features" "github.com/cloudflare/cloudflared/ingress" "github.com/cloudflare/cloudflared/orchestration" "github.com/cloudflare/cloudflared/supervisor" "github.com/cloudflare/cloudflared/tlsconfig" tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" - "github.com/cloudflare/cloudflared/validation" ) -const LogFieldOriginCertPath = "originCertPath" const secretValue = "*****" var ( @@ -43,26 +37,11 @@ var ( serviceUrl = developerPortal + "/reference/service/" argumentsUrl = developerPortal + "/reference/arguments/" - LogFieldHostname = "hostname" + secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag} - secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag} - defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders, supervisor.FeatureDatagramV2, supervisor.FeatureQUICSupportEOF} - - configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version"} + configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq", "edge-ip-version", "edge-bind-address"} ) -// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories -// contains a cert.pem file, return empty string -func findDefaultOriginCertPath() string { - for _, defaultConfigDir := range config.DefaultConfigSearchDirectories() { - originCertPath, _ := homedir.Expand(filepath.Join(defaultConfigDir, config.DefaultCredentialFile)) - if ok, _ := config.FileExists(originCertPath); ok { - return originCertPath - } - } - return "" -} - func generateRandomClientID(log *zerolog.Logger) (string, error) { u, err := uuid.NewRandom() if err != nil { @@ -127,63 +106,10 @@ func isSecretEnvVar(key string) bool { } func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool { - return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil) -} - -func findOriginCert(originCertPath string, log *zerolog.Logger) (string, error) { - if originCertPath == "" { - log.Info().Msgf("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigSearchDirectories()) - if isRunningFromTerminal() { - log.Error().Msgf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl) - return "", fmt.Errorf("client didn't specify origincert path when running from terminal") - } else { - log.Error().Msgf("You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", serviceUrl) - return "", fmt.Errorf("client didn't specify origincert path") - } - } - var err error - originCertPath, err = homedir.Expand(originCertPath) - if err != nil { - log.Err(err).Msgf("Cannot resolve origin certificate path") - return "", fmt.Errorf("cannot resolve path %s", originCertPath) - } - // Check that the user has acquired a certificate using the login command - ok, err := config.FileExists(originCertPath) - if err != nil { - log.Error().Err(err).Msgf("Cannot check if origin cert exists at path %s", originCertPath) - return "", fmt.Errorf("cannot check if origin cert exists at path %s", originCertPath) - } - if !ok { - log.Error().Msgf(`Cannot find a valid certificate for your origin at the path: - - %s - -If the path above is wrong, specify the path with the -origincert option. -If you don't have a certificate signed by Cloudflare, run the command: - - %s login -`, originCertPath, os.Args[0]) - return "", fmt.Errorf("cannot find a valid certificate at the path %s", originCertPath) - } - - return originCertPath, nil -} - -func readOriginCert(originCertPath string) ([]byte, error) { - // Easier to send the certificate as []byte via RPC than decoding it at this point - originCert, err := ioutil.ReadFile(originCertPath) - if err != nil { - return nil, fmt.Errorf("cannot read %s to load origin certificate", originCertPath) - } - return originCert, nil -} - -func getOriginCert(originCertPath string, log *zerolog.Logger) ([]byte, error) { - if originCertPath, err := findOriginCert(originCertPath, log); err != nil { - return nil, err - } else { - return readOriginCert(originCertPath) - } + return c.IsSet("proxy-dns") && + !(c.IsSet("name") || // adhoc-named tunnel + c.IsSet(ingress.HelloWorldFlag) || // quick or named tunnel + namedTunnel != nil) // named tunnel } func prepareTunnelConfig( @@ -193,37 +119,19 @@ func prepareTunnelConfig( observer *connection.Observer, namedTunnel *connection.NamedTunnelProperties, ) (*supervisor.TunnelConfig, *orchestration.Config, error) { - isNamedTunnel := namedTunnel != nil - - configHostname := c.String("hostname") - hostname, err := validation.ValidateHostname(configHostname) + clientID, err := uuid.NewRandom() if err != nil { - log.Err(err).Str(LogFieldHostname, configHostname).Msg("Invalid hostname") - return nil, nil, errors.Wrap(err, "Invalid hostname") + return nil, nil, errors.Wrap(err, "can't generate connector UUID") } - clientID := c.String("id") - if !c.IsSet("id") { - clientID, err = generateRandomClientID(log) - if err != nil { - return nil, nil, err - } - } - + log.Info().Msgf("Generated Connector ID: %s", clientID) tags, err := NewTagSliceFromCLI(c.StringSlice("tag")) if err != nil { log.Err(err).Msg("Tag parse failure") return nil, nil, errors.Wrap(err, "Tag parse failure") } - - tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID}) - - var ( - ingressRules ingress.Ingress - classicTunnel *connection.ClassicTunnelProperties - ) + tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID.String()}) transportProtocol := c.String("protocol") - needPQ := c.Bool("post-quantum") if needPQ { if FipsEnabled { @@ -236,81 +144,23 @@ func prepareTunnelConfig( transportProtocol = connection.QUIC.String() } - protocolFetcher := edgediscovery.ProtocolPercentage - + clientFeatures := dedup(append(c.StringSlice("features"), features.DefaultFeatures...)) + if needPQ { + clientFeatures = append(clientFeatures, features.FeaturePostQuantum) + } + namedTunnel.Client = tunnelpogs.ClientInfo{ + ClientID: clientID[:], + Features: clientFeatures, + Version: info.Version(), + Arch: info.OSArch(), + } cfg := config.GetConfiguration() - if isNamedTunnel { - clientUUID, err := uuid.NewRandom() - if err != nil { - return nil, nil, errors.Wrap(err, "can't generate connector UUID") - } - log.Info().Msgf("Generated Connector ID: %s", clientUUID) - features := append(c.StringSlice("features"), defaultFeatures...) - if needPQ { - features = append(features, supervisor.FeaturePostQuantum) - } - if c.IsSet(TunnelTokenFlag) { - if transportProtocol == connection.AutoSelectFlag { - protocolFetcher = func() (edgediscovery.ProtocolPercents, error) { - // If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back. - preferQuic := []edgediscovery.ProtocolPercent{ - { - Protocol: connection.QUIC.String(), - Percentage: 100, - }, - { - Protocol: connection.HTTP2.String(), - Percentage: 100, - }, - } - return preferQuic, nil - } - } - log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic") - } - namedTunnel.Client = tunnelpogs.ClientInfo{ - ClientID: clientUUID[:], - Features: dedup(features), - Version: info.Version(), - Arch: info.OSArch(), - } - ingressRules, err = ingress.ParseIngress(cfg) - if err != nil && err != ingress.ErrNoIngressRules { - return nil, nil, err - } - if !ingressRules.IsEmpty() && c.IsSet("url") { - return nil, nil, ingress.ErrURLIncompatibleWithIngress - } - } else { - - originCertPath := c.String("origincert") - originCertLog := log.With(). - Str(LogFieldOriginCertPath, originCertPath). - Logger() - - originCert, err := getOriginCert(originCertPath, &originCertLog) - if err != nil { - return nil, nil, errors.Wrap(err, "Error getting origin cert") - } - - classicTunnel = &connection.ClassicTunnelProperties{ - Hostname: hostname, - OriginCert: originCert, - // turn off use of reconnect token and auth refresh when using named tunnels - UseReconnectToken: !isNamedTunnel && c.Bool("use-reconnect-token"), - } + ingressRules, err := ingress.ParseIngressFromConfigAndCLI(cfg, c, log) + if err != nil { + return nil, nil, err } - // Convert single-origin configuration into multi-origin configuration. - if ingressRules.IsEmpty() { - ingressRules, err = ingress.NewSingleOrigin(c, !isNamedTunnel) - if err != nil { - return nil, nil, err - } - } - - warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel) - protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log, c.Bool("post-quantum")) + protocolSelector, err := connection.NewProtocolSelector(transportProtocol, namedTunnel.Credentials.AccountTag, c.IsSet(TunnelTokenFlag), c.Bool("post-quantum"), edgediscovery.ProtocolPercentage, connection.ResolveTTL, log) if err != nil { return nil, nil, err } @@ -336,18 +186,22 @@ func prepareTunnelConfig( if err != nil { return nil, nil, err } - muxerConfig := &connection.MuxerConfig{ - HeartbeatInterval: c.Duration("heartbeat-interval"), - // Note TUN-3758 , we use Int because UInt is not supported with altsrc - MaxHeartbeats: uint64(c.Int("heartbeat-count")), - // Note TUN-3758 , we use Int because UInt is not supported with altsrc - CompressionSetting: h2mux.CompressionSetting(uint64(c.Int("compression-quality"))), - MetricsUpdateFreq: c.Duration("metrics-update-freq"), - } edgeIPVersion, err := parseConfigIPVersion(c.String("edge-ip-version")) if err != nil { return nil, nil, err } + edgeBindAddr, err := parseConfigBindAddress(c.String("edge-bind-address")) + if err != nil { + return nil, nil, err + } + if err := testIPBindable(edgeBindAddr); err != nil { + return nil, nil, fmt.Errorf("invalid edge-bind-address %s: %v", edgeBindAddr, err) + } + edgeIPVersion, err = adjustIPVersionByBindAddress(edgeIPVersion, edgeBindAddr) + if err != nil { + // This is not a fatal error, we just overrode edgeIPVersion + log.Warn().Str("edgeIPVersion", edgeIPVersion.String()).Err(err).Msg("Overriding edge-ip-version") + } var pqKexIdx int if needPQ { @@ -362,11 +216,12 @@ func prepareTunnelConfig( GracePeriod: gracePeriod, ReplaceExisting: c.Bool("force"), OSArch: info.OSArch(), - ClientID: clientID, + ClientID: clientID.String(), EdgeAddrs: c.StringSlice("edge"), Region: c.String("region"), EdgeIPVersion: edgeIPVersion, - HAConnections: c.Int("ha-connections"), + EdgeBindAddr: edgeBindAddr, + HAConnections: c.Int(haConnectionsFlag), IncidentLookup: supervisor.NewIncidentLookup(), IsAutoupdated: c.Bool("is-autoupdated"), LBPool: c.String("lb-pool"), @@ -376,15 +231,14 @@ func prepareTunnelConfig( Observer: observer, ReportedVersion: info.Version(), // Note TUN-3758 , we use Int because UInt is not supported with altsrc - Retries: uint(c.Int("retries")), - RunFromTerminal: isRunningFromTerminal(), - NamedTunnel: namedTunnel, - ClassicTunnel: classicTunnel, - MuxerConfig: muxerConfig, - ProtocolSelector: protocolSelector, - EdgeTLSConfigs: edgeTLSConfigs, - NeedPQ: needPQ, - PQKexIdx: pqKexIdx, + Retries: uint(c.Int("retries")), + RunFromTerminal: isRunningFromTerminal(), + NamedTunnel: namedTunnel, + ProtocolSelector: protocolSelector, + EdgeTLSConfigs: edgeTLSConfigs, + NeedPQ: needPQ, + PQKexIdx: pqKexIdx, + MaxEdgeAddrRetries: uint8(c.Int("max-edge-addr-retries")), } packetConfig, err := newPacketConfig(c, log) if err != nil { @@ -420,10 +274,6 @@ func gracePeriod(c *cli.Context) (time.Duration, error) { return period, nil } -func isWarpRoutingEnabled(warpConfig config.WarpRoutingConfig, isNamedTunnel bool) bool { - return warpConfig.Enabled && isNamedTunnel -} - func isRunningFromTerminal() bool { return terminal.IsTerminal(int(os.Stdout.Fd())) } @@ -462,6 +312,51 @@ func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err err return } +func parseConfigBindAddress(ipstr string) (net.IP, error) { + // Unspecified - it's fine + if ipstr == "" { + return nil, nil + } + ip := net.ParseIP(ipstr) + if ip == nil { + return nil, fmt.Errorf("invalid value for edge-bind-address: %s", ipstr) + } + return ip, nil +} + +func testIPBindable(ip net.IP) error { + // "Unspecified" = let OS choose, so always bindable + if ip == nil { + return nil + } + + addr := &net.UDPAddr{IP: ip, Port: 0} + listener, err := net.ListenUDP("udp", addr) + if err != nil { + return err + } + listener.Close() + return nil +} + +func adjustIPVersionByBindAddress(ipVersion allregions.ConfigIPVersion, ip net.IP) (allregions.ConfigIPVersion, error) { + if ip == nil { + return ipVersion, nil + } + // https://pkg.go.dev/net#IP.To4: "If ip is not an IPv4 address, To4 returns nil." + if ip.To4() != nil { + if ipVersion == allregions.IPv6Only { + return allregions.IPv4Only, fmt.Errorf("IPv4 bind address is specified, but edge-ip-version is IPv6") + } + return allregions.IPv4Only, nil + } else { + if ipVersion == allregions.IPv4Only { + return allregions.IPv6Only, fmt.Errorf("IPv6 bind address is specified, but edge-ip-version is IPv4") + } + return allregions.IPv6Only, nil + } +} + func newPacketConfig(c *cli.Context, logger *zerolog.Logger) (*ingress.GlobalRouterConfig, error) { ipv4Src, err := determineICMPv4Src(c.String("icmpv4-src"), logger) if err != nil { diff --git a/cmd/cloudflared/tunnel/configuration_test.go b/cmd/cloudflared/tunnel/configuration_test.go index b4edf636..237bc829 100644 --- a/cmd/cloudflared/tunnel/configuration_test.go +++ b/cmd/cloudflared/tunnel/configuration_test.go @@ -9,6 +9,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "net" "os" "testing" @@ -214,3 +215,23 @@ func getCertPoolSubjects(certPool *x509.CertPool) ([]*pkix.Name, error) { func isUnrecoverableError(err error) bool { return err != nil && err.Error() != "crypto/x509: system root pool is not available on Windows" } + +func TestTestIPBindable(t *testing.T) { + assert.Nil(t, testIPBindable(nil)) + + // Public services - if one of these IPs is on the machine, the test environment is too weird + assert.NotNil(t, testIPBindable(net.ParseIP("8.8.8.8"))) + assert.NotNil(t, testIPBindable(net.ParseIP("1.1.1.1"))) + + addrs, err := net.InterfaceAddrs() + if err != nil { + t.Fatal(err) + } + for i, addr := range addrs { + if i >= 3 { + break + } + ip := addr.(*net.IPNet).IP + assert.Nil(t, testIPBindable(ip)) + } +} diff --git a/cmd/cloudflared/tunnel/credential_finder.go b/cmd/cloudflared/tunnel/credential_finder.go index a2320af4..92e05495 100644 --- a/cmd/cloudflared/tunnel/credential_finder.go +++ b/cmd/cloudflared/tunnel/credential_finder.go @@ -5,6 +5,7 @@ import ( "path/filepath" "github.com/cloudflare/cloudflared/config" + "github.com/cloudflare/cloudflared/credentials" "github.com/google/uuid" "github.com/rs/zerolog" @@ -56,13 +57,13 @@ func newSearchByID(id uuid.UUID, c *cli.Context, log *zerolog.Logger, fs fileSys } func (s searchByID) Path() (string, error) { - originCertPath := s.c.String("origincert") + originCertPath := s.c.String(credentials.OriginCertFlag) originCertLog := s.log.With(). - Str(LogFieldOriginCertPath, originCertPath). + Str("originCertPath", originCertPath). Logger() // Fallback to look for tunnel credentials in the origin cert directory - if originCertPath, err := findOriginCert(originCertPath, &originCertLog); err == nil { + if originCertPath, err := credentials.FindOriginCert(originCertPath, &originCertLog); err == nil { originCertDir := filepath.Dir(originCertPath) if filePath, err := tunnelFilePath(s.id, originCertDir); err == nil { if s.fs.validFilePath(filePath) { diff --git a/cmd/cloudflared/tunnel/login.go b/cmd/cloudflared/tunnel/login.go index 8b519147..c7d2881e 100644 --- a/cmd/cloudflared/tunnel/login.go +++ b/cmd/cloudflared/tunnel/login.go @@ -14,6 +14,7 @@ import ( "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" "github.com/cloudflare/cloudflared/config" + "github.com/cloudflare/cloudflared/credentials" "github.com/cloudflare/cloudflared/logger" "github.com/cloudflare/cloudflared/token" ) @@ -52,6 +53,7 @@ func login(c *cli.Context) error { resourceData, err := token.RunTransfer( loginURL, + "", "cert", "callback", callbackStoreURL, @@ -85,7 +87,7 @@ func checkForExistingCert() (string, bool, error) { if err != nil { return "", false, err } - path := filepath.Join(configPath, config.DefaultCredentialFile) + path := filepath.Join(configPath, credentials.DefaultCredentialFile) fileInfo, err := os.Stat(path) if err == nil && fileInfo.Size() > 0 { return path, true, nil diff --git a/cmd/cloudflared/tunnel/quick_tunnel.go b/cmd/cloudflared/tunnel/quick_tunnel.go index 51662b7a..da7d0a63 100644 --- a/cmd/cloudflared/tunnel/quick_tunnel.go +++ b/cmd/cloudflared/tunnel/quick_tunnel.go @@ -73,6 +73,9 @@ func RunQuickTunnel(sc *subcommandContext) error { sc.c.Set("protocol", "quic") } + // Override the number of connections used. Quick tunnels shouldn't be used for production usage, + // so, use a single connection instead. + sc.c.Set(haConnectionsFlag, "1") return StartServer( sc.c, buildInfo, diff --git a/cmd/cloudflared/tunnel/subcommand_context.go b/cmd/cloudflared/tunnel/subcommand_context.go index 650781e8..f49c15eb 100644 --- a/cmd/cloudflared/tunnel/subcommand_context.go +++ b/cmd/cloudflared/tunnel/subcommand_context.go @@ -13,9 +13,9 @@ import ( "github.com/rs/zerolog" "github.com/urfave/cli/v2" - "github.com/cloudflare/cloudflared/certutil" "github.com/cloudflare/cloudflared/cfapi" "github.com/cloudflare/cloudflared/connection" + "github.com/cloudflare/cloudflared/credentials" "github.com/cloudflare/cloudflared/logger" ) @@ -37,7 +37,7 @@ type subcommandContext struct { // These fields should be accessed using their respective Getter tunnelstoreClient cfapi.Client - userCredential *userCredential + userCredential *credentials.User } func newSubcommandContext(c *cli.Context) (*subcommandContext, error) { @@ -56,65 +56,28 @@ func (sc *subcommandContext) credentialFinder(tunnelID uuid.UUID) CredFinder { return newSearchByID(tunnelID, sc.c, sc.log, sc.fs) } -type userCredential struct { - cert *certutil.OriginCert - certPath string -} - func (sc *subcommandContext) client() (cfapi.Client, error) { if sc.tunnelstoreClient != nil { return sc.tunnelstoreClient, nil } - credential, err := sc.credential() + cred, err := sc.credential() if err != nil { return nil, err } - userAgent := fmt.Sprintf("cloudflared/%s", buildInfo.Version()) - client, err := cfapi.NewRESTClient( - sc.c.String("api-url"), - credential.cert.AccountID, - credential.cert.ZoneID, - credential.cert.ServiceKey, - userAgent, - sc.log, - ) - + sc.tunnelstoreClient, err = cred.Client(sc.c.String("api-url"), buildInfo.UserAgent(), sc.log) if err != nil { return nil, err } - sc.tunnelstoreClient = client - return client, nil + return sc.tunnelstoreClient, nil } -func (sc *subcommandContext) credential() (*userCredential, error) { +func (sc *subcommandContext) credential() (*credentials.User, error) { if sc.userCredential == nil { - originCertPath := sc.c.String("origincert") - originCertLog := sc.log.With(). - Str(LogFieldOriginCertPath, originCertPath). - Logger() - - originCertPath, err := findOriginCert(originCertPath, &originCertLog) + uc, err := credentials.Read(sc.c.String(credentials.OriginCertFlag), sc.log) if err != nil { - return nil, errors.Wrap(err, "Error locating origin cert") - } - blocks, err := readOriginCert(originCertPath) - if err != nil { - return nil, errors.Wrapf(err, "Can't read origin cert from %s", originCertPath) - } - - cert, err := certutil.DecodeOriginCert(blocks) - if err != nil { - return nil, errors.Wrap(err, "Error decoding origin cert") - } - - if cert.AccountID == "" { - return nil, errors.Errorf(`Origin certificate needs to be refreshed before creating new tunnels.\nDelete %s and run "cloudflared login" to obtain a new cert.`, originCertPath) - } - - sc.userCredential = &userCredential{ - cert: cert, - certPath: originCertPath, + return nil, err } + sc.userCredential = uc } return sc.userCredential, nil } @@ -175,13 +138,13 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec return nil, err } tunnelCredentials := connection.Credentials{ - AccountTag: credential.cert.AccountID, + AccountTag: credential.AccountID(), TunnelSecret: tunnelSecret, TunnelID: tunnel.ID, } usedCertPath := false if credentialsFilePath == "" { - originCertDir := filepath.Dir(credential.certPath) + originCertDir := filepath.Dir(credential.CertPath()) credentialsFilePath, err = tunnelFilePath(tunnelCredentials.TunnelID, originCertDir) if err != nil { return nil, err diff --git a/cmd/cloudflared/tunnel/subcommand_context_test.go b/cmd/cloudflared/tunnel/subcommand_context_test.go index 35cc46e7..c2293463 100644 --- a/cmd/cloudflared/tunnel/subcommand_context_test.go +++ b/cmd/cloudflared/tunnel/subcommand_context_test.go @@ -16,6 +16,7 @@ import ( "github.com/cloudflare/cloudflared/cfapi" "github.com/cloudflare/cloudflared/connection" + "github.com/cloudflare/cloudflared/credentials" ) type mockFileSystem struct { @@ -37,7 +38,7 @@ func Test_subcommandContext_findCredentials(t *testing.T) { log *zerolog.Logger fs fileSystem tunnelstoreClient cfapi.Client - userCredential *userCredential + userCredential *credentials.User } type args struct { tunnelID uuid.UUID @@ -249,7 +250,7 @@ func Test_subcommandContext_Delete(t *testing.T) { isUIEnabled bool fs fileSystem tunnelstoreClient *deleteMockTunnelStore - userCredential *userCredential + userCredential *credentials.User } type args struct { tunnelIDs []uuid.UUID diff --git a/cmd/cloudflared/tunnel/subcommand_context_vnets.go b/cmd/cloudflared/tunnel/subcommand_context_vnets.go index 14e055fe..c6e9adca 100644 --- a/cmd/cloudflared/tunnel/subcommand_context_vnets.go +++ b/cmd/cloudflared/tunnel/subcommand_context_vnets.go @@ -23,12 +23,12 @@ func (sc *subcommandContext) listVirtualNetworks(filter *cfapi.VnetFilter) ([]*c return client.ListVirtualNetworks(filter) } -func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID) error { +func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID, force bool) error { client, err := sc.client() if err != nil { return errors.Wrap(err, noClientMsg) } - return client.DeleteVirtualNetwork(vnetId) + return client.DeleteVirtualNetwork(vnetId, force) } func (sc *subcommandContext) updateVirtualNetwork(vnetId uuid.UUID, updates cfapi.UpdateVirtualNetwork) error { diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go index 9adc684c..04f0f0d8 100644 --- a/cmd/cloudflared/tunnel/subcommands.go +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -95,14 +95,6 @@ var ( Usage: "Inverts the sort order of the tunnel list.", EnvVars: []string{"TUNNEL_LIST_INVERT_SORT"}, } - forceFlag = altsrc.NewBoolFlag(&cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "By default, if a tunnel is currently being run from a cloudflared, you can't " + - "simultaneously rerun it again from a second cloudflared. The --force flag lets you " + - "overwrite the previous tunnel. If you want to use a single hostname with multiple " + - "tunnels, you can do so with Cloudflare's Load Balancer product.", - }) featuresFlag = altsrc.NewStringSliceFlag(&cli.StringSliceFlag{ Name: "features", Aliases: []string{"F"}, @@ -616,7 +608,6 @@ func renderOutput(format string, v interface{}) error { func buildRunCommand() *cli.Command { flags := []cli.Flag{ - forceFlag, credentialsFileFlag, credentialsContentsFlag, postQuantumFlag, @@ -632,7 +623,7 @@ func buildRunCommand() *cli.Command { Action: cliutil.ConfiguredAction(runCommand), Usage: "Proxy a local web server by running the given tunnel", UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] [TUNNEL]", - Description: `Runs the tunnel identified by name or UUUD, creating highly available connections + Description: `Runs the tunnel identified by name or UUID, creating highly available connections between your server and the Cloudflare edge. You can provide name or UUID of tunnel to run either as the last command line argument or in the configuration file using "tunnel: TUNNEL". @@ -783,7 +774,7 @@ func tokenCommand(c *cli.Context) error { return err } - fmt.Printf("%s", encodedToken) + fmt.Println(encodedToken) return nil } @@ -821,7 +812,7 @@ Further information about managing Cloudflare WARP traffic to your tunnel is ava Name: "lb", Action: cliutil.ConfiguredAction(routeLbCommand), Usage: "Use this tunnel as a load balancer origin, creating pool and load balancer if necessary", - UsageText: "cloudflared tunnel route lb [TUNNEL] [HOSTNAME] [LB-POOL]", + UsageText: "cloudflared tunnel route lb [TUNNEL] [HOSTNAME] [LB-POOL-NAME]", Description: `Creates Load Balancer with an origin pool that points to the tunnel.`, }, buildRouteIPSubcommand(), @@ -941,7 +932,7 @@ func commandHelpTemplate() string { for _, f := range configureCloudflaredFlags(false) { parentFlagsHelp += fmt.Sprintf(" %s\n\t", f) } - for _, f := range configureLoggingFlags(false) { + for _, f := range cliutil.ConfigureLoggingFlags(false) { parentFlagsHelp += fmt.Sprintf(" %s\n\t", f) } const template = `NAME: diff --git a/cmd/cloudflared/tunnel/vnets_subcommands.go b/cmd/cloudflared/tunnel/vnets_subcommands.go index 4a65ad96..57c350c9 100644 --- a/cmd/cloudflared/tunnel/vnets_subcommands.go +++ b/cmd/cloudflared/tunnel/vnets_subcommands.go @@ -33,6 +33,12 @@ var ( Aliases: []string{"c"}, Usage: "A new comment describing the purpose of the virtual network.", } + vnetForceDeleteFlag = &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Force the deletion of the virtual network even if it is being relied upon by other resources. Those" + + "resources will either be deleted (e.g. IP Routes) or moved to the current default virutal network.", + } ) func buildVirtualNetworkSubcommand(hidden bool) *cli.Command { @@ -82,6 +88,7 @@ be the current default.`, UsageText: "cloudflared tunnel [--config FILEPATH] network delete VIRTUAL_NETWORK", Description: `Deletes the virtual network (given its ID or name). This is only possible if that virtual network is unused. A virtual network may be used by IP routes or by WARP devices.`, + Flags: []cli.Flag{vnetForceDeleteFlag}, Hidden: hidden, }, { @@ -188,7 +195,7 @@ func deleteVirtualNetworkCommand(c *cli.Context) error { if err != nil { return err } - if c.NArg() != 1 { + if c.NArg() < 1 { return errors.New("You must supply exactly one argument, either the ID or name of the virtual network to delete") } @@ -198,7 +205,12 @@ func deleteVirtualNetworkCommand(c *cli.Context) error { return err } - if err := sc.deleteVirtualNetwork(vnetId); err != nil { + forceDelete := false + if c.IsSet(vnetForceDeleteFlag.Name) { + forceDelete = c.Bool(vnetForceDeleteFlag.Name) + } + + if err := sc.deleteVirtualNetwork(vnetId, forceDelete); err != nil { return errors.Wrap(err, "API error") } fmt.Printf("Successfully deleted virtual network '%s'\n", input) diff --git a/cmd/cloudflared/updater/update.go b/cmd/cloudflared/updater/update.go index 5ad40dcb..31be284f 100644 --- a/cmd/cloudflared/updater/update.go +++ b/cmd/cloudflared/updater/update.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "time" "github.com/facebookgo/grace/gracenet" @@ -95,11 +96,24 @@ func CheckForUpdate(options updateOptions) (CheckResult, error) { url = StagingUpdateURL } + if runtime.GOOS == "windows" { + cfdPath = encodeWindowsPath(cfdPath) + } + s := NewWorkersService(version, url, cfdPath, Options{IsBeta: options.isBeta, IsForced: options.isForced, RequestedVersion: options.intendedVersion}) return s.Check() } +func encodeWindowsPath(path string) string { + // We do this because Windows allows spaces in directories such as + // Program Files but does not allow these directories to be spaced in batch files. + targetPath := strings.Replace(path, "Program Files (x86)", "PROGRA~2", -1) + // This is to do the same in 32 bit systems. We do this second so that the first + // replace is for x86 dirs. + targetPath = strings.Replace(targetPath, "Program Files", "PROGRA~1", -1) + return targetPath +} func applyUpdate(options updateOptions, update CheckResult) UpdateOutcome { if update.Version() == "" || options.updateDisabled { diff --git a/cmd/cloudflared/updater/workers_update.go b/cmd/cloudflared/updater/workers_update.go index 443f896d..b2d451a9 100644 --- a/cmd/cloudflared/updater/workers_update.go +++ b/cmd/cloudflared/updater/workers_update.go @@ -25,14 +25,13 @@ const ( // rename cloudflared.exe.new to cloudflared.exe // delete cloudflared.exe.old // start the service - // delete the batch file - windowsUpdateCommandTemplate = `@echo off -sc stop cloudflared >nul 2>&1 + // exit with code 0 if we've reached this point indicating success. + windowsUpdateCommandTemplate = `sc stop cloudflared >nul 2>&1 rename "{{.TargetPath}}" {{.OldName}} rename "{{.NewPath}}" {{.BinaryName}} del "{{.OldPath}}" sc start cloudflared >nul 2>&1 -del {{.BatchName}}` +exit /b 0` batchFileName = "cfd_update.bat" ) @@ -214,8 +213,9 @@ func isValidChecksum(checksum, filePath string) error { // writeBatchFile writes a batch file out to disk // see the dicussion on why it has to be done this way func writeBatchFile(targetPath string, newPath string, oldPath string) error { - os.Remove(batchFileName) //remove any failed updates before download - f, err := os.Create(batchFileName) + batchFilePath := filepath.Join(filepath.Dir(targetPath), batchFileName) + os.Remove(batchFilePath) //remove any failed updates before download + f, err := os.Create(batchFilePath) if err != nil { return err } @@ -241,6 +241,16 @@ func writeBatchFile(targetPath string, newPath string, oldPath string) error { // run each OS command for windows func runWindowsBatch(batchFile string) error { - cmd := exec.Command("cmd", "/c", batchFile) - return cmd.Start() + defer os.Remove(batchFile) + cmd := exec.Command("cmd", "/C", batchFile) + _, err := cmd.Output() + // Remove the batch file we created. Don't let this interfere with the error + // we report. + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("Error during update : %s;", string(exitError.Stderr)) + } + + } + return err } diff --git a/component-tests/README.md b/component-tests/README.md index 8a76eeda..537fb47e 100644 --- a/component-tests/README.md +++ b/component-tests/README.md @@ -10,7 +10,6 @@ cloudflared_binary: "cloudflared" tunnel: "3d539f97-cd3a-4d8e-c33b-65e9099c7a8d" credentials_file: "/Users/tunnel/.cloudflared/3d539f97-cd3a-4d8e-c33b-65e9099c7a8d.json" -classic_hostname: "classic-tunnel-component-tests.example.com" origincert: "/Users/tunnel/.cloudflared/cert.pem" ingress: - hostname: named-tunnel-component-tests.example.com diff --git a/component-tests/cli.py b/component-tests/cli.py index ae4a1b12..c0127fa3 100644 --- a/component-tests/cli.py +++ b/component-tests/cli.py @@ -2,7 +2,9 @@ import json import subprocess from time import sleep +from constants import MANAGEMENT_HOST_NAME from setup import get_config_from_file +from util import get_tunnel_connector_id SINGLE_CASE_TIMEOUT = 600 @@ -28,6 +30,36 @@ class CloudflaredCli: listed = self._run_command(cmd_args, "list") return json.loads(listed.stdout) + def get_management_token(self, config, config_path): + basecmd = [config.cloudflared_binary] + if config_path is not None: + basecmd += ["--config", str(config_path)] + origincert = get_config_from_file()["origincert"] + if origincert: + basecmd += ["--origincert", origincert] + + cmd_args = ["tail", "token", config.get_tunnel_id()] + cmd = basecmd + cmd_args + result = run_subprocess(cmd, "token", self.logger, check=True, capture_output=True, timeout=15) + return json.loads(result.stdout.decode("utf-8").strip())["token"] + + def get_management_url(self, path, config, config_path): + access_jwt = self.get_management_token(config, config_path) + connector_id = get_tunnel_connector_id() + return f"https://{MANAGEMENT_HOST_NAME}/{path}?connector_id={connector_id}&access_token={access_jwt}" + + def get_management_wsurl(self, path, config, config_path): + access_jwt = self.get_management_token(config, config_path) + connector_id = get_tunnel_connector_id() + return f"wss://{MANAGEMENT_HOST_NAME}/{path}?connector_id={connector_id}&access_token={access_jwt}" + + def get_connector_id(self, config): + op = self.get_tunnel_info(config.get_tunnel_id()) + connectors = [] + for conn in op["conns"]: + connectors.append(conn["id"]) + return connectors + def get_tunnel_info(self, tunnel_id): info = self._run_command(["info", "--output", "json", tunnel_id], "info") return json.loads(info.stdout) diff --git a/component-tests/config.py b/component-tests/config.py index dd549081..7d811641 100644 --- a/component-tests/config.py +++ b/component-tests/config.py @@ -30,6 +30,7 @@ class NamedTunnelBaseConfig(BaseConfig): tunnel: str = None credentials_file: str = None ingress: list = None + hostname: str = None def __post_init__(self): if self.tunnel is None: @@ -41,8 +42,10 @@ class NamedTunnelBaseConfig(BaseConfig): def merge_config(self, additional): config = super(NamedTunnelBaseConfig, self).merge_config(additional) - config['tunnel'] = self.tunnel - config['credentials-file'] = self.credentials_file + if 'tunnel' not in config: + config['tunnel'] = self.tunnel + if 'credentials-file' not in config: + config['credentials-file'] = self.credentials_file # In some cases we want to override default ingress, such as in config tests if 'ingress' not in config: config['ingress'] = self.ingress @@ -61,7 +64,7 @@ class NamedTunnelConfig(NamedTunnelBaseConfig): self.merge_config(additional_config)) def get_url(self): - return "https://" + self.ingress[0]['hostname'] + return "https://" + self.hostname def base_config(self): config = self.full_config.copy() @@ -84,28 +87,9 @@ class NamedTunnelConfig(NamedTunnelBaseConfig): def get_credentials_json(self): with open(self.credentials_file) as json_file: return json.load(json_file) - - + @dataclass(frozen=True) -class ClassicTunnelBaseConfig(BaseConfig): - hostname: str = None - origincert: str = None - - def __post_init__(self): - if self.hostname is None: - raise TypeError("Field tunnel is not set") - if self.origincert is None: - raise TypeError("Field credentials_file is not set") - - def merge_config(self, additional): - config = super(ClassicTunnelBaseConfig, self).merge_config(additional) - config['hostname'] = self.hostname - config['origincert'] = self.origincert - return config - - -@dataclass(frozen=True) -class ClassicTunnelConfig(ClassicTunnelBaseConfig): +class QuickTunnelConfig(BaseConfig): full_config: dict = None additional_config: InitVar[dict] = {} @@ -115,10 +99,6 @@ class ClassicTunnelConfig(ClassicTunnelBaseConfig): object.__setattr__(self, 'full_config', self.merge_config(additional_config)) - def get_url(self): - return "https://" + self.hostname - - @dataclass(frozen=True) class ProxyDnsConfig(BaseConfig): full_config = { diff --git a/component-tests/config.yaml b/component-tests/config.yaml new file mode 100644 index 00000000..eb3682b3 --- /dev/null +++ b/component-tests/config.yaml @@ -0,0 +1,8 @@ +cloudflared_binary: "cloudflared" +tunnel: "ae21a96c-24d1-4ce8-a6ba-962cba5976d3" +credentials_file: "/Users/sudarsan/.cloudflared/ae21a96c-24d1-4ce8-a6ba-962cba5976d3.json" +origincert: "/Users/sudarsan/.cloudflared/cert.pem" +ingress: +- hostname: named-tunnel-component-tests.example.com + service: hello_world +- service: http_status:404 diff --git a/component-tests/conftest.py b/component-tests/conftest.py index 709ab39a..942c9b91 100644 --- a/component-tests/conftest.py +++ b/component-tests/conftest.py @@ -5,14 +5,14 @@ from time import sleep import pytest import yaml -from config import NamedTunnelConfig, ClassicTunnelConfig, ProxyDnsConfig +from config import NamedTunnelConfig, ProxyDnsConfig, QuickTunnelConfig from constants import BACKOFF_SECS, PROXY_DNS_PORT from util import LOGGER class CfdModes(Enum): NAMED = auto() - CLASSIC = auto() + QUICK = auto() PROXY_DNS = auto() @@ -26,7 +26,7 @@ def component_tests_config(): config = yaml.safe_load(stream) LOGGER.info(f"component tests base config {config}") - def _component_tests_config(additional_config={}, cfd_mode=CfdModes.NAMED, run_proxy_dns=True): + def _component_tests_config(additional_config={}, cfd_mode=CfdModes.NAMED, run_proxy_dns=True, provide_ingress=True): if run_proxy_dns: # Regression test for TUN-4177, running with proxy-dns should not prevent tunnels from running. # So we run all tests with it. @@ -36,18 +36,25 @@ def component_tests_config(): additional_config.pop("proxy-dns", None) additional_config.pop("proxy-dns-port", None) + # Allows the ingress rules to be omitted from the provided config + ingress = [] + if provide_ingress: + ingress = config['ingress'] + + # Provide the hostname to allow routing to the tunnel even if the ingress rule isn't defined in the config + hostname = config['ingress'][0]['hostname'] + if cfd_mode is CfdModes.NAMED: return NamedTunnelConfig(additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'], tunnel=config['tunnel'], credentials_file=config['credentials_file'], - ingress=config['ingress']) - elif cfd_mode is CfdModes.CLASSIC: - return ClassicTunnelConfig( - additional_config=additional_config, cloudflared_binary=config['cloudflared_binary'], - hostname=config['classic_hostname'], origincert=config['origincert']) + ingress=ingress, + hostname=hostname) elif cfd_mode is CfdModes.PROXY_DNS: return ProxyDnsConfig(cloudflared_binary=config['cloudflared_binary']) + elif cfd_mode is CfdModes.QUICK: + return QuickTunnelConfig(additional_config=additional_config, cloudflared_binary=config['cloudflared_binary']) else: raise Exception(f"Unknown cloudflared mode {cfd_mode}") diff --git a/component-tests/constants.py b/component-tests/constants.py index e46bac31..64937006 100644 --- a/component-tests/constants.py +++ b/component-tests/constants.py @@ -4,7 +4,8 @@ BACKOFF_SECS = 7 MAX_LOG_LINES = 50 PROXY_DNS_PORT = 9053 +MANAGEMENT_HOST_NAME = "management.argotunnel.com" def protocols(): - return ["h2mux", "http2", "quic"] + return ["http2", "quic"] diff --git a/component-tests/requirements.txt b/component-tests/requirements.txt index 061e960c..f19c2ac9 100644 --- a/component-tests/requirements.txt +++ b/component-tests/requirements.txt @@ -1,6 +1,8 @@ cloudflare==2.8.15 flaky==3.7.0 -pytest==6.2.2 +pytest==7.3.1 +pytest-asyncio==0.21.0 pyyaml==5.4.1 -requests==2.25.1 -retrying==1.3.3 \ No newline at end of file +requests==2.28.2 +retrying==1.3.4 +websockets==11.0.1 diff --git a/component-tests/setup.py b/component-tests/setup.py index 61352618..f1b6cc5b 100644 --- a/component-tests/setup.py +++ b/component-tests/setup.py @@ -81,12 +81,6 @@ def create_dns(config, hostname, type, content): ) -def create_classic_dns(config, random_uuid): - classic_hostname = "classic-" + random_uuid + "." + config["zone_domain"] - create_dns(config, classic_hostname, "AAAA", "fd10:aec2:5dae::") - return classic_hostname - - def create_named_dns(config, random_uuid): hostname = "named-" + random_uuid + "." + config["zone_domain"] create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com") @@ -119,7 +113,6 @@ def create(): Creates the necessary resources for the components test to run. - Creates a named tunnel with a random name. - Creates a random CNAME DNS entry for that tunnel. - - Creates a random AAAA DNS entry for a classic tunnel. Those created resources are added to the config (obtained from an environment variable). The resulting configuration is persisted for the tests to use. @@ -129,7 +122,6 @@ def create(): random_uuid = str(uuid.uuid4()) config["tunnel"] = create_tunnel(config, origincert_path, random_uuid) - config["classic_hostname"] = create_classic_dns(config, random_uuid) config["ingress"] = [ { "hostname": create_named_dns(config, random_uuid), @@ -150,7 +142,6 @@ def cleanup(): """ config = get_config_from_file() delete_tunnel(config) - delete_dns(config, config["classic_hostname"]) delete_dns(config, config["ingress"][0]["hostname"]) diff --git a/component-tests/test_logging.py b/component-tests/test_logging.py index e9b0bfbd..91af2e58 100644 --- a/component-tests/test_logging.py +++ b/component-tests/test_logging.py @@ -74,7 +74,7 @@ def assert_log_to_dir(config, log_dir): class TestLogging: def test_logging_to_terminal(self, tmp_path, component_tests_config): config = component_tests_config() - with start_cloudflared(tmp_path, config, new_process=True) as cloudflared: + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True) as cloudflared: wait_tunnel_ready(tunnel_url=config.get_url()) assert_log_to_terminal(cloudflared) @@ -85,7 +85,7 @@ class TestLogging: "logfile": str(log_file), } config = component_tests_config(extra_config) - with start_cloudflared(tmp_path, config, new_process=True, capture_output=False): + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, capture_output=False): wait_tunnel_ready(tunnel_url=config.get_url(), cfd_logs=str(log_file)) assert_log_in_file(log_file) assert_json_log(log_file) @@ -98,6 +98,6 @@ class TestLogging: "log-directory": str(log_dir), } config = component_tests_config(extra_config) - with start_cloudflared(tmp_path, config, new_process=True, capture_output=False): + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, capture_output=False): wait_tunnel_ready(tunnel_url=config.get_url(), cfd_logs=str(log_dir)) assert_log_to_dir(config, log_dir) diff --git a/component-tests/test_pq.py b/component-tests/test_pq.py index e897f293..a7b2ed50 100644 --- a/component-tests/test_pq.py +++ b/component-tests/test_pq.py @@ -12,6 +12,6 @@ class TestPostQuantum: def test_post_quantum(self, tmp_path, component_tests_config): config = component_tests_config(self._extra_config()) LOGGER.debug(config) - with start_cloudflared(tmp_path, config, cfd_args=["run", "--post-quantum"], new_process=True): + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run", "--post-quantum"], new_process=True): wait_tunnel_ready(tunnel_url=config.get_url(), - require_min_connections=4) + require_min_connections=1) diff --git a/component-tests/test_proxy_dns.py b/component-tests/test_proxy_dns.py index d498b9e7..aecfa0ae 100644 --- a/component-tests/test_proxy_dns.py +++ b/component-tests/test_proxy_dns.py @@ -12,18 +12,12 @@ class TestProxyDns: def test_proxy_dns_with_named_tunnel(self, tmp_path, component_tests_config): run_test_scenario(tmp_path, component_tests_config, CfdModes.NAMED, run_proxy_dns=True) - def test_proxy_dns_with_classic_tunnel(self, tmp_path, component_tests_config): - run_test_scenario(tmp_path, component_tests_config, CfdModes.CLASSIC, run_proxy_dns=True) - def test_proxy_dns_alone(self, tmp_path, component_tests_config): run_test_scenario(tmp_path, component_tests_config, CfdModes.PROXY_DNS, run_proxy_dns=True) def test_named_tunnel_alone(self, tmp_path, component_tests_config): run_test_scenario(tmp_path, component_tests_config, CfdModes.NAMED, run_proxy_dns=False) - def test_classic_tunnel_alone(self, tmp_path, component_tests_config): - run_test_scenario(tmp_path, component_tests_config, CfdModes.CLASSIC, run_proxy_dns=False) - def run_test_scenario(tmp_path, component_tests_config, cfd_mode, run_proxy_dns): expect_proxy_dns = run_proxy_dns @@ -31,12 +25,8 @@ def run_test_scenario(tmp_path, component_tests_config, cfd_mode, run_proxy_dns) if cfd_mode == CfdModes.NAMED: expect_tunnel = True - pre_args = ["tunnel"] + pre_args = ["tunnel", "--ha-connections", "1"] args = ["run"] - elif cfd_mode == CfdModes.CLASSIC: - expect_tunnel = True - pre_args = [] - args = [] elif cfd_mode == CfdModes.PROXY_DNS: expect_proxy_dns = True pre_args = [] diff --git a/component-tests/test_quicktunnels.py b/component-tests/test_quicktunnels.py new file mode 100644 index 00000000..fd2cf7aa --- /dev/null +++ b/component-tests/test_quicktunnels.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +from conftest import CfdModes +from constants import METRICS_PORT +from util import LOGGER, start_cloudflared, wait_tunnel_ready, get_quicktunnel_url, send_requests + +class TestQuickTunnels: + def test_quick_tunnel(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False) + LOGGER.debug(config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["--hello-world"], new_process=True): + wait_tunnel_ready(require_min_connections=1) + url = get_quicktunnel_url() + send_requests(url, 3, True) + + def test_quick_tunnel_url(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=False) + LOGGER.debug(config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["--url", f"http://localhost:{METRICS_PORT}/"], new_process=True): + wait_tunnel_ready(require_min_connections=1) + url = get_quicktunnel_url() + send_requests(url+"/ready", 3, True) + + def test_quick_tunnel_proxy_dns_url(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=True) + LOGGER.debug(config) + failed_start = start_cloudflared(tmp_path, config, cfd_args=["--url", f"http://localhost:{METRICS_PORT}/"], expect_success=False) + assert failed_start.returncode == 1, "Expected cloudflared to fail to run with `proxy-dns` and `hello-world`" + + def test_quick_tunnel_proxy_dns_hello_world(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.QUICK, run_proxy_dns=True) + LOGGER.debug(config) + failed_start = start_cloudflared(tmp_path, config, cfd_args=["--hello-world"], expect_success=False) + assert failed_start.returncode == 1, "Expected cloudflared to fail to run with `proxy-dns` and `url`" diff --git a/component-tests/test_reconnect.py b/component-tests/test_reconnect.py index e125845a..ece68bdc 100644 --- a/component-tests/test_reconnect.py +++ b/component-tests/test_reconnect.py @@ -13,7 +13,7 @@ from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connecte @flaky(max_runs=3, min_passes=1) class TestReconnect: - default_ha_conns = 4 + default_ha_conns = 1 default_reconnect_secs = 15 extra_config = { "stdin-control": True, @@ -29,17 +29,10 @@ class TestReconnect: @pytest.mark.parametrize("protocol", protocols()) def test_named_reconnect(self, tmp_path, component_tests_config, protocol): config = component_tests_config(self._extra_config(protocol)) - with start_cloudflared(tmp_path, config, new_process=True, allow_input=True, capture_output=False) as cloudflared: + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, allow_input=True, capture_output=False) as cloudflared: # Repeat the test multiple times because some issues only occur after multiple reconnects self.assert_reconnect(config, cloudflared, 5) - def test_classic_reconnect(self, tmp_path, component_tests_config): - extra_config = copy.copy(self.extra_config) - extra_config["hello-world"] = True - config = component_tests_config(additional_config=extra_config, cfd_mode=CfdModes.CLASSIC) - with start_cloudflared(tmp_path, config, cfd_args=[], new_process=True, allow_input=True, capture_output=False) as cloudflared: - self.assert_reconnect(config, cloudflared, 1) - def send_reconnect(self, cloudflared, secs): # Although it is recommended to use the Popen.communicate method, we cannot # use it because it blocks on reading stdout and stderr until EOF is reached diff --git a/component-tests/test_tail.py b/component-tests/test_tail.py new file mode 100644 index 00000000..732bee69 --- /dev/null +++ b/component-tests/test_tail.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +import asyncio +import json +import pytest +import requests +import websockets +from websockets.client import connect, WebSocketClientProtocol +from conftest import CfdModes +from constants import MAX_RETRIES, BACKOFF_SECS +from retrying import retry +from cli import CloudflaredCli +from util import LOGGER, start_cloudflared, write_config, wait_tunnel_ready + +class TestTail: + @pytest.mark.asyncio + async def test_start_stop_streaming(self, tmp_path, component_tests_config): + """ + Validates that a websocket connection to management.argotunnel.com/logs can be opened + with the access token and start and stop streaming on-demand. + """ + print("test_start_stop_streaming") + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + url = cfd_cli.get_management_wsurl("logs", config, config_path) + async with connect(url, open_timeout=5, close_timeout=3) as websocket: + await websocket.send('{"type": "start_streaming"}') + await websocket.send('{"type": "stop_streaming"}') + await websocket.send('{"type": "start_streaming"}') + await websocket.send('{"type": "stop_streaming"}') + + @pytest.mark.asyncio + async def test_streaming_logs(self, tmp_path, component_tests_config): + """ + Validates that a streaming logs connection will stream logs + """ + print("test_streaming_logs") + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + url = cfd_cli.get_management_wsurl("logs", config, config_path) + async with connect(url, open_timeout=5, close_timeout=5) as websocket: + # send start_streaming + await websocket.send('{"type": "start_streaming"}') + # send some http requests to the tunnel to trigger some logs + await generate_and_validate_http_events(websocket, config.get_url(), 10) + # send stop_streaming + await websocket.send('{"type": "stop_streaming"}') + + @pytest.mark.asyncio + async def test_streaming_logs_filters(self, tmp_path, component_tests_config): + """ + Validates that a streaming logs connection will stream logs + but not http when filters applied. + """ + print("test_streaming_logs_filters") + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + url = cfd_cli.get_management_wsurl("logs", config, config_path) + async with connect(url, open_timeout=5, close_timeout=5) as websocket: + # send start_streaming with tcp logs only + await websocket.send(json.dumps({ + "type": "start_streaming", + "filters": { + "events": ["tcp"], + "level": "debug" + } + })) + # don't expect any http logs + await generate_and_validate_no_log_event(websocket, config.get_url()) + # send stop_streaming + await websocket.send('{"type": "stop_streaming"}') + + @pytest.mark.asyncio + async def test_streaming_logs_sampling(self, tmp_path, component_tests_config): + """ + Validates that a streaming logs connection will stream logs with sampling. + """ + print("test_streaming_logs_sampling") + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + url = cfd_cli.get_management_wsurl("logs", config, config_path) + async with connect(url, open_timeout=5, close_timeout=5) as websocket: + # send start_streaming with info logs only + await websocket.send(json.dumps({ + "type": "start_streaming", + "filters": { + "sampling": 0.5 + } + })) + # don't expect any http logs + count = await generate_and_validate_http_events(websocket, config.get_url(), 10) + assert count < (10 * 2) # There are typically always two log lines for http requests (request and response) + # send stop_streaming + await websocket.send('{"type": "stop_streaming"}') + + @pytest.mark.asyncio + async def test_streaming_logs_actor_override(self, tmp_path, component_tests_config): + """ + Validates that a streaming logs session can be overriden by the same actor + """ + print("test_streaming_logs_actor_override") + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + url = cfd_cli.get_management_wsurl("logs", config, config_path) + task = asyncio.ensure_future(start_streaming_to_be_remotely_closed(url)) + override_task = asyncio.ensure_future(start_streaming_override(url)) + await asyncio.wait([task, override_task]) + assert task.exception() == None, task.exception() + assert override_task.exception() == None, override_task.exception() + +async def start_streaming_to_be_remotely_closed(url): + async with connect(url, open_timeout=5, close_timeout=5) as websocket: + try: + await websocket.send(json.dumps({"type": "start_streaming"})) + await asyncio.sleep(10) + assert websocket.closed, "expected this request to be forcibly closed by the override" + except websockets.ConnectionClosed: + # we expect the request to be closed + pass + +async def start_streaming_override(url): + # wait for the first connection to be established + await asyncio.sleep(1) + async with connect(url, open_timeout=5, close_timeout=5) as websocket: + await websocket.send(json.dumps({"type": "start_streaming"})) + await asyncio.sleep(1) + await websocket.send(json.dumps({"type": "stop_streaming"})) + await asyncio.sleep(1) + +# Every http request has two log lines sent +async def generate_and_validate_http_events(websocket: WebSocketClientProtocol, url: str, count_send: int): + for i in range(count_send): + send_request(url) + # There are typically always two log lines for http requests (request and response) + count = 0 + while True: + try: + req_line = await asyncio.wait_for(websocket.recv(), 2) + log_line = json.loads(req_line) + assert log_line["type"] == "logs" + assert log_line["logs"][0]["event"] == "http" + count += 1 + except asyncio.TimeoutError: + # ignore timeout from waiting for recv + break + return count + +# Every http request has two log lines sent +async def generate_and_validate_no_log_event(websocket: WebSocketClientProtocol, url: str): + send_request(url) + try: + # wait for 5 seconds and make sure we hit the timeout and not recv any events + req_line = await asyncio.wait_for(websocket.recv(), 5) + assert req_line == None, "expected no logs for the specified filters" + except asyncio.TimeoutError: + pass + +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) +def send_request(url, headers={}): + with requests.Session() as s: + resp = s.get(url, timeout=BACKOFF_SECS, headers=headers) + assert resp.status_code == 200, f"{url} returned {resp}" + return resp.status_code == 200 \ No newline at end of file diff --git a/component-tests/test_termination.py b/component-tests/test_termination.py index ef12edc8..26f4fea4 100644 --- a/component-tests/test_termination.py +++ b/component-tests/test_termination.py @@ -34,7 +34,7 @@ class TestTermination: def test_graceful_shutdown(self, tmp_path, component_tests_config, signal, protocol): config = component_tests_config(self._extra_config(protocol)) with start_cloudflared( - tmp_path, config, new_process=True, capture_output=False) as cloudflared: + tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, capture_output=False) as cloudflared: wait_tunnel_ready(tunnel_url=config.get_url()) connected = threading.Condition() @@ -56,7 +56,7 @@ class TestTermination: def test_shutdown_once_no_connection(self, tmp_path, component_tests_config, signal, protocol): config = component_tests_config(self._extra_config(protocol)) with start_cloudflared( - tmp_path, config, new_process=True, capture_output=False) as cloudflared: + tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, capture_output=False) as cloudflared: wait_tunnel_ready(tunnel_url=config.get_url()) connected = threading.Condition() @@ -76,7 +76,7 @@ class TestTermination: def test_no_connection_shutdown(self, tmp_path, component_tests_config, signal, protocol): config = component_tests_config(self._extra_config(protocol)) with start_cloudflared( - tmp_path, config, new_process=True, capture_output=False) as cloudflared: + tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], new_process=True, capture_output=False) as cloudflared: wait_tunnel_ready(tunnel_url=config.get_url()) with self.within_grace_period(): self.terminate_by_signal(cloudflared, signal) diff --git a/component-tests/test_tunnel.py b/component-tests/test_tunnel.py new file mode 100644 index 00000000..8266157c --- /dev/null +++ b/component-tests/test_tunnel.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +import requests +from conftest import CfdModes +from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS +from retrying import retry +from cli import CloudflaredCli +from util import LOGGER, write_config, start_cloudflared, wait_tunnel_ready, send_requests +import platform + +class TestTunnel: + '''Test tunnels with no ingress rules from config.yaml but ingress rules from CLI only''' + + def test_tunnel_hello_world(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), + require_min_connections=1) + + """ + test_get_host_details does the following: + 1. It gets a management token from Tunnelstore using cloudflared tail token + 2. It gets the connector_id after starting a cloudflare tunnel + 3. It sends a request to the management host with the connector_id and management token + 4. Asserts that the response has a hostname and ip. + """ + def test_get_host_details(self, tmp_path, component_tests_config): + # TUN-7377 : wait_tunnel_ready does not work properly in windows. + # Skipping this test for windows for now and will address it as part of tun-7377 + if platform.system() == "Windows": + return + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + headers = {} + headers["Content-Type"] = "application/json" + config_path = write_config(tmp_path, config.full_config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1", "--label" , "test"], cfd_args=["run", "--hello-world"], new_process=True): + wait_tunnel_ready(tunnel_url=config.get_url(), + require_min_connections=1) + cfd_cli = CloudflaredCli(config, config_path, LOGGER) + connector_id = cfd_cli.get_connector_id(config)[0] + url = cfd_cli.get_management_url("host_details", config, config_path) + resp = send_request(url, headers=headers) + + # Assert response json. + assert resp.status_code == 200, "Expected cloudflared to return 200 for host details" + assert resp.json()["hostname"] == "custom:test", "Expected cloudflared to return hostname" + assert resp.json()["ip"] != "", "Expected cloudflared to return ip" + assert resp.json()["connector_id"] == connector_id, "Expected cloudflared to return connector_id" + + + def test_tunnel_url(self, tmp_path, component_tests_config): + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run", "--url", f"http://localhost:{METRICS_PORT}/"], new_process=True): + wait_tunnel_ready(require_min_connections=1) + send_requests(config.get_url()+"/ready", 3, True) + + def test_tunnel_no_ingress(self, tmp_path, component_tests_config): + ''' + Running a tunnel with no ingress rules provided from either config.yaml or CLI will still work but return 503 + for all incoming requests. + ''' + config = component_tests_config(cfd_mode=CfdModes.NAMED, run_proxy_dns=False, provide_ingress=False) + LOGGER.debug(config) + with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run"], new_process=True): + wait_tunnel_ready(require_min_connections=1) + resp = send_request(config.get_url()+"/") + assert resp.status_code == 503, "Expected cloudflared to return 503 for all requests with no ingress defined" + resp = send_request(config.get_url()+"/test") + assert resp.status_code == 503, "Expected cloudflared to return 503 for all requests with no ingress defined" + + +@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000) +def send_request(url, headers={}): + with requests.Session() as s: + return s.get(url, timeout=BACKOFF_SECS, headers=headers) diff --git a/component-tests/util.py b/component-tests/util.py index 02a9f1fd..41a008c6 100644 --- a/component-tests/util.py +++ b/component-tests/util.py @@ -9,6 +9,7 @@ import pytest import requests import yaml +import json from retrying import retry from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS @@ -35,7 +36,7 @@ def write_config(directory, config): def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel"], new_process=False, - allow_input=False, capture_output=True, root=False, skip_config_flag=False): + allow_input=False, capture_output=True, root=False, skip_config_flag=False, expect_success=True): config_path = None if not skip_config_flag: @@ -46,8 +47,7 @@ def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel if new_process: return run_cloudflared_background(cmd, allow_input, capture_output) # By setting check=True, it will raise an exception if the process exits with non-zero exit code - return subprocess.run(cmd, check=True, capture_output=capture_output) - + return subprocess.run(cmd, check=expect_success, capture_output=capture_output) def cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root): cmd = [] @@ -77,7 +77,18 @@ def run_cloudflared_background(cmd, allow_input, capture_output): cfd.terminate() if capture_output: LOGGER.info(f"cloudflared log: {cfd.stderr.read()}") + +def get_quicktunnel_url(): + quicktunnel_url = f'http://localhost:{METRICS_PORT}/quicktunnel' + with requests.Session() as s: + resp = send_request(s, quicktunnel_url, True) + + hostname = resp.json()["hostname"] + assert hostname, \ + f"Quicktunnel endpoint returned {hostname} but we expected a url" + + return f"https://{hostname}" def wait_tunnel_ready(tunnel_url=None, require_min_connections=1, cfd_logs=None): try: @@ -95,13 +106,14 @@ def inner_wait_tunnel_ready(tunnel_url=None, require_min_connections=1): with requests.Session() as s: resp = send_request(s, metrics_url, True) - assert resp.json()["readyConnections"] >= require_min_connections, \ + ready_connections = resp.json()["readyConnections"] + + assert ready_connections >= require_min_connections, \ f"Ready endpoint returned {resp.json()} but we expect at least {require_min_connections} connections" if tunnel_url is not None: send_request(s, tunnel_url, True) - def _log_cloudflared_logs(cfd_logs): log_file = cfd_logs if os.path.isdir(cfd_logs): diff --git a/config/configuration.go b/config/configuration.go index 34a35612..73d45fbc 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -39,8 +39,6 @@ var ( ) const ( - DefaultCredentialFile = "cert.pem" - // BastionFlag is to enable bastion, or jump host, operation BastionFlag = "bastion" ) @@ -391,7 +389,8 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe log.Debug().Msgf("Loading configuration from %s", configFile) file, err := os.Open(configFile) if err != nil { - if os.IsNotExist(err) { + // If does not exist and config file was not specificly specified then return ErrNoConfigFile found. + if os.IsNotExist(err) && !c.IsSet("config") { err = ErrNoConfigFile } return nil, "", err diff --git a/connection/connection.go b/connection/connection.go index cf17d506..25d796dd 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "math" + "net" "net/http" "strconv" "strings" @@ -142,6 +143,7 @@ type TCPRequest struct { LBProbe bool FlowID string CfTraceID string + ConnIndex uint8 } // ReadWriteAcker is a readwriter with the ability to Acknowledge to the downstream (edge) that the origin has @@ -196,9 +198,55 @@ func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) erro return h.w.WriteRespHeaders(resp.StatusCode, resp.Header) } +// localProxyConnection emulates an incoming connection to cloudflared as a net.Conn. +// Used when handling a "hijacked" connection from connection.ResponseWriter +type localProxyConnection struct { + io.ReadWriteCloser +} + +func (c *localProxyConnection) Read(b []byte) (int, error) { + return c.ReadWriteCloser.Read(b) +} + +func (c *localProxyConnection) Write(b []byte) (int, error) { + return c.ReadWriteCloser.Write(b) +} + +func (c *localProxyConnection) Close() error { + return c.ReadWriteCloser.Close() +} + +func (c *localProxyConnection) LocalAddr() net.Addr { + // Unused LocalAddr + return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""} +} + +func (c *localProxyConnection) RemoteAddr() net.Addr { + // Unused RemoteAddr + return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""} +} + +func (c *localProxyConnection) SetDeadline(t time.Time) error { + // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld + return nil +} + +func (c *localProxyConnection) SetReadDeadline(t time.Time) error { + // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld + return nil +} + +func (c *localProxyConnection) SetWriteDeadline(t time.Time) error { + // ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld + return nil +} + +// ResponseWriter is the response path for a request back through cloudflared's tunnel. type ResponseWriter interface { WriteRespHeaders(status int, header http.Header) error AddTrailer(trailerName, trailerValue string) + http.ResponseWriter + http.Hijacker io.Writer } diff --git a/connection/connection_test.go b/connection/connection_test.go index 14c20c0d..3d6801e0 100644 --- a/connection/connection_test.go +++ b/connection/connection_test.go @@ -10,6 +10,7 @@ import ( "github.com/rs/zerolog" + "github.com/cloudflare/cloudflared/stream" "github.com/cloudflare/cloudflared/tracing" tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" "github.com/cloudflare/cloudflared/websocket" @@ -140,7 +141,7 @@ func wsEchoEndpoint(w ResponseWriter, r *http.Request) error { }() originConn := &echoPipe{reader: readPipe, writer: writePipe} - websocket.Stream(wsConn, originConn, &log) + stream.Pipe(wsConn, originConn, &log) cancel() wsConn.Close() return nil @@ -178,7 +179,7 @@ func wsFlakyEndpoint(w ResponseWriter, r *http.Request) error { closedAfter := time.Millisecond * time.Duration(rand.Intn(50)) originConn := &flakyConn{closeAt: time.Now().Add(closedAfter)} - websocket.Stream(wsConn, originConn, &log) + stream.Pipe(wsConn, originConn, &log) cancel() wsConn.Close() return nil diff --git a/connection/control.go b/connection/control.go index a7fe1ac9..5cde0204 100644 --- a/connection/control.go +++ b/connection/control.go @@ -2,13 +2,13 @@ package connection import ( "context" - "fmt" "io" "net" "time" "github.com/rs/zerolog" + "github.com/cloudflare/cloudflared/management" tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" ) @@ -85,7 +85,7 @@ func (c *controlStream) ServeControlStream( return err } - c.observer.logServerInfo(c.connIndex, registrationDetails.Location, c.edgeAddress, fmt.Sprintf("Connection %s registered", registrationDetails.UUID)) + c.observer.logConnected(registrationDetails.UUID, c.connIndex, registrationDetails.Location, c.edgeAddress, c.protocol) c.observer.sendConnectedEvent(c.connIndex, c.protocol, registrationDetails.Location) c.connectedFuse.Connected() @@ -116,7 +116,11 @@ func (c *controlStream) waitForUnregister(ctx context.Context, rpcClient NamedTu c.observer.sendUnregisteringEvent(c.connIndex) rpcClient.GracefulShutdown(ctx, c.gracePeriod) - c.observer.log.Info().Uint8(LogFieldConnIndex, c.connIndex).Msg("Unregistered tunnel connection") + c.observer.log.Info(). + Int(management.EventTypeKey, int(management.Cloudflared)). + Uint8(LogFieldConnIndex, c.connIndex). + IPAddr(LogFieldIPAddress, c.edgeAddress). + Msg("Unregistered tunnel connection") } func (c *controlStream) IsStopped() bool { diff --git a/connection/h2mux.go b/connection/h2mux.go index d8291b62..4de983bc 100644 --- a/connection/h2mux.go +++ b/connection/h2mux.go @@ -1,46 +1,17 @@ package connection import ( - "context" - "io" - "net" - "net/http" "time" - "github.com/pkg/errors" "github.com/rs/zerolog" - "golang.org/x/sync/errgroup" "github.com/cloudflare/cloudflared/h2mux" - "github.com/cloudflare/cloudflared/tracing" - tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" - "github.com/cloudflare/cloudflared/websocket" ) const ( - muxerTimeout = 5 * time.Second - openStreamTimeout = 30 * time.Second + muxerTimeout = 5 * time.Second ) -type h2muxConnection struct { - orchestrator Orchestrator - gracePeriod time.Duration - muxerConfig *MuxerConfig - muxer *h2mux.Muxer - // connectionID is only used by metrics, and prometheus requires labels to be string - connIndexStr string - connIndex uint8 - - observer *Observer - gracefulShutdownC <-chan struct{} - stoppedGracefully bool - - log *zerolog.Logger - - // newRPCClientFunc allows us to mock RPCs during testing - newRPCClientFunc func(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient -} - type MuxerConfig struct { HeartbeatInterval time.Duration MaxHeartbeats uint64 @@ -59,220 +30,3 @@ func (mc *MuxerConfig) H2MuxerConfig(h h2mux.MuxedStreamHandler, log *zerolog.Lo CompressionQuality: mc.CompressionSetting, } } - -// NewTunnelHandler returns a TunnelHandler, origin LAN IP and error -func NewH2muxConnection( - orchestrator Orchestrator, - gracePeriod time.Duration, - muxerConfig *MuxerConfig, - edgeConn net.Conn, - connIndex uint8, - observer *Observer, - gracefulShutdownC <-chan struct{}, - log *zerolog.Logger, -) (*h2muxConnection, error, bool) { - h := &h2muxConnection{ - orchestrator: orchestrator, - gracePeriod: gracePeriod, - muxerConfig: muxerConfig, - connIndexStr: uint8ToString(connIndex), - connIndex: connIndex, - observer: observer, - gracefulShutdownC: gracefulShutdownC, - newRPCClientFunc: newRegistrationRPCClient, - log: log, - } - - // Establish a muxed connection with the edge - // Client mux handshake with agent server - muxer, err := h2mux.Handshake(edgeConn, edgeConn, *muxerConfig.H2MuxerConfig(h, observer.logTransport), h2mux.ActiveStreams) - if err != nil { - recoverable := isHandshakeErrRecoverable(err, connIndex, observer) - return nil, err, recoverable - } - h.muxer = muxer - return h, nil, false -} - -func (h *h2muxConnection) ServeNamedTunnel(ctx context.Context, namedTunnel *NamedTunnelProperties, connOptions *tunnelpogs.ConnectionOptions, connectedFuse ConnectedFuse) error { - errGroup, serveCtx := errgroup.WithContext(ctx) - errGroup.Go(func() error { - return h.serveMuxer(serveCtx) - }) - - errGroup.Go(func() error { - if err := h.registerNamedTunnel(serveCtx, namedTunnel, connOptions); err != nil { - return err - } - connectedFuse.Connected() - return nil - }) - - errGroup.Go(func() error { - h.controlLoop(serveCtx, connectedFuse, true) - return nil - }) - - err := errGroup.Wait() - if err == errMuxerStopped { - if h.stoppedGracefully { - return nil - } - h.observer.log.Info().Uint8(LogFieldConnIndex, h.connIndex).Msg("Unexpected muxer shutdown") - } - return err -} - -func (h *h2muxConnection) ServeClassicTunnel(ctx context.Context, classicTunnel *ClassicTunnelProperties, credentialManager CredentialManager, registrationOptions *tunnelpogs.RegistrationOptions, connectedFuse ConnectedFuse) error { - errGroup, serveCtx := errgroup.WithContext(ctx) - errGroup.Go(func() error { - return h.serveMuxer(serveCtx) - }) - - errGroup.Go(func() (err error) { - defer func() { - if err == nil { - connectedFuse.Connected() - } - }() - if classicTunnel.UseReconnectToken && connectedFuse.IsConnected() { - err := h.reconnectTunnel(ctx, credentialManager, classicTunnel, registrationOptions) - if err == nil { - return nil - } - // log errors and proceed to RegisterTunnel - h.observer.log.Err(err). - Uint8(LogFieldConnIndex, h.connIndex). - Msg("Couldn't reconnect connection. Re-registering it instead.") - } - return h.registerTunnel(ctx, credentialManager, classicTunnel, registrationOptions) - }) - - errGroup.Go(func() error { - h.controlLoop(serveCtx, connectedFuse, false) - return nil - }) - - err := errGroup.Wait() - if err == errMuxerStopped { - if h.stoppedGracefully { - return nil - } - h.observer.log.Info().Uint8(LogFieldConnIndex, h.connIndex).Msg("Unexpected muxer shutdown") - } - return err -} - -func (h *h2muxConnection) serveMuxer(ctx context.Context) error { - // All routines should stop when muxer finish serving. When muxer is shutdown - // gracefully, it doesn't return an error, so we need to return errMuxerShutdown - // here to notify other routines to stop - err := h.muxer.Serve(ctx) - if err == nil { - return errMuxerStopped - } - return err -} - -func (h *h2muxConnection) controlLoop(ctx context.Context, connectedFuse ConnectedFuse, isNamedTunnel bool) { - updateMetricsTicker := time.NewTicker(h.muxerConfig.MetricsUpdateFreq) - defer updateMetricsTicker.Stop() - var shutdownCompleted <-chan struct{} - for { - select { - case <-h.gracefulShutdownC: - if connectedFuse.IsConnected() { - h.unregister(isNamedTunnel) - } - h.stoppedGracefully = true - h.gracefulShutdownC = nil - shutdownCompleted = h.muxer.Shutdown() - - case <-shutdownCompleted: - return - - case <-ctx.Done(): - // UnregisterTunnel blocks until the RPC call returns - if !h.stoppedGracefully && connectedFuse.IsConnected() { - h.unregister(isNamedTunnel) - } - h.muxer.Shutdown() - // don't wait for shutdown to finish when context is closed, this is the hard termination path - return - - case <-updateMetricsTicker.C: - h.observer.metrics.updateMuxerMetrics(h.connIndexStr, h.muxer.Metrics()) - } - } -} - -func (h *h2muxConnection) newRPCStream(ctx context.Context, rpcName rpcName) (*h2mux.MuxedStream, error) { - openStreamCtx, openStreamCancel := context.WithTimeout(ctx, openStreamTimeout) - defer openStreamCancel() - stream, err := h.muxer.OpenRPCStream(openStreamCtx) - if err != nil { - return nil, err - } - return stream, nil -} - -func (h *h2muxConnection) ServeStream(stream *h2mux.MuxedStream) error { - respWriter := &h2muxRespWriter{stream} - - req, reqErr := h.newRequest(stream) - if reqErr != nil { - respWriter.WriteErrorResponse() - return reqErr - } - - var sourceConnectionType = TypeHTTP - if websocket.IsWebSocketUpgrade(req) { - sourceConnectionType = TypeWebsocket - } - - originProxy, err := h.orchestrator.GetOriginProxy() - if err != nil { - respWriter.WriteErrorResponse() - return err - } - - err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, h.log), sourceConnectionType == TypeWebsocket) - if err != nil { - respWriter.WriteErrorResponse() - } - return err -} - -func (h *h2muxConnection) newRequest(stream *h2mux.MuxedStream) (*http.Request, error) { - req, err := http.NewRequest("GET", "http://localhost:8080", h2mux.MuxedStreamReader{MuxedStream: stream}) - if err != nil { - return nil, errors.Wrap(err, "Unexpected error from http.NewRequest") - } - err = H2RequestHeadersToH1Request(stream.Headers, req) - if err != nil { - return nil, errors.Wrap(err, "invalid request received") - } - return req, nil -} - -type h2muxRespWriter struct { - *h2mux.MuxedStream -} - -func (rp *h2muxRespWriter) AddTrailer(trailerName, trailerValue string) { - // do nothing. we don't support trailers over h2mux -} - -func (rp *h2muxRespWriter) WriteRespHeaders(status int, header http.Header) error { - headers := H1ResponseToH2ResponseHeaders(status, header) - headers = append(headers, h2mux.Header{Name: ResponseMetaHeader, Value: responseMetaHeaderOrigin}) - return rp.WriteHeaders(headers) -} - -func (rp *h2muxRespWriter) WriteErrorResponse() { - _ = rp.WriteHeaders([]h2mux.Header{ - {Name: ":status", Value: "502"}, - {Name: ResponseMetaHeader, Value: responseMetaHeaderCfd}, - }) - _, _ = rp.Write([]byte("502 Bad Gateway")) -} diff --git a/connection/h2mux_test.go b/connection/h2mux_test.go deleted file mode 100644 index 53c28227..00000000 --- a/connection/h2mux_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package connection - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "strconv" - "sync" - "testing" - "time" - - "github.com/gobwas/ws/wsutil" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/cloudflare/cloudflared/h2mux" -) - -var ( - testMuxerConfig = &MuxerConfig{ - HeartbeatInterval: time.Second * 5, - MaxHeartbeats: 5, - CompressionSetting: 0, - MetricsUpdateFreq: time.Second * 5, - } -) - -func newH2MuxConnection(t require.TestingT) (*h2muxConnection, *h2mux.Muxer) { - edgeConn, originConn := net.Pipe() - edgeMuxChan := make(chan *h2mux.Muxer) - go func() { - edgeMuxConfig := h2mux.MuxerConfig{ - Log: &log, - Handler: h2mux.MuxedStreamFunc(func(stream *h2mux.MuxedStream) error { - // we only expect RPC traffic in client->edge direction, provide minimal support for mocking - require.True(t, stream.IsRPCStream()) - return stream.WriteHeaders([]h2mux.Header{ - {Name: ":status", Value: "200"}, - }) - }), - } - edgeMux, err := h2mux.Handshake(edgeConn, edgeConn, edgeMuxConfig, h2mux.ActiveStreams) - require.NoError(t, err) - edgeMuxChan <- edgeMux - }() - var connIndex = uint8(0) - testObserver := NewObserver(&log, &log) - h2muxConn, err, _ := NewH2muxConnection(testOrchestrator, testGracePeriod, testMuxerConfig, originConn, connIndex, testObserver, nil, &log) - require.NoError(t, err) - return h2muxConn, <-edgeMuxChan -} - -func TestServeStreamHTTP(t *testing.T) { - tests := []testRequest{ - { - name: "ok", - endpoint: "/ok", - expectedStatus: http.StatusOK, - expectedBody: []byte(http.StatusText(http.StatusOK)), - }, - { - name: "large_file", - endpoint: "/large_file", - expectedStatus: http.StatusOK, - expectedBody: testLargeResp, - }, - { - name: "Bad request", - endpoint: "/400", - expectedStatus: http.StatusBadRequest, - expectedBody: []byte(http.StatusText(http.StatusBadRequest)), - }, - { - name: "Internal server error", - endpoint: "/500", - expectedStatus: http.StatusInternalServerError, - expectedBody: []byte(http.StatusText(http.StatusInternalServerError)), - }, - { - name: "Proxy error", - endpoint: "/error", - expectedStatus: http.StatusBadGateway, - expectedBody: nil, - isProxyError: true, - }, - } - - ctx, cancel := context.WithCancel(context.Background()) - h2muxConn, edgeMux := newH2MuxConnection(t) - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - _ = edgeMux.Serve(ctx) - }() - go func() { - defer wg.Done() - err := h2muxConn.serveMuxer(ctx) - require.Error(t, err) - }() - - for _, test := range tests { - headers := []h2mux.Header{ - { - Name: ":path", - Value: test.endpoint, - }, - } - stream, err := edgeMux.OpenStream(ctx, headers, nil) - require.NoError(t, err) - require.True(t, hasHeader(stream, ":status", strconv.Itoa(test.expectedStatus))) - - if test.isProxyError { - assert.True(t, hasHeader(stream, ResponseMetaHeader, responseMetaHeaderCfd)) - } else { - assert.True(t, hasHeader(stream, ResponseMetaHeader, responseMetaHeaderOrigin)) - body := make([]byte, len(test.expectedBody)) - _, err = stream.Read(body) - require.NoError(t, err) - require.Equal(t, test.expectedBody, body) - } - } - cancel() - wg.Wait() -} - -func TestServeStreamWS(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - h2muxConn, edgeMux := newH2MuxConnection(t) - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - edgeMux.Serve(ctx) - }() - go func() { - defer wg.Done() - err := h2muxConn.serveMuxer(ctx) - require.Error(t, err) - }() - - headers := []h2mux.Header{ - { - Name: ":path", - Value: "/ws/echo", - }, - { - Name: "connection", - Value: "upgrade", - }, - { - Name: "upgrade", - Value: "websocket", - }, - } - - readPipe, writePipe := io.Pipe() - stream, err := edgeMux.OpenStream(ctx, headers, readPipe) - require.NoError(t, err) - - require.True(t, hasHeader(stream, ":status", strconv.Itoa(http.StatusSwitchingProtocols))) - assert.True(t, hasHeader(stream, ResponseMetaHeader, responseMetaHeaderOrigin)) - - data := []byte("test websocket") - err = wsutil.WriteClientBinary(writePipe, data) - require.NoError(t, err) - - respBody, err := wsutil.ReadServerBinary(stream) - require.NoError(t, err) - require.Equal(t, data, respBody, fmt.Sprintf("Expect %s, got %s", string(data), string(respBody))) - - cancel() - wg.Wait() -} - -func TestGracefulShutdownH2Mux(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - h2muxConn, edgeMux := newH2MuxConnection(t) - - shutdownC := make(chan struct{}) - unregisteredC := make(chan struct{}) - h2muxConn.gracefulShutdownC = shutdownC - h2muxConn.newRPCClientFunc = func(_ context.Context, _ io.ReadWriteCloser, _ *zerolog.Logger) NamedTunnelRPCClient { - return &mockNamedTunnelRPCClient{ - registered: nil, - unregistered: unregisteredC, - } - } - - var wg sync.WaitGroup - wg.Add(3) - go func() { - defer wg.Done() - _ = edgeMux.Serve(ctx) - }() - go func() { - defer wg.Done() - _ = h2muxConn.serveMuxer(ctx) - }() - - go func() { - defer wg.Done() - h2muxConn.controlLoop(ctx, &mockConnectedFuse{}, true) - }() - - time.Sleep(100 * time.Millisecond) - close(shutdownC) - - select { - case <-unregisteredC: - break // ok - case <-time.Tick(time.Second): - assert.Fail(t, "timed out waiting for control loop to unregister") - } - - cancel() - wg.Wait() - - assert.True(t, h2muxConn.stoppedGracefully) - assert.Nil(t, h2muxConn.gracefulShutdownC) -} - -func hasHeader(stream *h2mux.MuxedStream, name, val string) bool { - for _, header := range stream.Headers { - if header.Name == name && header.Value == val { - return true - } - } - return false -} - -func benchmarkServeStreamHTTPSimple(b *testing.B, test testRequest) { - ctx, cancel := context.WithCancel(context.Background()) - h2muxConn, edgeMux := newH2MuxConnection(b) - - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - edgeMux.Serve(ctx) - }() - go func() { - defer wg.Done() - err := h2muxConn.serveMuxer(ctx) - require.Error(b, err) - }() - - headers := []h2mux.Header{ - { - Name: ":path", - Value: test.endpoint, - }, - } - - body := make([]byte, len(test.expectedBody)) - b.ResetTimer() - for i := 0; i < b.N; i++ { - b.StartTimer() - stream, openstreamErr := edgeMux.OpenStream(ctx, headers, nil) - _, readBodyErr := stream.Read(body) - b.StopTimer() - - require.NoError(b, openstreamErr) - assert.True(b, hasHeader(stream, ResponseMetaHeader, responseMetaHeaderOrigin)) - require.True(b, hasHeader(stream, ":status", strconv.Itoa(http.StatusOK))) - require.NoError(b, readBodyErr) - require.Equal(b, test.expectedBody, body) - } - - cancel() - wg.Wait() -} - -func BenchmarkServeStreamHTTPSimple(b *testing.B) { - test := testRequest{ - name: "ok", - endpoint: "/ok", - expectedStatus: http.StatusOK, - expectedBody: []byte(http.StatusText(http.StatusOK)), - } - - benchmarkServeStreamHTTPSimple(b, test) -} - -func BenchmarkServeStreamHTTPLargeFile(b *testing.B) { - test := testRequest{ - name: "large_file", - endpoint: "/large_file", - expectedStatus: http.StatusOK, - expectedBody: testLargeResp, - } - - benchmarkServeStreamHTTPSimple(b, test) -} diff --git a/connection/http2.go b/connection/http2.go index a6756b1d..4945855b 100644 --- a/connection/http2.go +++ b/connection/http2.go @@ -1,6 +1,7 @@ package connection import ( + "bufio" "context" gojson "encoding/json" "fmt" @@ -115,52 +116,53 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + var requestErr error switch connType { case TypeControlStream: - if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator); err != nil { - c.controlStreamErr = err - c.log.Error().Err(err) - respWriter.WriteErrorResponse() + requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator) + if requestErr != nil { + c.controlStreamErr = requestErr } case TypeConfiguration: - if err := c.handleConfigurationUpdate(respWriter, r); err != nil { - c.log.Error().Err(err) - respWriter.WriteErrorResponse() - } + requestErr = c.handleConfigurationUpdate(respWriter, r) case TypeWebsocket, TypeHTTP: stripWebsocketUpgradeHeader(r) // Check for tracing on request - tr := tracing.NewTracedHTTPRequest(r, c.log) + tr := tracing.NewTracedHTTPRequest(r, c.connIndex, c.log) if err := originProxy.ProxyHTTP(respWriter, tr, connType == TypeWebsocket); err != nil { - err := fmt.Errorf("Failed to proxy HTTP: %w", err) - c.log.Error().Err(err) - respWriter.WriteErrorResponse() + requestErr = fmt.Errorf("Failed to proxy HTTP: %w", err) } case TypeTCP: host, err := getRequestHost(r) if err != nil { - err := fmt.Errorf(`cloudflared received a warp-routing request with an empty host value: %w`, err) - c.log.Error().Err(err) - respWriter.WriteErrorResponse() + requestErr = fmt.Errorf(`cloudflared received a warp-routing request with an empty host value: %w`, err) + break } rws := NewHTTPResponseReadWriterAcker(respWriter, r) - if err := originProxy.ProxyTCP(r.Context(), rws, &TCPRequest{ + requestErr = originProxy.ProxyTCP(r.Context(), rws, &TCPRequest{ Dest: host, CFRay: FindCfRayHeader(r), LBProbe: IsLBProbeRequest(r), CfTraceID: r.Header.Get(tracing.TracerContextName), - }); err != nil { - respWriter.WriteErrorResponse() - } + ConnIndex: c.connIndex, + }) default: - err := fmt.Errorf("Received unknown connection type: %s", connType) - c.log.Error().Err(err) - respWriter.WriteErrorResponse() + requestErr = fmt.Errorf("Received unknown connection type: %s", connType) + } + + if requestErr != nil { + c.log.Error().Err(requestErr).Msg("failed to serve incoming request") + + // WriteErrorResponse will return false if status was already written. we need to abort handler. + if !respWriter.WriteErrorResponse() { + c.log.Debug().Msg("Handler aborted due to failure to write error response after status already sent") + panic(http.ErrAbortHandler) + } } } @@ -196,6 +198,9 @@ type http2RespWriter struct { flusher http.Flusher shouldFlush bool statusWritten bool + respHeaders http.Header + hijackedMutex sync.Mutex + hijackedv bool log *zerolog.Logger } @@ -216,6 +221,7 @@ func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, l w: w, flusher: flusher, shouldFlush: connType.shouldFlush(), + respHeaders: make(http.Header), log: log, }, nil } @@ -230,6 +236,10 @@ func (rp *http2RespWriter) AddTrailer(trailerName, trailerValue string) { } func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) error { + if rp.hijacked() { + rp.log.Warn().Msg("WriteRespHeaders after hijack") + return nil + } dest := rp.w.Header() userHeaders := make(http.Header, len(header)) for name, values := range header { @@ -275,9 +285,58 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro return nil } -func (rp *http2RespWriter) WriteErrorResponse() { +func (rp *http2RespWriter) Header() http.Header { + return rp.respHeaders +} + +func (rp *http2RespWriter) WriteHeader(status int) { + if rp.hijacked() { + rp.log.Warn().Msg("WriteHeader after hijack") + return + } + rp.WriteRespHeaders(status, rp.respHeaders) +} + +func (rp *http2RespWriter) hijacked() bool { + rp.hijackedMutex.Lock() + defer rp.hijackedMutex.Unlock() + return rp.hijackedv +} + +func (rp *http2RespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if !rp.statusWritten { + return nil, nil, fmt.Errorf("status not yet written before attempting to hijack connection") + } + // Make sure to flush anything left in the buffer before hijacking + if rp.shouldFlush { + rp.flusher.Flush() + } + rp.hijackedMutex.Lock() + defer rp.hijackedMutex.Unlock() + if rp.hijackedv { + return nil, nil, http.ErrHijacked + } + rp.hijackedv = true + conn := &localProxyConnection{rp} + // We return the http2RespWriter here because we want to make sure that we flush after every write + // otherwise the HTTP2 write buffer waits a few seconds before sending. + readWriter := bufio.NewReadWriter( + bufio.NewReader(rp), + bufio.NewWriter(rp), + ) + return conn, readWriter, nil +} + +func (rp *http2RespWriter) WriteErrorResponse() bool { + if rp.statusWritten { + return false + } + rp.setResponseMetaHeader(responseMetaHeaderCfd) rp.w.WriteHeader(http.StatusBadGateway) + rp.statusWritten = true + + return true } func (rp *http2RespWriter) setResponseMetaHeader(value string) { diff --git a/connection/observer.go b/connection/observer.go index 7429b32c..c6cb895e 100644 --- a/connection/observer.go +++ b/connection/observer.go @@ -4,12 +4,17 @@ import ( "net" "strings" + "github.com/google/uuid" "github.com/rs/zerolog" + + "github.com/cloudflare/cloudflared/management" ) const ( + LogFieldConnectionID = "connection" LogFieldLocation = "location" LogFieldIPAddress = "ip" + LogFieldProtocol = "protocol" observerChannelBufferSize = 16 ) @@ -41,13 +46,16 @@ func (o *Observer) RegisterSink(sink EventSink) { o.addSinkChan <- sink } -func (o *Observer) logServerInfo(connIndex uint8, location string, address net.IP, msg string) { +func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) { o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location}) o.log.Info(). + Int(management.EventTypeKey, int(management.Cloudflared)). + Str(LogFieldConnectionID, connectionID.String()). Uint8(LogFieldConnIndex, connIndex). Str(LogFieldLocation, location). IPAddr(LogFieldIPAddress, address). - Msg(msg) + Str(LogFieldProtocol, protocol.String()). + Msg("Registered tunnel connection") o.metrics.registerServerLocation(uint8ToString(connIndex), location) } diff --git a/connection/protocol.go b/connection/protocol.go index ddf9bb76..ecc367b4 100644 --- a/connection/protocol.go +++ b/connection/protocol.go @@ -1,7 +1,6 @@ package connection import ( - "errors" "fmt" "hash/fnv" "sync" @@ -13,7 +12,7 @@ import ( ) const ( - AvailableProtocolFlagMessage = "Available protocols: 'auto' - automatically chooses the best protocol over time (the default; and also the recommended one); 'quic' - based on QUIC, relying on UDP egress to Cloudflare edge; 'http2' - using Go's HTTP2 library, relying on TCP egress to Cloudflare edge; 'h2mux' - Cloudflare's implementation of HTTP/2, deprecated" + AvailableProtocolFlagMessage = "Available protocols: 'auto' - automatically chooses the best protocol over time (the default; and also the recommended one); 'quic' - based on QUIC, relying on UDP egress to Cloudflare edge; 'http2' - using Go's HTTP2 library, relying on TCP egress to Cloudflare edge" // edgeH2muxTLSServerName is the server name to establish h2mux connection with edge edgeH2muxTLSServerName = "cftunnel.com" // edgeH2TLSServerName is the server name to establish http2 connection with edge @@ -21,43 +20,32 @@ const ( // edgeQUICServerName is the server name to establish quic connection with edge. edgeQUICServerName = "quic.cftunnel.com" AutoSelectFlag = "auto" + // SRV and TXT record resolution TTL + ResolveTTL = time.Hour ) var ( - // ProtocolList represents a list of supported protocols for communication with the edge. - ProtocolList = []Protocol{H2mux, HTTP2, HTTP2Warp, QUIC, QUICWarp} + // ProtocolList represents a list of supported protocols for communication with the edge + // in order of precedence for remote percentage fetcher. + ProtocolList = []Protocol{QUIC, HTTP2} ) type Protocol int64 const ( - // H2mux protocol can be used both with Classic and Named Tunnels. . - H2mux Protocol = iota - // HTTP2 is used only with named tunnels. It's more efficient than H2mux for L4 proxying. - HTTP2 - // QUIC is used only with named tunnels. + // HTTP2 using golang HTTP2 library for edge connections. + HTTP2 Protocol = iota + // QUIC using quic-go for edge connections. QUIC - // HTTP2Warp is used only with named tunnels. It's useful for warp-routing where we don't want to fallback to - // H2mux on HTTP2 failure to connect. - HTTP2Warp - //QUICWarp is used only with named tunnels. It's useful for warp-routing where we want to fallback to HTTP2 but - // don't want HTTP2 to fallback to H2mux - QUICWarp ) // Fallback returns the fallback protocol and whether the protocol has a fallback func (p Protocol) fallback() (Protocol, bool) { switch p { - case H2mux: - return 0, false case HTTP2: - return H2mux, true - case HTTP2Warp: return 0, false case QUIC: return HTTP2, true - case QUICWarp: - return HTTP2Warp, true default: return 0, false } @@ -65,11 +53,9 @@ func (p Protocol) fallback() (Protocol, bool) { func (p Protocol) String() string { switch p { - case H2mux: - return "h2mux" - case HTTP2, HTTP2Warp: + case HTTP2: return "http2" - case QUIC, QUICWarp: + case QUIC: return "quic" default: return fmt.Sprintf("unknown protocol") @@ -78,15 +64,11 @@ func (p Protocol) String() string { func (p Protocol) TLSSettings() *TLSSettings { switch p { - case H2mux: - return &TLSSettings{ - ServerName: edgeH2muxTLSServerName, - } - case HTTP2, HTTP2Warp: + case HTTP2: return &TLSSettings{ ServerName: edgeH2TLSServerName, } - case QUIC, QUICWarp: + case QUIC: return &TLSSettings{ ServerName: edgeQUICServerName, NextProtos: []string{"argotunnel"}, @@ -106,6 +88,7 @@ type ProtocolSelector interface { Fallback() (Protocol, bool) } +// staticProtocolSelector will not provide a different protocol for Fallback type staticProtocolSelector struct { current Protocol } @@ -115,10 +98,11 @@ func (s *staticProtocolSelector) Current() Protocol { } func (s *staticProtocolSelector) Fallback() (Protocol, bool) { - return 0, false + return s.current, false } -type autoProtocolSelector struct { +// remoteProtocolSelector will fetch a list of remote protocols to provide for edge discovery +type remoteProtocolSelector struct { lock sync.RWMutex current Protocol @@ -127,23 +111,21 @@ type autoProtocolSelector struct { protocolPool []Protocol switchThreshold int32 - fetchFunc PercentageFetcher + fetchFunc edgediscovery.PercentageFetcher refreshAfter time.Time ttl time.Duration log *zerolog.Logger - needPQ bool } -func newAutoProtocolSelector( +func newRemoteProtocolSelector( current Protocol, protocolPool []Protocol, switchThreshold int32, - fetchFunc PercentageFetcher, + fetchFunc edgediscovery.PercentageFetcher, ttl time.Duration, log *zerolog.Logger, - needPQ bool, -) *autoProtocolSelector { - return &autoProtocolSelector{ +) *remoteProtocolSelector { + return &remoteProtocolSelector{ current: current, protocolPool: protocolPool, switchThreshold: switchThreshold, @@ -151,11 +133,10 @@ func newAutoProtocolSelector( refreshAfter: time.Now().Add(ttl), ttl: ttl, log: log, - needPQ: needPQ, } } -func (s *autoProtocolSelector) Current() Protocol { +func (s *remoteProtocolSelector) Current() Protocol { s.lock.Lock() defer s.lock.Unlock() if time.Now().Before(s.refreshAfter) { @@ -173,7 +154,13 @@ func (s *autoProtocolSelector) Current() Protocol { return s.current } -func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThreshold int32) (Protocol, error) { +func (s *remoteProtocolSelector) Fallback() (Protocol, bool) { + s.lock.RLock() + defer s.lock.RUnlock() + return s.current.fallback() +} + +func getProtocol(protocolPool []Protocol, fetchFunc edgediscovery.PercentageFetcher, switchThreshold int32) (Protocol, error) { protocolPercentages, err := fetchFunc() if err != nil { return 0, err @@ -185,112 +172,78 @@ func getProtocol(protocolPool []Protocol, fetchFunc PercentageFetcher, switchThr } } - return protocolPool[len(protocolPool)-1], nil + // Default to first index in protocolPool list + return protocolPool[0], nil } -func (s *autoProtocolSelector) Fallback() (Protocol, bool) { +// defaultProtocolSelector will allow for a protocol to have a fallback +type defaultProtocolSelector struct { + lock sync.RWMutex + current Protocol +} + +func newDefaultProtocolSelector( + current Protocol, +) *defaultProtocolSelector { + return &defaultProtocolSelector{ + current: current, + } +} + +func (s *defaultProtocolSelector) Current() Protocol { + s.lock.Lock() + defer s.lock.Unlock() + return s.current +} + +func (s *defaultProtocolSelector) Fallback() (Protocol, bool) { s.lock.RLock() defer s.lock.RUnlock() - if s.needPQ { - return 0, false - } return s.current.fallback() } -type PercentageFetcher func() (edgediscovery.ProtocolPercents, error) - func NewProtocolSelector( protocolFlag string, - warpRoutingEnabled bool, - namedTunnel *NamedTunnelProperties, - fetchFunc PercentageFetcher, - ttl time.Duration, - log *zerolog.Logger, + accountTag string, + tunnelTokenProvided bool, needPQ bool, + protocolFetcher edgediscovery.PercentageFetcher, + resolveTTL time.Duration, + log *zerolog.Logger, ) (ProtocolSelector, error) { - // Classic tunnel is only supported with h2mux - if namedTunnel == nil { - if needPQ { - return nil, errors.New("Classic tunnel does not support post-quantum") - } - + // With --post-quantum, we force quic + if needPQ { return &staticProtocolSelector{ - current: H2mux, + current: QUIC, }, nil } - threshold := switchThreshold(namedTunnel.Credentials.AccountTag) - fetchedProtocol, err := getProtocol([]Protocol{QUIC, HTTP2}, fetchFunc, threshold) - if err != nil && protocolFlag == "auto" { - log.Err(err).Msg("Unable to lookup protocol. Defaulting to `http2`. If this fails, you can attempt `--protocol quic` instead.") - if needPQ { - return nil, errors.New("http2 does not support post-quantum") - } - return &staticProtocolSelector{ - current: HTTP2, - }, nil - } - if warpRoutingEnabled { - if protocolFlag == H2mux.String() || fetchedProtocol == H2mux { - log.Warn().Msg("Warp routing is not supported in h2mux protocol. Upgrading to http2 to allow it.") - protocolFlag = HTTP2.String() - fetchedProtocol = HTTP2Warp - } - return selectWarpRoutingProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ) + threshold := switchThreshold(accountTag) + fetchedProtocol, err := getProtocol(ProtocolList, protocolFetcher, threshold) + log.Debug().Msgf("Fetched protocol: %s", fetchedProtocol) + if err != nil { + log.Warn().Msg("Unable to lookup protocol percentage.") + // Falling through here since 'auto' is handled in the switch and failing + // to do the protocol lookup isn't a failure since it can be triggered again + // after the TTL. } - return selectNamedTunnelProtocols(protocolFlag, fetchFunc, ttl, log, threshold, fetchedProtocol, needPQ) -} - -func selectNamedTunnelProtocols( - protocolFlag string, - fetchFunc PercentageFetcher, - ttl time.Duration, - log *zerolog.Logger, - threshold int32, - protocol Protocol, - needPQ bool, -) (ProtocolSelector, error) { // If the user picks a protocol, then we stick to it no matter what. switch protocolFlag { - case H2mux.String(): - return &staticProtocolSelector{current: H2mux}, nil + case "h2mux": + // Any users still requesting h2mux will be upgraded to http2 instead + log.Warn().Msg("h2mux is no longer a supported protocol: upgrading edge connection to http2. Please remove '--protocol h2mux' from runtime arguments to remove this warning.") + return &staticProtocolSelector{current: HTTP2}, nil case QUIC.String(): return &staticProtocolSelector{current: QUIC}, nil case HTTP2.String(): return &staticProtocolSelector{current: HTTP2}, nil - } - - // If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and - // fallback on failures. - if protocolFlag == AutoSelectFlag { - return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log, needPQ), nil - } - - return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage) -} - -func selectWarpRoutingProtocols( - protocolFlag string, - fetchFunc PercentageFetcher, - ttl time.Duration, - log *zerolog.Logger, - threshold int32, - protocol Protocol, - needPQ bool, -) (ProtocolSelector, error) { - // If the user picks a protocol, then we stick to it no matter what. - switch protocolFlag { - case QUIC.String(): - return &staticProtocolSelector{current: QUICWarp}, nil - case HTTP2.String(): - return &staticProtocolSelector{current: HTTP2Warp}, nil - } - - // If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and - // fallback on failures. - if protocolFlag == AutoSelectFlag { - return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log, needPQ), nil + case AutoSelectFlag: + // When a --token is provided, we want to start with QUIC but have fallback to HTTP2 + if tunnelTokenProvided { + return newDefaultProtocolSelector(QUIC), nil + } + return newRemoteProtocolSelector(fetchedProtocol, ProtocolList, threshold, protocolFetcher, resolveTTL, log), nil } return nil, fmt.Errorf("Unknown protocol %s, %s", protocolFlag, AvailableProtocolFlagMessage) diff --git a/connection/protocol_test.go b/connection/protocol_test.go index b3bda44a..12d238c5 100644 --- a/connection/protocol_test.go +++ b/connection/protocol_test.go @@ -3,7 +3,6 @@ package connection import ( "fmt" "testing" - "time" "github.com/stretchr/testify/assert" @@ -11,19 +10,11 @@ import ( ) const ( - testNoTTL = 0 - noWarpRoutingEnabled = false + testNoTTL = 0 + testAccountTag = "testAccountTag" ) -var ( - testNamedTunnelProperties = &NamedTunnelProperties{ - Credentials: Credentials{ - AccountTag: "testAccountTag", - }, - } -) - -func mockFetcher(getError bool, protocolPercent ...edgediscovery.ProtocolPercent) PercentageFetcher { +func mockFetcher(getError bool, protocolPercent ...edgediscovery.ProtocolPercent) edgediscovery.PercentageFetcher { return func() (edgediscovery.ProtocolPercents, error) { if getError { return nil, fmt.Errorf("failed to fetch percentage") @@ -37,7 +28,7 @@ type dynamicMockFetcher struct { err error } -func (dmf *dynamicMockFetcher) fetch() PercentageFetcher { +func (dmf *dynamicMockFetcher) fetch() edgediscovery.PercentageFetcher { return func() (edgediscovery.ProtocolPercents, error) { return dmf.protocolPercents, dmf.err } @@ -45,181 +36,58 @@ func (dmf *dynamicMockFetcher) fetch() PercentageFetcher { func TestNewProtocolSelector(t *testing.T) { tests := []struct { - name string - protocol string - expectedProtocol Protocol - hasFallback bool - expectedFallback Protocol - warpRoutingEnabled bool - namedTunnelConfig *NamedTunnelProperties - fetchFunc PercentageFetcher - wantErr bool + name string + protocol string + tunnelTokenProvided bool + needPQ bool + expectedProtocol Protocol + hasFallback bool + expectedFallback Protocol + wantErr bool }{ { - name: "classic tunnel", - protocol: "h2mux", - expectedProtocol: H2mux, - namedTunnelConfig: nil, + name: "named tunnel with unknown protocol", + protocol: "unknown", + wantErr: true, }, { - name: "named tunnel over h2mux", - protocol: "h2mux", - expectedProtocol: H2mux, - fetchFunc: func() (edgediscovery.ProtocolPercents, error) { return nil, nil }, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "named tunnel over http2", - protocol: "http2", - expectedProtocol: HTTP2, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}), - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "named tunnel http2 disabled still gets http2 because it is manually picked", - protocol: "http2", - expectedProtocol: HTTP2, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}), - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "named tunnel quic disabled still gets quic because it is manually picked", - protocol: "quic", - expectedProtocol: QUIC, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}), - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "named tunnel quic and http2 disabled", - protocol: AutoSelectFlag, - expectedProtocol: H2mux, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}), - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "named tunnel quic disabled", - protocol: AutoSelectFlag, + name: "named tunnel with h2mux: force to http2", + protocol: "h2mux", expectedProtocol: HTTP2, - // Hasfallback true is because if http2 fails, then we further fallback to h2mux. - hasFallback: true, - expectedFallback: H2mux, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}), - namedTunnelConfig: testNamedTunnelProperties, }, { - name: "named tunnel auto all http2 disabled", - protocol: AutoSelectFlag, - expectedProtocol: H2mux, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}), - namedTunnelConfig: testNamedTunnelProperties, + name: "named tunnel with http2: no fallback", + protocol: "http2", + expectedProtocol: HTTP2, }, { - name: "named tunnel auto to h2mux", - protocol: AutoSelectFlag, - expectedProtocol: H2mux, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}), - namedTunnelConfig: testNamedTunnelProperties, + name: "named tunnel with auto: quic", + protocol: AutoSelectFlag, + expectedProtocol: QUIC, + hasFallback: true, + expectedFallback: HTTP2, }, { - name: "named tunnel auto to http2", - protocol: AutoSelectFlag, - expectedProtocol: HTTP2, - hasFallback: true, - expectedFallback: H2mux, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}), - namedTunnelConfig: testNamedTunnelProperties, + name: "named tunnel (post quantum)", + protocol: AutoSelectFlag, + needPQ: true, + expectedProtocol: QUIC, }, { - name: "named tunnel auto to quic", - protocol: AutoSelectFlag, - expectedProtocol: QUIC, - hasFallback: true, - expectedFallback: HTTP2, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}), - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing requesting h2mux", - protocol: "h2mux", - expectedProtocol: HTTP2Warp, - hasFallback: false, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing requesting h2mux picks HTTP2 even if http2 percent is -1", - protocol: "h2mux", - expectedProtocol: HTTP2Warp, - hasFallback: false, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing http2", - protocol: "http2", - expectedProtocol: HTTP2Warp, - hasFallback: false, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing quic", - protocol: AutoSelectFlag, - expectedProtocol: QUICWarp, - hasFallback: true, - expectedFallback: HTTP2Warp, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing auto", - protocol: AutoSelectFlag, - expectedProtocol: HTTP2Warp, - hasFallback: false, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - name: "warp routing auto- quic", - protocol: AutoSelectFlag, - expectedProtocol: QUICWarp, - hasFallback: true, - expectedFallback: HTTP2Warp, - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}), - warpRoutingEnabled: true, - namedTunnelConfig: testNamedTunnelProperties, - }, - { - // None named tunnel can only use h2mux, so specifying an unknown protocol is not an error - name: "classic tunnel unknown protocol", - protocol: "unknown", - expectedProtocol: H2mux, - }, - { - name: "named tunnel unknown protocol", - protocol: "unknown", - fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}), - namedTunnelConfig: testNamedTunnelProperties, - wantErr: true, - }, - { - name: "named tunnel fetch error", - protocol: AutoSelectFlag, - fetchFunc: mockFetcher(true), - namedTunnelConfig: testNamedTunnelProperties, - expectedProtocol: HTTP2, - wantErr: false, + name: "named tunnel (post quantum) w/http2", + protocol: "http2", + needPQ: true, + expectedProtocol: QUIC, }, } + fetcher := dynamicMockFetcher{ + protocolPercents: edgediscovery.ProtocolPercents{}, + } + for _, test := range tests { t.Run(test.name, func(t *testing.T) { - selector, err := NewProtocolSelector(test.protocol, test.warpRoutingEnabled, test.namedTunnelConfig, test.fetchFunc, testNoTTL, &log, false) + selector, err := NewProtocolSelector(test.protocol, testAccountTag, test.tunnelTokenProvided, test.needPQ, fetcher.fetch(), ResolveTTL, &log) if test.wantErr { assert.Error(t, err, fmt.Sprintf("test %s failed", test.name)) } else { @@ -237,15 +105,15 @@ func TestNewProtocolSelector(t *testing.T) { func TestAutoProtocolSelectorRefresh(t *testing.T) { fetcher := dynamicMockFetcher{} - selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false) + selector, err := NewProtocolSelector(AutoSelectFlag, testAccountTag, false, false, fetcher.fetch(), testNoTTL, &log) assert.NoError(t, err) - assert.Equal(t, H2mux, selector.Current()) + assert.Equal(t, QUIC, selector.Current()) fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}} assert.Equal(t, HTTP2, selector.Current()) fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}} - assert.Equal(t, H2mux, selector.Current()) + assert.Equal(t, QUIC, selector.Current()) fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}} assert.Equal(t, HTTP2, selector.Current()) @@ -255,10 +123,10 @@ func TestAutoProtocolSelectorRefresh(t *testing.T) { fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}} fetcher.err = nil - assert.Equal(t, H2mux, selector.Current()) + assert.Equal(t, QUIC, selector.Current()) fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}} - assert.Equal(t, H2mux, selector.Current()) + assert.Equal(t, QUIC, selector.Current()) fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}} assert.Equal(t, QUIC, selector.Current()) @@ -267,7 +135,7 @@ func TestAutoProtocolSelectorRefresh(t *testing.T) { func TestHTTP2ProtocolSelectorRefresh(t *testing.T) { fetcher := dynamicMockFetcher{} // Since the user chooses http2 on purpose, we always stick to it. - selector, err := NewProtocolSelector("http2", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log, false) + selector, err := NewProtocolSelector(HTTP2.String(), testAccountTag, false, false, fetcher.fetch(), testNoTTL, &log) assert.NoError(t, err) assert.Equal(t, HTTP2, selector.Current()) @@ -294,13 +162,12 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) { assert.Equal(t, HTTP2, selector.Current()) } -func TestProtocolSelectorRefreshTTL(t *testing.T) { +func TestAutoProtocolSelectorNoRefreshWithToken(t *testing.T) { fetcher := dynamicMockFetcher{} - fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}} - selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log, false) + selector, err := NewProtocolSelector(AutoSelectFlag, testAccountTag, true, false, fetcher.fetch(), testNoTTL, &log) assert.NoError(t, err) assert.Equal(t, QUIC, selector.Current()) - fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 0}} + fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}} assert.Equal(t, QUIC, selector.Current()) } diff --git a/connection/quic.go b/connection/quic.go index 645daf31..e5e47d2f 100644 --- a/connection/quic.go +++ b/connection/quic.go @@ -1,6 +1,7 @@ package connection import ( + "bufio" "context" "crypto/tls" "fmt" @@ -24,6 +25,7 @@ import ( "github.com/cloudflare/cloudflared/datagramsession" "github.com/cloudflare/cloudflared/ingress" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/packet" quicpogs "github.com/cloudflare/cloudflared/quic" "github.com/cloudflare/cloudflared/tracing" @@ -60,12 +62,14 @@ type QUICConnection struct { packetRouter *ingress.PacketRouter controlStreamHandler ControlStreamHandler connOptions *tunnelpogs.ConnectionOptions + connIndex uint8 } // NewQUICConnection returns a new instance of QUICConnection. func NewQUICConnection( quicConfig *quic.Config, edgeAddr net.Addr, + localAddr net.IP, connIndex uint8, tlsConfig *tls.Config, orchestrator Orchestrator, @@ -74,13 +78,15 @@ func NewQUICConnection( logger *zerolog.Logger, packetRouterConfig *ingress.GlobalRouterConfig, ) (*QUICConnection, error) { - udpConn, err := createUDPConnForConnIndex(connIndex, logger) + udpConn, err := createUDPConnForConnIndex(connIndex, localAddr, logger) if err != nil { return nil, err } session, err := quic.Dial(udpConn, edgeAddr, edgeAddr.String(), tlsConfig, quicConfig) if err != nil { + // close the udp server socket in case of error connecting to the edge + udpConn.Close() return nil, &EdgeQuicDialError{Cause: err} } @@ -104,6 +110,7 @@ func NewQUICConnection( packetRouter: packetRouter, controlStreamHandler: controlStreamHandler, connOptions: connOptions, + connIndex: connIndex, }, nil } @@ -191,7 +198,11 @@ func (q *QUICConnection) runStream(quicStream quic.Stream) { // A call to close will simulate a close to the read-side, which will fail subsequent reads. noCloseStream := &nopCloserReadWriter{ReadWriteCloser: stream} if err := q.handleStream(ctx, noCloseStream); err != nil { - q.logger.Err(err).Msg("Failed to handle QUIC stream") + q.logger.Debug().Err(err).Msg("Failed to handle QUIC stream") + + // if we received an error at this level, then close write side of stream with an error, which will result in + // RST_STREAM frame. + quicStream.CancelWrite(0) } } @@ -224,43 +235,61 @@ func (q *QUICConnection) handleDataStream(ctx context.Context, stream *quicpogs. return err } - if err := q.dispatchRequest(ctx, stream, err, request); err != nil { - _ = stream.WriteConnectResponseData(err) + if err, connectResponseSent := q.dispatchRequest(ctx, stream, err, request); err != nil { q.logger.Err(err).Str("type", request.Type.String()).Str("dest", request.Dest).Msg("Request failed") + + // if the connectResponse was already sent and we had an error, we need to propagate it up, so that the stream is + // closed with an RST_STREAM frame + if connectResponseSent { + return err + } + + if writeRespErr := stream.WriteConnectResponseData(err); writeRespErr != nil { + return writeRespErr + } } return nil } -func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *quicpogs.RequestServerStream, err error, request *quicpogs.ConnectRequest) error { +// dispatchRequest will dispatch the request depending on the type and returns an error if it occurs. +// More importantly, it also tells if the during processing of the request the ConnectResponse metadata was sent downstream. +// This is important since it informs +func (q *QUICConnection) dispatchRequest(ctx context.Context, stream *quicpogs.RequestServerStream, err error, request *quicpogs.ConnectRequest) (error, bool) { originProxy, err := q.orchestrator.GetOriginProxy() if err != nil { - return err + return err, false } switch request.Type { case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket: - tracedReq, err := buildHTTPRequest(ctx, request, stream, q.logger) + tracedReq, err := buildHTTPRequest(ctx, request, stream, q.connIndex, q.logger) if err != nil { - return err + return err, false } w := newHTTPResponseAdapter(stream) - return originProxy.ProxyHTTP(w, tracedReq, request.Type == quicpogs.ConnectionTypeWebsocket) + return originProxy.ProxyHTTP(&w, tracedReq, request.Type == quicpogs.ConnectionTypeWebsocket), w.connectResponseSent case quicpogs.ConnectionTypeTCP: - rwa := &streamReadWriteAcker{stream} + rwa := &streamReadWriteAcker{RequestServerStream: stream} metadata := request.MetadataMap() return originProxy.ProxyTCP(ctx, rwa, &TCPRequest{ Dest: request.Dest, FlowID: metadata[QUICMetadataFlowID], CfTraceID: metadata[tracing.TracerContextName], - }) + ConnIndex: q.connIndex, + }), rwa.connectResponseSent + default: + return errors.Errorf("unsupported error type: %s", request.Type), false } - return nil } func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) error { - return rpcStream.Serve(q, q, q.logger) + if err := rpcStream.Serve(q, q, q.logger); err != nil { + q.logger.Err(err).Msg("failed handling RPC stream") + } + + return nil } // RegisterUdpSession is the RPC method invoked by edge to register and run a session @@ -270,11 +299,12 @@ func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid. attribute.String("session-id", sessionID.String()), attribute.String("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)), )) + log := q.logger.With().Int(management.EventTypeKey, int(management.UDP)).Logger() // Each session is a series of datagram from an eyeball to a dstIP:dstPort. // (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket. originProxy, err := ingress.DialUDP(dstIP, dstPort) if err != nil { - q.logger.Err(err).Msgf("Failed to create udp proxy to %s:%d", dstIP, dstPort) + log.Err(err).Msgf("Failed to create udp proxy to %s:%d", dstIP, dstPort) tracing.EndWithErrorStatus(registerSpan, err) return nil, err } @@ -285,14 +315,18 @@ func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid. session, err := q.sessionManager.RegisterSession(ctx, sessionID, originProxy) if err != nil { - q.logger.Err(err).Str("sessionID", sessionID.String()).Msgf("Failed to register udp session") + log.Err(err).Str("sessionID", sessionID.String()).Msgf("Failed to register udp session") tracing.EndWithErrorStatus(registerSpan, err) return nil, err } go q.serveUDPSession(session, closeAfterIdleHint) - q.logger.Debug().Str("sessionID", sessionID.String()).Str("src", originProxy.LocalAddr().String()).Str("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)).Msgf("Registered session") + log.Debug(). + Str("sessionID", sessionID.String()). + Str("src", originProxy.LocalAddr().String()). + Str("dst", fmt.Sprintf("%s:%d", dstIP, dstPort)). + Msgf("Registered session") tracing.End(registerSpan) resp := tunnelpogs.RegisterUdpSessionResponse{ @@ -313,7 +347,10 @@ func (q *QUICConnection) serveUDPSession(session *datagramsession.Session, close q.closeUDPSession(ctx, session.ID, "terminated without error") } } - q.logger.Debug().Err(err).Str("sessionID", session.ID.String()).Msg("Session terminated") + q.logger.Debug().Err(err). + Int(management.EventTypeKey, int(management.UDP)). + Str("sessionID", session.ID.String()). + Msg("Session terminated") } // closeUDPSession first unregisters the session from session manager, then it tries to unregister from edge @@ -323,7 +360,9 @@ func (q *QUICConnection) closeUDPSession(ctx context.Context, sessionID uuid.UUI if err != nil { // Log this at debug because this is not an error if session was closed due to lost connection // with edge - q.logger.Debug().Err(err).Str("sessionID", sessionID.String()). + q.logger.Debug().Err(err). + Int(management.EventTypeKey, int(management.UDP)). + Str("sessionID", sessionID.String()). Msgf("Failed to open quic stream to unregister udp session with edge") return } @@ -355,31 +394,39 @@ func (q *QUICConnection) UpdateConfiguration(ctx context.Context, version int32, // the client. type streamReadWriteAcker struct { *quicpogs.RequestServerStream + connectResponseSent bool } // AckConnection acks response back to the proxy. func (s *streamReadWriteAcker) AckConnection(tracePropagation string) error { - metadata := quicpogs.Metadata{ - Key: tracing.CanonicalCloudflaredTracingHeader, - Val: tracePropagation, + metadata := []quicpogs.Metadata{} + // Only add tracing if provided by origintunneld + if tracePropagation != "" { + metadata = append(metadata, quicpogs.Metadata{ + Key: tracing.CanonicalCloudflaredTracingHeader, + Val: tracePropagation, + }) } - return s.WriteConnectResponseData(nil, metadata) + s.connectResponseSent = true + return s.WriteConnectResponseData(nil, metadata...) } // httpResponseAdapter translates responses written by the HTTP Proxy into ones that can be used in QUIC. type httpResponseAdapter struct { *quicpogs.RequestServerStream + headers http.Header + connectResponseSent bool } func newHTTPResponseAdapter(s *quicpogs.RequestServerStream) httpResponseAdapter { - return httpResponseAdapter{s} + return httpResponseAdapter{RequestServerStream: s, headers: make(http.Header)} } -func (hrw httpResponseAdapter) AddTrailer(trailerName, trailerValue string) { +func (hrw *httpResponseAdapter) AddTrailer(trailerName, trailerValue string) { // we do not support trailers over QUIC } -func (hrw httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error { +func (hrw *httpResponseAdapter) WriteRespHeaders(status int, header http.Header) error { metadata := make([]quicpogs.Metadata, 0) metadata = append(metadata, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(status)}) for k, vv := range header { @@ -388,17 +435,41 @@ func (hrw httpResponseAdapter) WriteRespHeaders(status int, header http.Header) metadata = append(metadata, quicpogs.Metadata{Key: httpHeaderKey, Val: v}) } } + return hrw.WriteConnectResponseData(nil, metadata...) } -func (hrw httpResponseAdapter) WriteErrorResponse(err error) { +func (hrw *httpResponseAdapter) Header() http.Header { + return hrw.headers +} + +func (hrw *httpResponseAdapter) WriteHeader(status int) { + hrw.WriteRespHeaders(status, hrw.headers) +} + +func (hrw *httpResponseAdapter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + conn := &localProxyConnection{hrw.ReadWriteCloser} + readWriter := bufio.NewReadWriter( + bufio.NewReader(hrw.ReadWriteCloser), + bufio.NewWriter(hrw.ReadWriteCloser), + ) + return conn, readWriter, nil +} + +func (hrw *httpResponseAdapter) WriteErrorResponse(err error) { hrw.WriteConnectResponseData(err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)}) } +func (hrw *httpResponseAdapter) WriteConnectResponseData(respErr error, metadata ...quicpogs.Metadata) error { + hrw.connectResponseSent = true + return hrw.RequestServerStream.WriteConnectResponseData(respErr, metadata...) +} + func buildHTTPRequest( ctx context.Context, connectRequest *quicpogs.ConnectRequest, body io.ReadCloser, + connIndex uint8, log *zerolog.Logger, ) (*tracing.TracedHTTPRequest, error) { metadata := connectRequest.MetadataMap() @@ -442,7 +513,7 @@ func buildHTTPRequest( stripWebsocketUpgradeHeader(req) // Check for tracing on request - tracedReq := tracing.NewTracedHTTPRequest(req, log) + tracedReq := tracing.NewTracedHTTPRequest(req, connIndex, log) return tracedReq, err } @@ -523,13 +594,17 @@ func (rp *muxerWrapper) Close() error { return nil } -func createUDPConnForConnIndex(connIndex uint8, logger *zerolog.Logger) (*net.UDPConn, error) { +func createUDPConnForConnIndex(connIndex uint8, localIP net.IP, logger *zerolog.Logger) (*net.UDPConn, error) { portMapMutex.Lock() defer portMapMutex.Unlock() + if localIP == nil { + localIP = net.IPv4zero + } + // if port was not set yet, it will be zero, so bind will randomly allocate one. if port, ok := portForConnIndex[connIndex]; ok { - udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: port}) + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: localIP, Port: port}) // if there wasn't an error, or if port was 0 (independently of error or not, just return) if err == nil { return udpConn, nil @@ -539,7 +614,7 @@ func createUDPConnForConnIndex(connIndex uint8, logger *zerolog.Logger) (*net.UD } // if we reached here, then there was an error or port as not been allocated it. - udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: localIP, Port: 0}) if err == nil { udpAddr, ok := (udpConn.LocalAddr()).(*net.UDPAddr) if !ok { diff --git a/connection/quic_test.go b/connection/quic_test.go index db330190..d1dcaa68 100644 --- a/connection/quic_test.go +++ b/connection/quic_test.go @@ -485,7 +485,7 @@ func TestBuildHTTPRequest(t *testing.T) { for _, test := range tests { test := test // capture range variable t.Run(test.name, func(t *testing.T) { - req, err := buildHTTPRequest(context.Background(), test.connectRequest, test.body, &log) + req, err := buildHTTPRequest(context.Background(), test.connectRequest, test.body, 0, &log) assert.NoError(t, err) test.req = test.req.WithContext(req.Context()) assert.Equal(t, test.req, req.Request) @@ -572,7 +572,7 @@ func TestNopCloserReadWriterCloseAfterEOF(t *testing.T) { func TestCreateUDPConnReuseSourcePort(t *testing.T) { logger := zerolog.Nop() - conn, err := createUDPConnForConnIndex(0, &logger) + conn, err := createUDPConnForConnIndex(0, nil, &logger) require.NoError(t, err) getPortFunc := func(conn *net.UDPConn) int { @@ -586,17 +586,17 @@ func TestCreateUDPConnReuseSourcePort(t *testing.T) { conn.Close() // should get the same port as before. - conn, err = createUDPConnForConnIndex(0, &logger) + conn, err = createUDPConnForConnIndex(0, nil, &logger) require.NoError(t, err) require.Equal(t, initialPort, getPortFunc(conn)) // new index, should get a different port - conn1, err := createUDPConnForConnIndex(1, &logger) + conn1, err := createUDPConnForConnIndex(1, nil, &logger) require.NoError(t, err) require.NotEqual(t, initialPort, getPortFunc(conn1)) // not closing the conn and trying to obtain a new conn for same index should give a different random port - conn, err = createUDPConnForConnIndex(0, &logger) + conn, err = createUDPConnForConnIndex(0, nil, &logger) require.NoError(t, err) require.NotEqual(t, initialPort, getPortFunc(conn)) } @@ -716,6 +716,7 @@ func testQUICConnection(udpListenerAddr net.Addr, t *testing.T, index uint8) *QU qc, err := NewQUICConnection( testQUICConfig, udpListenerAddr, + nil, index, tlsClientConfig, &mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}}, diff --git a/connection/rpc.go b/connection/rpc.go index 384be0dc..e01031f9 100644 --- a/connection/rpc.go +++ b/connection/rpc.go @@ -2,7 +2,6 @@ package connection import ( "context" - "fmt" "io" "net" "time" @@ -152,178 +151,3 @@ const ( unregister rpcName = "unregister" authenticate rpcName = " authenticate" ) - -func (h *h2muxConnection) registerTunnel(ctx context.Context, credentialSetter CredentialManager, classicTunnel *ClassicTunnelProperties, registrationOptions *tunnelpogs.RegistrationOptions) error { - h.observer.sendRegisteringEvent(registrationOptions.ConnectionID) - - stream, err := h.newRPCStream(ctx, register) - if err != nil { - return err - } - rpcClient := NewTunnelServerClient(ctx, stream, h.observer.log) - defer rpcClient.Close() - - _ = h.logServerInfo(ctx, rpcClient) - registration := rpcClient.client.RegisterTunnel( - ctx, - classicTunnel.OriginCert, - classicTunnel.Hostname, - registrationOptions, - ) - if registrationErr := registration.DeserializeError(); registrationErr != nil { - // RegisterTunnel RPC failure - return h.processRegisterTunnelError(registrationErr, register) - } - - credentialSetter.SetEventDigest(h.connIndex, registration.EventDigest) - return h.processRegistrationSuccess(registration, register, credentialSetter, classicTunnel) -} - -type CredentialManager interface { - ReconnectToken() ([]byte, error) - EventDigest(connID uint8) ([]byte, error) - SetEventDigest(connID uint8, digest []byte) - ConnDigest(connID uint8) ([]byte, error) - SetConnDigest(connID uint8, digest []byte) -} - -func (h *h2muxConnection) processRegistrationSuccess( - registration *tunnelpogs.TunnelRegistration, - name rpcName, - credentialManager CredentialManager, classicTunnel *ClassicTunnelProperties, -) error { - for _, logLine := range registration.LogLines { - h.observer.log.Info().Msg(logLine) - } - - if registration.TunnelID != "" { - h.observer.metrics.tunnelsHA.AddTunnelID(h.connIndex, registration.TunnelID) - h.observer.log.Info().Msgf("Each HA connection's tunnel IDs: %v", h.observer.metrics.tunnelsHA.String()) - } - - credentialManager.SetConnDigest(h.connIndex, registration.ConnDigest) - h.observer.metrics.userHostnamesCounts.WithLabelValues(registration.Url).Inc() - - h.observer.log.Info().Msgf("Route propagating, it may take up to 1 minute for your new route to become functional") - h.observer.metrics.regSuccess.WithLabelValues(string(name)).Inc() - return nil -} - -func (h *h2muxConnection) processRegisterTunnelError(err tunnelpogs.TunnelRegistrationError, name rpcName) error { - if err.Error() == DuplicateConnectionError { - h.observer.metrics.regFail.WithLabelValues("dup_edge_conn", string(name)).Inc() - return errDuplicationConnection - } - h.observer.metrics.regFail.WithLabelValues("server_error", string(name)).Inc() - return ServerRegisterTunnelError{ - Cause: err, - Permanent: err.IsPermanent(), - } -} - -func (h *h2muxConnection) reconnectTunnel(ctx context.Context, credentialManager CredentialManager, classicTunnel *ClassicTunnelProperties, registrationOptions *tunnelpogs.RegistrationOptions) error { - token, err := credentialManager.ReconnectToken() - if err != nil { - return err - } - eventDigest, err := credentialManager.EventDigest(h.connIndex) - if err != nil { - return err - } - connDigest, err := credentialManager.ConnDigest(h.connIndex) - if err != nil { - return err - } - - h.observer.log.Debug().Msg("initiating RPC stream to reconnect") - stream, err := h.newRPCStream(ctx, register) - if err != nil { - return err - } - rpcClient := NewTunnelServerClient(ctx, stream, h.observer.log) - defer rpcClient.Close() - - _ = h.logServerInfo(ctx, rpcClient) - registration := rpcClient.client.ReconnectTunnel( - ctx, - token, - eventDigest, - connDigest, - classicTunnel.Hostname, - registrationOptions, - ) - if registrationErr := registration.DeserializeError(); registrationErr != nil { - // ReconnectTunnel RPC failure - return h.processRegisterTunnelError(registrationErr, reconnect) - } - return h.processRegistrationSuccess(registration, reconnect, credentialManager, classicTunnel) -} - -func (h *h2muxConnection) logServerInfo(ctx context.Context, rpcClient *tunnelServerClient) error { - // Request server info without blocking tunnel registration; must use capnp library directly. - serverInfoPromise := tunnelrpc.TunnelServer{Client: rpcClient.client.Client}.GetServerInfo(ctx, func(tunnelrpc.TunnelServer_getServerInfo_Params) error { - return nil - }) - serverInfoMessage, err := serverInfoPromise.Result().Struct() - if err != nil { - h.observer.log.Err(err).Msg("Failed to retrieve server information") - return err - } - serverInfo, err := tunnelpogs.UnmarshalServerInfo(serverInfoMessage) - if err != nil { - h.observer.log.Err(err).Msg("Failed to retrieve server information") - return err - } - h.observer.logServerInfo(h.connIndex, serverInfo.LocationName, net.IP{}, "Connection established") - return nil -} - -func (h *h2muxConnection) registerNamedTunnel( - ctx context.Context, - namedTunnel *NamedTunnelProperties, - connOptions *tunnelpogs.ConnectionOptions, -) error { - stream, err := h.newRPCStream(ctx, register) - if err != nil { - return err - } - rpcClient := h.newRPCClientFunc(ctx, stream, h.observer.log) - defer rpcClient.Close() - - registrationDetails, err := rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, nil, h.observer) - if err != nil { - return err - } - h.observer.logServerInfo(h.connIndex, registrationDetails.Location, nil, fmt.Sprintf("Connection %s registered", registrationDetails.UUID)) - h.observer.sendConnectedEvent(h.connIndex, H2mux, registrationDetails.Location) - - return nil -} - -func (h *h2muxConnection) unregister(isNamedTunnel bool) { - h.observer.sendUnregisteringEvent(h.connIndex) - - unregisterCtx, cancel := context.WithTimeout(context.Background(), h.gracePeriod) - defer cancel() - - stream, err := h.newRPCStream(unregisterCtx, unregister) - if err != nil { - return - } - defer stream.Close() - - if isNamedTunnel { - rpcClient := h.newRPCClientFunc(unregisterCtx, stream, h.observer.log) - defer rpcClient.Close() - - rpcClient.GracefulShutdown(unregisterCtx, h.gracePeriod) - } else { - rpcClient := NewTunnelServerClient(unregisterCtx, stream, h.observer.log) - defer rpcClient.Close() - - // gracePeriod is encoded in int64 using capnproto - _ = rpcClient.client.UnregisterTunnel(unregisterCtx, h.gracePeriod.Nanoseconds()) - } - - h.observer.log.Info().Uint8(LogFieldConnIndex, h.connIndex).Msg("Unregistered tunnel connection") -} diff --git a/credentials/credentials.go b/credentials/credentials.go new file mode 100644 index 00000000..8d1d8908 --- /dev/null +++ b/credentials/credentials.go @@ -0,0 +1,83 @@ +package credentials + +import ( + "github.com/pkg/errors" + "github.com/rs/zerolog" + + "github.com/cloudflare/cloudflared/cfapi" +) + +const ( + logFieldOriginCertPath = "originCertPath" +) + +type User struct { + cert *OriginCert + certPath string +} + +func (c User) AccountID() string { + return c.cert.AccountID +} + +func (c User) ZoneID() string { + return c.cert.ZoneID +} + +func (c User) APIToken() string { + return c.cert.APIToken +} + +func (c User) CertPath() string { + return c.certPath +} + +// Client uses the user credentials to create a Cloudflare API client +func (c *User) Client(apiURL string, userAgent string, log *zerolog.Logger) (cfapi.Client, error) { + if apiURL == "" { + return nil, errors.New("An api-url was not provided for the Cloudflare API client") + } + client, err := cfapi.NewRESTClient( + apiURL, + c.cert.AccountID, + c.cert.ZoneID, + c.cert.APIToken, + userAgent, + log, + ) + + if err != nil { + return nil, err + } + return client, nil +} + +// Read will load and read the origin cert.pem to load the user credentials +func Read(originCertPath string, log *zerolog.Logger) (*User, error) { + originCertLog := log.With(). + Str(logFieldOriginCertPath, originCertPath). + Logger() + + originCertPath, err := FindOriginCert(originCertPath, &originCertLog) + if err != nil { + return nil, errors.Wrap(err, "Error locating origin cert") + } + blocks, err := readOriginCert(originCertPath) + if err != nil { + return nil, errors.Wrapf(err, "Can't read origin cert from %s", originCertPath) + } + + cert, err := decodeOriginCert(blocks) + if err != nil { + return nil, errors.Wrap(err, "Error decoding origin cert") + } + + if cert.AccountID == "" { + return nil, errors.Errorf(`Origin certificate needs to be refreshed before creating new tunnels.\nDelete %s and run "cloudflared login" to obtain a new cert.`, originCertPath) + } + + return &User{ + cert: cert, + certPath: originCertPath, + }, nil +} diff --git a/credentials/credentials_test.go b/credentials/credentials_test.go new file mode 100644 index 00000000..d9b2d7b7 --- /dev/null +++ b/credentials/credentials_test.go @@ -0,0 +1,38 @@ +package credentials + +import ( + "io/fs" + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCredentialsRead(t *testing.T) { + file, err := os.ReadFile("test-cloudflare-tunnel-cert-json.pem") + require.NoError(t, err) + dir := t.TempDir() + certPath := path.Join(dir, originCertFile) + os.WriteFile(certPath, file, fs.ModePerm) + user, err := Read(certPath, &nopLog) + require.NoError(t, err) + require.Equal(t, certPath, user.CertPath()) + require.Equal(t, "test-service-key", user.APIToken()) + require.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", user.ZoneID()) + require.Equal(t, "abcdabcdabcdabcd1234567890abcdef", user.AccountID()) +} + +func TestCredentialsClient(t *testing.T) { + user := User{ + certPath: "/tmp/cert.pem", + cert: &OriginCert{ + ZoneID: "7b0a4d77dfb881c1a3b7d61ea9443e19", + AccountID: "abcdabcdabcdabcd1234567890abcdef", + APIToken: "test-service-key", + }, + } + client, err := user.Client("example.com", "cloudflared/test", &nopLog) + require.NoError(t, err) + require.NotNil(t, client) +} diff --git a/credentials/origin_cert.go b/credentials/origin_cert.go new file mode 100644 index 00000000..73a59fa3 --- /dev/null +++ b/credentials/origin_cert.go @@ -0,0 +1,130 @@ +package credentials + +import ( + "encoding/json" + "encoding/pem" + "fmt" + "os" + "path/filepath" + + "github.com/mitchellh/go-homedir" + "github.com/rs/zerolog" + + "github.com/cloudflare/cloudflared/config" +) + +const ( + DefaultCredentialFile = "cert.pem" + OriginCertFlag = "origincert" +) + +type namedTunnelToken struct { + ZoneID string `json:"zoneID"` + AccountID string `json:"accountID"` + APIToken string `json:"apiToken"` +} + +type OriginCert struct { + ZoneID string + APIToken string + AccountID string +} + +// FindDefaultOriginCertPath returns the first path that contains a cert.pem file. If none of the +// DefaultConfigSearchDirectories contains a cert.pem file, return empty string +func FindDefaultOriginCertPath() string { + for _, defaultConfigDir := range config.DefaultConfigSearchDirectories() { + originCertPath, _ := homedir.Expand(filepath.Join(defaultConfigDir, DefaultCredentialFile)) + if ok := fileExists(originCertPath); ok { + return originCertPath + } + } + return "" +} + +func decodeOriginCert(blocks []byte) (*OriginCert, error) { + if len(blocks) == 0 { + return nil, fmt.Errorf("Cannot decode empty certificate") + } + originCert := OriginCert{} + block, rest := pem.Decode(blocks) + for { + if block == nil { + break + } + switch block.Type { + case "PRIVATE KEY", "CERTIFICATE": + // this is for legacy purposes. + break + case "ARGO TUNNEL TOKEN": + if originCert.ZoneID != "" || originCert.APIToken != "" { + return nil, fmt.Errorf("Found multiple tokens in the certificate") + } + // The token is a string, + // Try the newer JSON format + ntt := namedTunnelToken{} + if err := json.Unmarshal(block.Bytes, &ntt); err == nil { + originCert.ZoneID = ntt.ZoneID + originCert.APIToken = ntt.APIToken + originCert.AccountID = ntt.AccountID + } + default: + return nil, fmt.Errorf("Unknown block %s in the certificate", block.Type) + } + block, rest = pem.Decode(rest) + } + + if originCert.ZoneID == "" || originCert.APIToken == "" { + return nil, fmt.Errorf("Missing token in the certificate") + } + + return &originCert, nil +} + +func readOriginCert(originCertPath string) ([]byte, error) { + originCert, err := os.ReadFile(originCertPath) + if err != nil { + return nil, fmt.Errorf("cannot read %s to load origin certificate", originCertPath) + } + + return originCert, nil +} + +// FindOriginCert will check to make sure that the certificate exists at the specified file path. +func FindOriginCert(originCertPath string, log *zerolog.Logger) (string, error) { + if originCertPath == "" { + log.Error().Msgf("Cannot determine default origin certificate path. No file %s in %v. You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable", DefaultCredentialFile, config.DefaultConfigSearchDirectories()) + return "", fmt.Errorf("client didn't specify origincert path") + } + var err error + originCertPath, err = homedir.Expand(originCertPath) + if err != nil { + log.Err(err).Msgf("Cannot resolve origin certificate path") + return "", fmt.Errorf("cannot resolve path %s", originCertPath) + } + // Check that the user has acquired a certificate using the login command + ok := fileExists(originCertPath) + if !ok { + log.Error().Msgf(`Cannot find a valid certificate for your origin at the path: + + %s + +If the path above is wrong, specify the path with the -origincert option. +If you don't have a certificate signed by Cloudflare, run the command: + + cloudflared login +`, originCertPath) + return "", fmt.Errorf("cannot find a valid certificate at the path %s", originCertPath) + } + + return originCertPath, nil +} + +// FileExists checks to see if a file exist at the provided path. +func fileExists(path string) bool { + fileStat, err := os.Stat(path) + if err != nil { + return false + } + return !fileStat.IsDir() +} diff --git a/credentials/origin_cert_test.go b/credentials/origin_cert_test.go new file mode 100644 index 00000000..77a473e4 --- /dev/null +++ b/credentials/origin_cert_test.go @@ -0,0 +1,110 @@ +package credentials + +import ( + "fmt" + "io/fs" + "os" + "path" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + originCertFile = "cert.pem" +) + +var ( + nopLog = zerolog.Nop().With().Logger() +) + +func TestLoadOriginCert(t *testing.T) { + cert, err := decodeOriginCert([]byte{}) + assert.Equal(t, fmt.Errorf("Cannot decode empty certificate"), err) + assert.Nil(t, cert) + + blocks, err := os.ReadFile("test-cert-unknown-block.pem") + assert.NoError(t, err) + cert, err = decodeOriginCert(blocks) + assert.Equal(t, fmt.Errorf("Unknown block RSA PRIVATE KEY in the certificate"), err) + assert.Nil(t, cert) +} + +func TestJSONArgoTunnelTokenEmpty(t *testing.T) { + blocks, err := os.ReadFile("test-cert-no-token.pem") + assert.NoError(t, err) + cert, err := decodeOriginCert(blocks) + assert.Equal(t, fmt.Errorf("Missing token in the certificate"), err) + assert.Nil(t, cert) +} + +func TestJSONArgoTunnelToken(t *testing.T) { + // The given cert's Argo Tunnel Token was generated by base64 encoding this JSON: + // { + // "zoneID": "7b0a4d77dfb881c1a3b7d61ea9443e19", + // "apiToken": "test-service-key", + // "accountID": "abcdabcdabcdabcd1234567890abcdef" + // } + CloudflareTunnelTokenTest(t, "test-cloudflare-tunnel-cert-json.pem") +} + +func CloudflareTunnelTokenTest(t *testing.T, path string) { + blocks, err := os.ReadFile(path) + assert.NoError(t, err) + cert, err := decodeOriginCert(blocks) + assert.NoError(t, err) + assert.NotNil(t, cert) + assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID) + key := "test-service-key" + assert.Equal(t, key, cert.APIToken) +} + +type mockFile struct { + path string + data []byte + err error +} + +type mockFileSystem struct { + files map[string]mockFile +} + +func newMockFileSystem(files ...mockFile) *mockFileSystem { + fs := mockFileSystem{map[string]mockFile{}} + for _, f := range files { + fs.files[f.path] = f + } + return &fs +} + +func (fs *mockFileSystem) ReadFile(path string) ([]byte, error) { + if f, ok := fs.files[path]; ok { + return f.data, f.err + } + return nil, os.ErrNotExist +} + +func (fs *mockFileSystem) ValidFilePath(path string) bool { + _, exists := fs.files[path] + return exists +} + +func TestFindOriginCert_Valid(t *testing.T) { + file, err := os.ReadFile("test-cloudflare-tunnel-cert-json.pem") + require.NoError(t, err) + dir := t.TempDir() + certPath := path.Join(dir, originCertFile) + os.WriteFile(certPath, file, fs.ModePerm) + path, err := FindOriginCert(certPath, &nopLog) + require.NoError(t, err) + require.Equal(t, certPath, path) +} + +func TestFindOriginCert_Missing(t *testing.T) { + dir := t.TempDir() + certPath := path.Join(dir, originCertFile) + _, err := FindOriginCert(certPath, &nopLog) + require.Error(t, err) +} diff --git a/certutil/test-argo-tunnel-cert-json.pem b/credentials/test-cert-no-token.pem similarity index 96% rename from certutil/test-argo-tunnel-cert-json.pem rename to credentials/test-cert-no-token.pem index 6755cff4..f77b3a2d 100644 --- a/certutil/test-argo-tunnel-cert-json.pem +++ b/credentials/test-cert-no-token.pem @@ -51,7 +51,6 @@ K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ x+Yo/cL8fGfVpPt4UM8= -----END CERTIFICATE----- -----BEGIN ARGO TUNNEL TOKEN----- -eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAi -c2VydmljZUtleSI6ICJ0ZXN0LXNlcnZpY2Uta2V5IiwgImFjY291bnRJRCI6ICJh -YmNkYWJjZGFiY2RhYmNkMTIzNDU2Nzg5MGFiY2RlZiJ9 +eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAiYWNjb3VudElE +IjogImFiY2RhYmNkYWJjZGFiY2QxMjM0NTY3ODkwYWJjZGVmIn0= -----END ARGO TUNNEL TOKEN----- diff --git a/certutil/test-cert-unknown-block.pem b/credentials/test-cert-unknown-block.pem similarity index 98% rename from certutil/test-cert-unknown-block.pem rename to credentials/test-cert-unknown-block.pem index f7180851..4a847eb0 100644 --- a/certutil/test-cert-unknown-block.pem +++ b/credentials/test-cert-unknown-block.pem @@ -50,7 +50,7 @@ cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ x+Yo/cL8fGfVpPt4UM8= -----END CERTIFICATE----- ------BEGIN WARP TOKEN----- +-----BEGIN ARGO TUNNEL TOKEN----- N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4 ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5 MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4 @@ -58,7 +58,7 @@ NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2 NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5 OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz ZWYxZTI2Zjc= ------END WARP TOKEN----- +-----END ARGO TUNNEL TOKEN----- -----BEGIN RSA PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3 sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg diff --git a/certutil/test-argo-tunnel-cert.pem b/credentials/test-cloudflare-tunnel-cert-json.pem similarity index 94% rename from certutil/test-argo-tunnel-cert.pem rename to credentials/test-cloudflare-tunnel-cert-json.pem index 1a3397ac..cbbaa334 100644 --- a/certutil/test-argo-tunnel-cert.pem +++ b/credentials/test-cloudflare-tunnel-cert-json.pem @@ -51,6 +51,7 @@ K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+ x+Yo/cL8fGfVpPt4UM8= -----END CERTIFICATE----- -----BEGIN ARGO TUNNEL TOKEN----- -N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdGVzdC1zZXJ2aWNlLWtl -eQ== +eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAiYXBpVG9rZW4i +OiAidGVzdC1zZXJ2aWNlLWtleSIsICJhY2NvdW50SUQiOiAiYWJjZGFiY2RhYmNkYWJjZDEyMzQ1 +Njc4OTBhYmNkZWYifQ== -----END ARGO TUNNEL TOKEN----- diff --git a/datagramsession/manager.go b/datagramsession/manager.go index a461df4c..d1062f33 100644 --- a/datagramsession/manager.go +++ b/datagramsession/manager.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/rs/zerolog" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/packet" ) @@ -120,7 +121,9 @@ func (m *manager) registerSession(ctx context.Context, registration *registerSes } func (m *manager) newSession(id uuid.UUID, dstConn io.ReadWriteCloser) *Session { - logger := m.log.With().Str("sessionID", id.String()).Logger() + logger := m.log.With(). + Int(management.EventTypeKey, int(management.UDP)). + Str("sessionID", id.String()).Logger() return &Session{ ID: id, sendFunc: m.sendFunc, diff --git a/edgediscovery/allregions/discovery.go b/edgediscovery/allregions/discovery.go index dafaac13..cab06611 100644 --- a/edgediscovery/allregions/discovery.go +++ b/edgediscovery/allregions/discovery.go @@ -9,6 +9,8 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" + + "github.com/cloudflare/cloudflared/management" ) const ( @@ -41,6 +43,19 @@ const ( IPv6Only ConfigIPVersion = 6 ) +func (c ConfigIPVersion) String() string { + switch c { + case Auto: + return "auto" + case IPv4Only: + return "4" + case IPv6Only: + return "6" + default: + return "" + } +} + // IPVersion is the IP version of an EdgeAddr type EdgeIPVersion int8 @@ -95,16 +110,20 @@ var friendlyDNSErrorLines = []string{ // EdgeDiscovery implements HA service discovery lookup. func edgeDiscovery(log *zerolog.Logger, srvService string) ([][]*EdgeAddr, error) { - log.Debug().Str("domain", "_"+srvService+"._"+srvProto+"."+srvName).Msg("looking up edge SRV record") + logger := log.With().Int(management.EventTypeKey, int(management.Cloudflared)).Logger() + logger.Debug(). + Int(management.EventTypeKey, int(management.Cloudflared)). + Str("domain", "_"+srvService+"._"+srvProto+"."+srvName). + Msg("edge discovery: looking up edge SRV record") _, addrs, err := netLookupSRV(srvService, srvProto, srvName) if err != nil { _, fallbackAddrs, fallbackErr := fallbackLookupSRV(srvService, srvProto, srvName) if fallbackErr != nil || len(fallbackAddrs) == 0 { // use the original DNS error `err` in messages, not `fallbackErr` - log.Err(err).Msg("Error looking up Cloudflare edge IPs: the DNS query failed") + logger.Err(err).Msg("edge discovery: error looking up Cloudflare edge IPs: the DNS query failed") for _, s := range friendlyDNSErrorLines { - log.Error().Msg(s) + logger.Error().Msg(s) } return nil, errors.Wrapf(err, "Could not lookup srv records on _%v._%v.%v", srvService, srvProto, srvName) } @@ -118,9 +137,13 @@ func edgeDiscovery(log *zerolog.Logger, srvService string) ([][]*EdgeAddr, error if err != nil { return nil, err } - for _, e := range edgeAddrs { - log.Debug().Msgf("Edge Address: %+v", *e) + logAddrs := make([]string, len(edgeAddrs)) + for i, e := range edgeAddrs { + logAddrs[i] = e.UDP.IP.String() } + logger.Debug(). + Strs("addresses", logAddrs). + Msg("edge discovery: resolved edge addresses") resolvedAddrPerCNAME = append(resolvedAddrPerCNAME, edgeAddrs) } @@ -175,13 +198,15 @@ func ResolveAddrs(addrs []string, log *zerolog.Logger) (resolved []*EdgeAddr) { for _, addr := range addrs { tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - log.Error().Str(logFieldAddress, addr).Err(err).Msg("failed to resolve to TCP address") + log.Error().Int(management.EventTypeKey, int(management.Cloudflared)). + Str(logFieldAddress, addr).Err(err).Msg("edge discovery: failed to resolve to TCP address") continue } udpAddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { - log.Error().Str(logFieldAddress, addr).Err(err).Msg("failed to resolve to UDP address") + log.Error().Int(management.EventTypeKey, int(management.Cloudflared)). + Str(logFieldAddress, addr).Err(err).Msg("edge discovery: failed to resolve to UDP address") continue } version := V6 diff --git a/edgediscovery/allregions/regions.go b/edgediscovery/allregions/regions.go index 4b2a841b..b9b7d3ea 100644 --- a/edgediscovery/allregions/regions.go +++ b/edgediscovery/allregions/regions.go @@ -2,6 +2,7 @@ package allregions import ( "fmt" + "math/rand" "github.com/rs/zerolog" ) @@ -85,6 +86,15 @@ func (rs *Regions) AddrUsedBy(connID int) *EdgeAddr { // GetUnusedAddr gets an unused addr from the edge, excluding the given addr. Prefer to use addresses // evenly across both regions. func (rs *Regions) GetUnusedAddr(excluding *EdgeAddr, connID int) *EdgeAddr { + // If both regions have the same number of available addrs, lets randomise which one + // we pick. The rest of this algorithm will continue to make sure we always use addresses + // evenly across both regions. + if rs.region1.AvailableAddrs() == rs.region2.AvailableAddrs() { + regions := []Region{rs.region1, rs.region2} + firstChoice := rand.Intn(2) + return getAddrs(excluding, connID, ®ions[firstChoice], ®ions[1-firstChoice]) + } + if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() { return getAddrs(excluding, connID, &rs.region1, &rs.region2) } diff --git a/edgediscovery/dial.go b/edgediscovery/dial.go index c8ae632e..675e5dc5 100644 --- a/edgediscovery/dial.go +++ b/edgediscovery/dial.go @@ -15,12 +15,16 @@ func DialEdge( timeout time.Duration, tlsConfig *tls.Config, edgeTCPAddr *net.TCPAddr, + localIP net.IP, ) (net.Conn, error) { // Inherit from parent context so we can cancel (Ctrl-C) while dialing dialCtx, dialCancel := context.WithTimeout(ctx, timeout) defer dialCancel() dialer := net.Dialer{} + if localIP != nil { + dialer.LocalAddr = &net.TCPAddr{IP: localIP, Port: 0} + } edgeConn, err := dialer.DialContext(dialCtx, "tcp", edgeTCPAddr.String()) if err != nil { return nil, newDialError(err, "DialContext error") diff --git a/edgediscovery/edgediscovery.go b/edgediscovery/edgediscovery.go index 88788b10..59091bc7 100644 --- a/edgediscovery/edgediscovery.go +++ b/edgediscovery/edgediscovery.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/cloudflare/cloudflared/edgediscovery/allregions" + "github.com/cloudflare/cloudflared/management" ) const ( @@ -74,33 +75,35 @@ func (ed *Edge) GetAddrForRPC() (*allregions.EdgeAddr, error) { // GetAddr gives this proxy connection an edge Addr. Prefer Addrs this connection has already used. func (ed *Edge) GetAddr(connIndex int) (*allregions.EdgeAddr, error) { - log := ed.log.With().Int(LogFieldConnIndex, connIndex).Logger() + log := ed.log.With(). + Int(LogFieldConnIndex, connIndex). + Int(management.EventTypeKey, int(management.Cloudflared)). + Logger() ed.Lock() defer ed.Unlock() // If this connection has already used an edge addr, return it. if addr := ed.regions.AddrUsedBy(connIndex); addr != nil { - log.Debug().Msg("edgediscovery - GetAddr: Returning same address back to proxy connection") + log.Debug().IPAddr(LogFieldIPAddress, addr.UDP.IP).Msg("edge discovery: returning same edge address back to pool") return addr, nil } // Otherwise, give it an unused one addr := ed.regions.GetUnusedAddr(nil, connIndex) if addr == nil { - log.Debug().Msg("edgediscovery - GetAddr: No addresses left to give proxy connection") + log.Debug().Msg("edge discovery: no addresses left in pool to give proxy connection") return nil, errNoAddressesLeft } - log = ed.log.With(). - Int(LogFieldConnIndex, connIndex). - IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger() - log.Debug().Msgf("edgediscovery - GetAddr: Giving connection its new address") + log.Debug().IPAddr(LogFieldIPAddress, addr.UDP.IP).Msg("edge discovery: giving new address to connection") return addr, nil } // GetDifferentAddr gives back the proxy connection's edge Addr and uses a new one. func (ed *Edge) GetDifferentAddr(connIndex int, hasConnectivityError bool) (*allregions.EdgeAddr, error) { - log := ed.log.With().Int(LogFieldConnIndex, connIndex).Logger() - + log := ed.log.With(). + Int(LogFieldConnIndex, connIndex). + Int(management.EventTypeKey, int(management.Cloudflared)). + Logger() ed.Lock() defer ed.Unlock() @@ -110,14 +113,14 @@ func (ed *Edge) GetDifferentAddr(connIndex int, hasConnectivityError bool) (*all } addr := ed.regions.GetUnusedAddr(oldAddr, connIndex) if addr == nil { - log.Debug().Msg("edgediscovery - GetDifferentAddr: No addresses left to give proxy connection") + log.Debug().Msg("edge discovery: no addresses left in pool to give proxy connection") // note: if oldAddr were not nil, it will become available on the next iteration return nil, errNoAddressesLeft } - log = ed.log.With(). - Int(LogFieldConnIndex, connIndex). - IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger() - log.Debug().Msgf("edgediscovery - GetDifferentAddr: Giving connection its new address from the address list: %v", ed.regions.AvailableAddrs()) + log.Debug(). + IPAddr(LogFieldIPAddress, addr.UDP.IP). + Int("available", ed.regions.AvailableAddrs()). + Msg("edge discovery: giving new address to connection") return addr, nil } @@ -133,8 +136,9 @@ func (ed *Edge) AvailableAddrs() int { func (ed *Edge) GiveBack(addr *allregions.EdgeAddr, hasConnectivityError bool) bool { ed.Lock() defer ed.Unlock() - log := ed.log.With(). - IPAddr(LogFieldIPAddress, addr.UDP.IP).Logger() - log.Debug().Msgf("edgediscovery - GiveBack: Address now unused") + ed.log.Debug(). + Int(management.EventTypeKey, int(management.Cloudflared)). + IPAddr(LogFieldIPAddress, addr.UDP.IP). + Msg("edge discovery: gave back address to the pool") return ed.regions.GiveBack(addr, hasConnectivityError) } diff --git a/edgediscovery/protocol.go b/edgediscovery/protocol.go index 5bbb1e91..2427294b 100644 --- a/edgediscovery/protocol.go +++ b/edgediscovery/protocol.go @@ -15,6 +15,8 @@ var ( errNoProtocolRecord = fmt.Errorf("No TXT record found for %s to determine connection protocol", protocolRecord) ) +type PercentageFetcher func() (ProtocolPercents, error) + // ProtocolPercent represents a single Protocol Percentage combination. type ProtocolPercent struct { Protocol string `json:"protocol"` diff --git a/features/features.go b/features/features.go new file mode 100644 index 00000000..d6aaa6a6 --- /dev/null +++ b/features/features.go @@ -0,0 +1,30 @@ +package features + +const ( + FeatureSerializedHeaders = "serialized_headers" + FeatureQuickReconnects = "quick_reconnects" + FeatureAllowRemoteConfig = "allow_remote_config" + FeatureDatagramV2 = "support_datagram_v2" + FeaturePostQuantum = "postquantum" + FeatureQUICSupportEOF = "support_quic_eof" + FeatureManagementLogs = "management_logs" +) + +var ( + DefaultFeatures = []string{ + FeatureAllowRemoteConfig, + FeatureSerializedHeaders, + FeatureDatagramV2, + FeatureQUICSupportEOF, + FeatureManagementLogs, + } +) + +func Contains(feature string) bool { + for _, f := range DefaultFeatures { + if f == feature { + return true + } + } + return false +} diff --git a/github_release.py b/github_release.py index e59a0cbf..e28a89cc 100755 --- a/github_release.py +++ b/github_release.py @@ -166,6 +166,17 @@ def parse_args(): def upload_asset(release, filepath, filename, release_version, kv_account_id, namespace_id, kv_api_token): logging.info("Uploading asset: %s", filename) + assets = release.get_assets() + uploaded = False + for asset in assets: + if asset.name == filename: + uploaded = True + break + + if uploaded: + logging.info("asset already uploaded, skipping upload") + return + release.upload_asset(filepath, name=filename) # check and extract if the file is a tar and gzipped file (as is the case with the macos builds) @@ -182,9 +193,11 @@ def upload_asset(release, filepath, filename, release_version, kv_account_id, na binary_path = os.path.join(os.getcwd(), 'cfd', 'cloudflared') # send the sha256 (the checksum) to workers kv + logging.info("Uploading sha256 checksum for: %s", filename) pkg_hash = get_sha256(binary_path) send_hash(pkg_hash, filename, release_version, kv_account_id, namespace_id, kv_api_token) +def move_asset(filepath, filename): # create the artifacts directory if it doesn't exist artifact_path = os.path.join(os.getcwd(), 'artifacts') if not os.path.isdir(artifact_path): @@ -215,6 +228,7 @@ def main(): binary_path = os.path.join(args.path, filename) upload_asset(release, binary_path, filename, args.release_version, args.kv_account_id, args.namespace_id, args.kv_api_token) + move_asset(binary_path, filename) else: upload_asset(release, args.path, args.name, args.release_version, args.kv_account_id, args.namespace_id, args.kv_api_token) diff --git a/go.mod b/go.mod index b2bca9cb..7b1549c5 100644 --- a/go.mod +++ b/go.mod @@ -5,28 +5,30 @@ go 1.19 require ( github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc - github.com/coredns/coredns v1.8.7 + github.com/coredns/coredns v1.10.0 github.com/coreos/go-oidc/v3 v3.4.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434 github.com/fsnotify/fsnotify v1.4.9 - github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10 + github.com/getsentry/raven-go v0.2.0 + github.com/getsentry/sentry-go v0.16.0 + github.com/go-chi/chi/v5 v5.0.8 + github.com/go-jose/go-jose/v3 v3.0.0 github.com/gobwas/ws v1.0.4 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 github.com/google/gopacket v1.1.19 github.com/google/uuid v1.3.0 - github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/json-iterator/go v1.1.12 github.com/lucas-clemente/quic-go v0.28.1 - github.com/mattn/go-colorable v0.1.8 - github.com/miekg/dns v1.1.45 + github.com/mattn/go-colorable v0.1.13 + github.com/miekg/dns v1.1.50 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.12.1 + github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_model v0.2.0 github.com/rs/zerolog v1.20.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.1 github.com/urfave/cli/v2 v2.3.0 go.opentelemetry.io/contrib/propagators v0.22.0 go.opentelemetry.io/otel v1.6.3 @@ -35,24 +37,25 @@ require ( go.opentelemetry.io/otel/trace v1.6.3 go.opentelemetry.io/proto/otlp v0.15.0 go.uber.org/automaxprocs v1.4.0 - golang.org/x/crypto v0.2.0 - golang.org/x/net v0.2.0 + golang.org/x/crypto v0.8.0 + golang.org/x/net v0.9.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.2.0 - golang.org/x/term v0.2.0 - google.golang.org/protobuf v1.28.0 + golang.org/x/sys v0.7.0 + golang.org/x/term v0.7.0 + google.golang.org/protobuf v1.28.1 gopkg.in/coreos/go-oidc.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 + nhooyr.io/websocket v1.8.7 zombiezen.com/go/capnproto2 v2.18.0+incompatible ) require ( - github.com/BurntSushi/toml v0.3.1 // indirect + github.com/BurntSushi/toml v1.2.0 // indirect github.com/apparentlymart/go-cidr v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect + github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect @@ -72,30 +75,34 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/klauspost/compress v1.15.11 // indirect + github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.23.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect - google.golang.org/grpc v1.47.0 // indirect + google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect + google.golang.org/grpc v1.51.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -111,9 +118,11 @@ replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1 // Post-quantum tunnel RTG-1339 replace ( - // branch go1.18 - github.com/marten-seemann/qtls-go1-18 => github.com/cloudflare/qtls-pq v0.0.0-20221010110824-0053225e48b2 - - // branch go1.19 - github.com/marten-seemann/qtls-go1-19 => github.com/cloudflare/qtls-pq v0.0.0-20221010110800-4f3769902fe0 + // Branches go1.18 go1.19 go1.20 on github.com/cloudflare/qtls-pq + github.com/marten-seemann/qtls-go1-18 => github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e + github.com/marten-seemann/qtls-go1-19 => github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e + github.com/marten-seemann/qtls-go1-20 => github.com/cloudflare/qtls-pq v0.0.0-20230215110727-8b4e1699c2a8 + github.com/quic-go/qtls-go1-18 => github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e + github.com/quic-go/qtls-go1-19 => github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e + github.com/quic-go/qtls-go1-20 => github.com/cloudflare/qtls-pq v0.0.0-20230215110727-8b4e1699c2a8 ) diff --git a/go.sum b/go.sum index db7068e4..4b0ec975 100644 --- a/go.sum +++ b/go.sum @@ -61,37 +61,11 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/Azure/azure-sdk-for-go v61.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.23/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.10/go.mod h1:zQXYYNX9kXzRMrJNVXWUfNy38oPMF5/2TeZ4Wylc9fE= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -101,8 +75,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.42.30/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -112,8 +84,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA= -github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= +github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -132,10 +104,10 @@ github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 h1:YzpECHxZ9TzO github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47/go.mod h1:qhx8gBILsYlbam7h09SvHDSkjpe3TfLA7b/z4rxJvkE= github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc h1:Dvk3ySBsOm5EviLx6VCyILnafPcQinXGP5jbTdHUJgE= github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0= -github.com/cloudflare/qtls-pq v0.0.0-20221010110800-4f3769902fe0 h1:LEsjEHfKnIJEJU9QEIPVRuslxpBu+2kG2DXhxpkGT+o= -github.com/cloudflare/qtls-pq v0.0.0-20221010110800-4f3769902fe0/go.mod h1:aIsWqC0WXyUiUxBl/RfxAjDyWE9CCLqvSMnCMTd/+bc= -github.com/cloudflare/qtls-pq v0.0.0-20221010110824-0053225e48b2 h1:ErNoeVNqFXV+emlf4gY7Ms7/0DbQ8PT2UFxNyWBc51Q= -github.com/cloudflare/qtls-pq v0.0.0-20221010110824-0053225e48b2/go.mod h1:mW0BgKFFDAiSmOdUwoORtjo0V2vqw5QzVYRtKQqw/Jg= +github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e h1:frfo+L0qloEb6Vj+qjS4pbAYSJQZAlUnKZu0uJoErac= +github.com/cloudflare/qtls-pq v0.0.0-20230103171413-e7a2fb559a0e/go.mod h1:mW0BgKFFDAiSmOdUwoORtjo0V2vqw5QzVYRtKQqw/Jg= +github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e h1:RtQDXvDi0PK3EonP0v7zkE5/rApK4MsgRATCdD+ughg= +github.com/cloudflare/qtls-pq v0.0.0-20230103171656-05e84f90909e/go.mod h1:aIsWqC0WXyUiUxBl/RfxAjDyWE9CCLqvSMnCMTd/+bc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -147,16 +119,14 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0= github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/coredns v1.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk= -github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM= +github.com/coredns/coredns v1.10.0 h1:jCfuWsBjTs0dapkkhISfPCzn5LqvSRtrFtaf/Tjj4DI= +github.com/coredns/coredns v1.10.0/go.mod h1:CIfRU5TgpuoIiJBJ4XrofQzfFQpPFh32ERpUevrSlaw= github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -164,18 +134,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/dnstap/golang-dnstap v0.4.0/go.mod h1:FqsSdH58NAmkAvKcpyxht7i4FoBjKu8E4JUPt8ipSUs= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -186,7 +145,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 h1:wWke/RUCl7VRjQhwPlR/v0glZXNYzBHdNUzf/Am2Nmg= @@ -197,68 +155,73 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/farsightsec/golang-framestream v0.3.0/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10 h1:YO10pIIBftO/kkTFdWhctH96grJ7qiy7bMdiZcIvPKs= -github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo= +github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 h1:YyrUZvJaU8Q0QsoVo+xLFBgWDTam29PKea6GYmwvSiQ= github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -288,12 +251,9 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -307,13 +267,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -331,7 +289,6 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -350,49 +307,29 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0 github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI= github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI= github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -401,14 +338,14 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -418,30 +355,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ= github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= -github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -449,52 +383,35 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= -github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= -github.com/oschwald/geoip2-golang v1.5.0/go.mod h1:xdvYt5xQzB8ORWFqPnqMwZpCpgNagttWdoZLlJQzg7s= -github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= @@ -504,8 +421,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -515,18 +433,17 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -563,42 +480,36 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= -github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -623,11 +534,8 @@ go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5f go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0 h1:h0bKrvdrT/9sBwEJ6iWUqT/N/xPcS66bL4u3isneJ6w= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0= go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -636,17 +544,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= -golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -683,8 +587,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -703,8 +607,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -732,10 +634,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -744,8 +643,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -768,8 +667,9 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -801,13 +701,10 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -840,7 +737,6 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -852,7 +748,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -869,13 +764,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -885,13 +780,12 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -915,7 +809,6 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -930,11 +823,9 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -944,7 +835,6 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -952,8 +842,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -995,7 +885,6 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= @@ -1049,7 +938,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1080,7 +968,6 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= @@ -1097,16 +984,15 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd h1:OjndDrsik+Gt+e6fs45z9AxiewiKyLKYpA45W5Kpkks= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1130,15 +1016,14 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1153,14 +1038,13 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/DataDog/dd-trace-go.v1 v1.34.0/go.mod h1:HtrC65fyJ6lWazShCC9rlOeiTSZJ0XtZhkwjZM2WpC4= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc= @@ -1194,24 +1078,11 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess= diff --git a/ingress/config.go b/ingress/config.go index 212180e6..25f7993a 100644 --- a/ingress/config.go +++ b/ingress/config.go @@ -113,7 +113,7 @@ func (rc *RemoteConfig) UnmarshalJSON(b []byte) error { return nil } -func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig { +func originRequestFromSingleRule(c *cli.Context) OriginRequestConfig { var connectTimeout = defaultHTTPConnectTimeout var tlsTimeout = defaultTLSTimeout var tcpKeepAlive = defaultTCPKeepAlive diff --git a/ingress/config_test.go b/ingress/config_test.go index 2b9f8a3b..249cf2d5 100644 --- a/ingress/config_test.go +++ b/ingress/config_test.go @@ -411,7 +411,7 @@ func TestDefaultConfigFromCLI(t *testing.T) { KeepAliveTimeout: defaultKeepAliveTimeout, ProxyAddress: defaultProxyAddress, } - actual := originRequestFromSingeRule(c) + actual := originRequestFromSingleRule(c) require.Equal(t, expected, actual) } diff --git a/ingress/ingress.go b/ingress/ingress.go index 8905fb6d..56b3b1e7 100644 --- a/ingress/ingress.go +++ b/ingress/ingress.go @@ -20,6 +20,7 @@ import ( var ( ErrNoIngressRules = errors.New("The config file doesn't contain any ingress rules") + ErrNoIngressRulesCLI = errors.New("No ingress rules were defined in provided config (if any) nor from the cli, cloudflared will return 503 for all incoming HTTP requests") errLastRuleNotCatchAll = errors.New("The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter)") errBadWildcard = errors.New("Hostname patterns can have at most one wildcard character (\"*\") and it can only be used for subdomains, e.g. \"*.example.com\"") errHostnameContainsPort = errors.New("Hostname cannot contain a port") @@ -34,13 +35,22 @@ const ( // FindMatchingRule returns the index of the Ingress Rule which matches the given // hostname and path. This function assumes the last rule matches everything, -// which is the case if the rules were instantiated via the ingress#Validate method +// which is the case if the rules were instantiated via the ingress#Validate method. +// +// Negative index rule signifies local cloudflared rules (not-user defined). func (ing Ingress) FindMatchingRule(hostname, path string) (*Rule, int) { // The hostname might contain port. We only want to compare the host part with the rule host, _, err := net.SplitHostPort(hostname) if err == nil { hostname = host } + for i, rule := range ing.LocalRules { + if rule.Matches(hostname, path) { + // Local rule matches return a negative rule index to distiguish local rules from user-defined rules in logs + // Full range would be [-1 .. ) + return &rule, -1 - i + } + } for i, rule := range ing.Rules { if rule.Matches(hostname, path) { return &rule, i @@ -58,7 +68,7 @@ func matchHost(ruleHost, reqHost string) bool { // Validate hostnames that use wildcards at the start if strings.HasPrefix(ruleHost, "*.") { - toMatch := strings.TrimPrefix(ruleHost, "*.") + toMatch := strings.TrimPrefix(ruleHost, "*") return strings.HasSuffix(reqHost, toMatch) } return false @@ -66,21 +76,63 @@ func matchHost(ruleHost, reqHost string) bool { // Ingress maps eyeball requests to origins. type Ingress struct { + // Set of ingress rules that are not added to remote config, e.g. management + LocalRules []Rule + // Rules that are provided by the user from remote or local configuration Rules []Rule `json:"ingress"` Defaults OriginRequestConfig `json:"originRequest"` } -// NewSingleOrigin constructs an Ingress set with only one rule, constructed from -// legacy CLI parameters like --url or --no-chunked-encoding. -func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) { +// ParseIngress parses ingress rules, but does not send HTTP requests to the origins. +func ParseIngress(conf *config.Configuration) (Ingress, error) { + if len(conf.Ingress) == 0 { + return Ingress{}, ErrNoIngressRules + } + return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest)) +} +// ParseIngressFromConfigAndCLI will parse the configuration rules from config files for ingress +// rules and then attempt to parse CLI for ingress rules. +// Will always return at least one valid ingress rule. If none are provided by the user, the default +// will be to return 503 status code for all incoming requests. +func ParseIngressFromConfigAndCLI(conf *config.Configuration, c *cli.Context, log *zerolog.Logger) (Ingress, error) { + // Attempt to parse ingress rules from configuration + ingressRules, err := ParseIngress(conf) + if err == nil && !ingressRules.IsEmpty() { + return ingressRules, nil + } + if err != ErrNoIngressRules { + return Ingress{}, err + } + // Attempt to parse ingress rules from CLI: + // --url or --unix-socket flag for a tunnel HTTP ingress + // --hello-world for a basic HTTP ingress self-served + // --bastion for ssh bastion service + ingressRules, err = parseCLIIngress(c, false) + if errors.Is(err, ErrNoIngressRulesCLI) { + // Only log a warning if the tunnel is not a remotely managed tunnel and the config + // will be loaded after connecting. + if !c.IsSet("token") { + log.Warn().Msgf(ErrNoIngressRulesCLI.Error()) + } + return newDefaultOrigin(c, log), nil + } + if err != nil { + return Ingress{}, err + } + return ingressRules, nil +} + +// parseCLIIngress constructs an Ingress set with only one rule constructed from +// CLI parameters: --url, --hello-world, --bastion, or --unix-socket +func parseCLIIngress(c *cli.Context, allowURLFromArgs bool) (Ingress, error) { service, err := parseSingleOriginService(c, allowURLFromArgs) if err != nil { return Ingress{}, err } // Construct an Ingress with the single rule. - defaults := originRequestFromSingeRule(c) + defaults := originRequestFromSingleRule(c) ing := Ingress{ Rules: []Rule{ { @@ -93,27 +145,25 @@ func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) { return ing, err } -// WarpRoutingService starts a tcp stream between the origin and requests from -// warp clients. -type WarpRoutingService struct { - Proxy StreamBasedOriginProxy -} - -func NewWarpRoutingService(config WarpRoutingConfig) *WarpRoutingService { - svc := &rawTCPService{ - name: ServiceWarpRouting, - dialer: net.Dialer{ - Timeout: config.ConnectTimeout.Duration, - KeepAlive: config.TCPKeepAlive.Duration, +// newDefaultOrigin always returns a 503 response code to help indicate that there are no ingress +// rules setup, but the tunnel is reachable. +func newDefaultOrigin(c *cli.Context, log *zerolog.Logger) Ingress { + noRulesService := newDefaultStatusCode(log) + defaults := originRequestFromSingleRule(c) + ingress := Ingress{ + Rules: []Rule{ + { + Service: &noRulesService, + }, }, + Defaults: defaults, } - - return &WarpRoutingService{Proxy: svc} + return ingress } // Get a single origin service from the CLI/config. func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginService, error) { - if c.IsSet("hello-world") { + if c.IsSet(HelloWorldFlag) { return new(helloWorld), nil } if c.IsSet(config.BastionFlag) { @@ -138,8 +188,7 @@ func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginServ } return &unixSocketPath{path: path, scheme: "http"}, nil } - u, err := url.Parse("http://localhost:8080") - return &httpService{url: u}, err + return nil, ErrNoIngressRulesCLI } // IsEmpty checks if there are any ingress rules. @@ -207,7 +256,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq } srv := newStatusCode(statusCode) service = &srv - } else if r.Service == HelloWorldService || r.Service == "hello-world" || r.Service == "helloworld" { + } else if r.Service == HelloWorldFlag || r.Service == HelloWorldService { service = new(helloWorld) } else if r.Service == ServiceSocksProxy { rules := make([]ipaccess.Rule, len(r.OriginRequest.IPRules)) @@ -336,14 +385,6 @@ func (e errRuleShouldNotBeCatchAll) Error() string { "will never be triggered.", e.index+1, e.hostname) } -// ParseIngress parses ingress rules, but does not send HTTP requests to the origins. -func ParseIngress(conf *config.Configuration) (Ingress, error) { - if len(conf.Ingress) == 0 { - return Ingress{}, ErrNoIngressRules - } - return validateIngress(conf.Ingress, originRequestFromConfig(conf.OriginRequest)) -} - func isHTTPService(url *url.URL) bool { return url.Scheme == "http" || url.Scheme == "https" || url.Scheme == "ws" || url.Scheme == "wss" } diff --git a/ingress/ingress_test.go b/ingress/ingress_test.go index dd54c1c0..affbbf97 100644 --- a/ingress/ingress_test.go +++ b/ingress/ingress_test.go @@ -43,7 +43,7 @@ ingress: require.Equal(t, "https", s.scheme) } -func Test_parseIngress(t *testing.T) { +func TestParseIngress(t *testing.T) { localhost8000 := MustParseURL(t, "https://localhost:8000") localhost8001 := MustParseURL(t, "https://localhost:8001") fourOhFour := newStatusCode(404) @@ -517,7 +517,7 @@ func TestSingleOriginSetsConfig(t *testing.T) { allowURLFromArgs := false require.NoError(t, err) - ingress, err := NewSingleOrigin(cliCtx, allowURLFromArgs) + ingress, err := parseCLIIngress(cliCtx, allowURLFromArgs) require.NoError(t, err) assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout) @@ -537,6 +537,119 @@ func TestSingleOriginSetsConfig(t *testing.T) { assert.Equal(t, socksProxy, ingress.Rules[0].Config.ProxyType) } +func TestSingleOriginServices(t *testing.T) { + host := "://localhost:8080" + httpURL := urlMustParse("http" + host) + tcpURL := urlMustParse("tcp" + host) + unix := "unix://service" + newCli := func(params ...string) *cli.Context { + flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError) + flagSet.Bool("hello-world", false, "") + flagSet.Bool("bastion", false, "") + flagSet.String("url", "", "") + flagSet.String("unix-socket", "", "") + cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil) + for i := 0; i < len(params); i += 2 { + cliCtx.Set(params[i], params[i+1]) + } + + return cliCtx + } + + tests := []struct { + name string + cli *cli.Context + expectedService OriginService + err error + }{ + { + name: "Valid hello-world", + cli: newCli("hello-world", "true"), + expectedService: &helloWorld{}, + }, + { + name: "Valid bastion", + cli: newCli("bastion", "true"), + expectedService: newBastionService(), + }, + { + name: "Valid http url", + cli: newCli("url", httpURL.String()), + expectedService: &httpService{url: httpURL}, + }, + { + name: "Valid tcp url", + cli: newCli("url", tcpURL.String()), + expectedService: newTCPOverWSService(tcpURL), + }, + { + name: "Valid unix-socket", + cli: newCli("unix-socket", unix), + expectedService: &unixSocketPath{path: unix, scheme: "http"}, + }, + { + name: "No origins defined", + cli: newCli(), + err: ErrNoIngressRulesCLI, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ingress, err := parseCLIIngress(test.cli, false) + require.Equal(t, err, test.err) + if test.err != nil { + return + } + require.Equal(t, 1, len(ingress.Rules)) + rule := ingress.Rules[0] + require.Equal(t, test.expectedService, rule.Service) + }) + } +} + +func urlMustParse(s string) *url.URL { + u, err := url.Parse(s) + if err != nil { + panic(err) + } + return u +} + +func TestSingleOriginServices_URL(t *testing.T) { + host := "://localhost:8080" + newCli := func(param string, value string) *cli.Context { + flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError) + flagSet.String("url", "", "") + cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil) + cliCtx.Set(param, value) + return cliCtx + } + + httpTests := []string{"http", "https"} + for _, test := range httpTests { + t.Run(test, func(t *testing.T) { + url := urlMustParse(test + host) + ingress, err := parseCLIIngress(newCli("url", url.String()), false) + require.NoError(t, err) + require.Equal(t, 1, len(ingress.Rules)) + rule := ingress.Rules[0] + require.Equal(t, &httpService{url: url}, rule.Service) + }) + } + + tcpTests := []string{"ssh", "rdp", "smb", "tcp"} + for _, test := range tcpTests { + t.Run(test, func(t *testing.T) { + url := urlMustParse(test + host) + ingress, err := parseCLIIngress(newCli("url", url.String()), false) + require.NoError(t, err) + require.Equal(t, 1, len(ingress.Rules)) + rule := ingress.Rules[0] + require.Equal(t, newTCPOverWSService(url), rule.Service) + }) + } +} + func TestFindMatchingRule(t *testing.T) { ingress := Ingress{ Rules: []Rule{ diff --git a/ingress/origin_connection.go b/ingress/origin_connection.go index 2e8b946e..fbc4df39 100644 --- a/ingress/origin_connection.go +++ b/ingress/origin_connection.go @@ -9,6 +9,7 @@ import ( "github.com/cloudflare/cloudflared/ipaccess" "github.com/cloudflare/cloudflared/socks" + "github.com/cloudflare/cloudflared/stream" "github.com/cloudflare/cloudflared/websocket" ) @@ -25,7 +26,7 @@ type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log * // DefaultStreamHandler is an implementation of streamHandlerFunc that // performs a two way io.Copy between originConn and remoteConn. func DefaultStreamHandler(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger) { - websocket.Stream(originConn, remoteConn, log) + stream.Pipe(originConn, remoteConn, log) } // tcpConnection is an OriginConnection that directly streams to raw TCP. @@ -34,7 +35,7 @@ type tcpConnection struct { } func (tc *tcpConnection) Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger) { - websocket.Stream(tunnelConn, tc.conn, log) + stream.Pipe(tunnelConn, tc.conn, log) } func (tc *tcpConnection) Close() { diff --git a/ingress/origin_connection_test.go b/ingress/origin_connection_test.go index 07bcb500..ae8a757e 100644 --- a/ingress/origin_connection_test.go +++ b/ingress/origin_connection_test.go @@ -22,6 +22,7 @@ import ( "github.com/cloudflare/cloudflared/logger" "github.com/cloudflare/cloudflared/socks" + "github.com/cloudflare/cloudflared/stream" "github.com/cloudflare/cloudflared/websocket" ) @@ -50,6 +51,7 @@ func TestStreamTCPConnection(t *testing.T) { errGroup, ctx := errgroup.WithContext(ctx) errGroup.Go(func() error { _, err := eyeballConn.Write(testMessage) + require.NoError(t, err) readBuffer := make([]byte, len(testResponse)) _, err = eyeballConn.Read(readBuffer) @@ -158,7 +160,7 @@ func TestSocksStreamWSOverTCPConnection(t *testing.T) { require.NoError(t, err) defer wsForwarderInConn.Close() - websocket.Stream(wsForwarderInConn, &wsEyeball{wsForwarderOutConn}, testLogger) + stream.Pipe(wsForwarderInConn, &wsEyeball{wsForwarderOutConn}, testLogger) return nil }) diff --git a/ingress/origin_proxy.go b/ingress/origin_proxy.go index 83bcd4fc..40a87811 100644 --- a/ingress/origin_proxy.go +++ b/ingress/origin_proxy.go @@ -17,6 +17,12 @@ type StreamBasedOriginProxy interface { EstablishConnection(ctx context.Context, dest string) (OriginConnection, error) } +// HTTPLocalProxy can be implemented by cloudflared services that want to handle incoming http requests. +type HTTPLocalProxy interface { + // Handler is how cloudflared proxies eyeball requests to the local cloudflared services + http.Handler +} + func (o *unixSocketPath) RoundTrip(req *http.Request) (*http.Response, error) { req.URL.Scheme = o.scheme return o.transport.RoundTrip(req) @@ -44,6 +50,9 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) { } func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) { + if o.defaultResp { + o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error()) + } resp := &http.Response{ StatusCode: o.code, Status: fmt.Sprintf("%d %s", o.code, http.StatusText(o.code)), diff --git a/ingress/origin_service.go b/ingress/origin_service.go index 85fd29cd..f7bcc297 100644 --- a/ingress/origin_service.go +++ b/ingress/origin_service.go @@ -17,12 +17,14 @@ import ( "github.com/cloudflare/cloudflared/hello" "github.com/cloudflare/cloudflared/ipaccess" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/socks" "github.com/cloudflare/cloudflared/tlsconfig" ) const ( HelloWorldService = "hello_world" + HelloWorldFlag = "hello-world" HttpStatusService = "http_status" ) @@ -246,12 +248,21 @@ func (o helloWorld) MarshalJSON() ([]byte, error) { // Typical use-case is "user wants the catch-all rule to just respond 404". type statusCode struct { code int + + // Set only when the user has not defined any ingress rules + defaultResp bool + log *zerolog.Logger } func newStatusCode(status int) statusCode { return statusCode{code: status} } +// default status code (503) that is returned for requests to cloudflared that don't have any ingress rules setup +func newDefaultStatusCode(log *zerolog.Logger) statusCode { + return statusCode{code: 503, defaultResp: true, log: log} +} + func (o *statusCode) String() string { return fmt.Sprintf("http_status:%d", o.code) } @@ -268,6 +279,54 @@ func (o statusCode) MarshalJSON() ([]byte, error) { return json.Marshal(o.String()) } +// WarpRoutingService starts a tcp stream between the origin and requests from +// warp clients. +type WarpRoutingService struct { + Proxy StreamBasedOriginProxy +} + +func NewWarpRoutingService(config WarpRoutingConfig) *WarpRoutingService { + svc := &rawTCPService{ + name: ServiceWarpRouting, + dialer: net.Dialer{ + Timeout: config.ConnectTimeout.Duration, + KeepAlive: config.TCPKeepAlive.Duration, + }, + } + + return &WarpRoutingService{Proxy: svc} +} + +// ManagementService starts a local HTTP server to handle incoming management requests. +type ManagementService struct { + HTTPLocalProxy +} + +func newManagementService(managementProxy HTTPLocalProxy) *ManagementService { + return &ManagementService{ + HTTPLocalProxy: managementProxy, + } +} + +func (o *ManagementService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error { + return nil +} + +func (o *ManagementService) String() string { + return "management" +} + +func (o ManagementService) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func NewManagementRule(management *management.ManagementService) Rule { + return Rule{ + Hostname: management.Hostname, + Service: newManagementService(management), + } +} + type NopReadCloser struct{} // Read always returns EOF to signal end of input diff --git a/ingress/rule_test.go b/ingress/rule_test.go index 1c051137..1a46f155 100644 --- a/ingress/rule_test.go +++ b/ingress/rule_test.go @@ -148,6 +148,16 @@ func Test_rule_matches(t *testing.T) { }, want: true, }, + { + name: "Hostname with wildcard should not match if no dot present", + rule: Rule{ + Hostname: "*.api.abc.cloud", + }, + args: args{ + requestURL: MustParseURL(t, "https://testing-api.abc.cloud"), + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/test/wstest.go b/internal/test/wstest.go new file mode 100644 index 00000000..07ba8df0 --- /dev/null +++ b/internal/test/wstest.go @@ -0,0 +1,66 @@ +package test + +// copied from https://github.com/nhooyr/websocket/blob/master/internal/test/wstest/pipe.go + +import ( + "bufio" + "context" + "net" + "net/http" + "net/http/httptest" + + "nhooyr.io/websocket" +) + +// Pipe is used to create an in memory connection +// between two websockets analogous to net.Pipe. +func WSPipe(dialOpts *websocket.DialOptions, acceptOpts *websocket.AcceptOptions) (clientConn, serverConn *websocket.Conn) { + tt := fakeTransport{ + h: func(w http.ResponseWriter, r *http.Request) { + serverConn, _ = websocket.Accept(w, r, acceptOpts) + }, + } + + if dialOpts == nil { + dialOpts = &websocket.DialOptions{} + } + dialOpts = &*dialOpts + dialOpts.HTTPClient = &http.Client{ + Transport: tt, + } + + clientConn, _, _ = websocket.Dial(context.Background(), "ws://example.com", dialOpts) + return clientConn, serverConn +} + +type fakeTransport struct { + h http.HandlerFunc +} + +func (t fakeTransport) RoundTrip(r *http.Request) (*http.Response, error) { + clientConn, serverConn := net.Pipe() + + hj := testHijacker{ + ResponseRecorder: httptest.NewRecorder(), + serverConn: serverConn, + } + + t.h.ServeHTTP(hj, r) + + resp := hj.ResponseRecorder.Result() + if resp.StatusCode == http.StatusSwitchingProtocols { + resp.Body = clientConn + } + return resp, nil +} + +type testHijacker struct { + *httptest.ResponseRecorder + serverConn net.Conn +} + +var _ http.Hijacker = testHijacker{} + +func (hj testHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return hj.serverConn, bufio.NewReadWriter(bufio.NewReader(hj.serverConn), bufio.NewWriter(hj.serverConn)), nil +} diff --git a/logger/create.go b/logger/create.go index 256ff4c7..d7a987e1 100644 --- a/logger/create.go +++ b/logger/create.go @@ -15,6 +15,9 @@ import ( "github.com/urfave/cli/v2" "golang.org/x/term" "gopkg.in/natefinch/lumberjack.v2" + + "github.com/cloudflare/cloudflared/features" + "github.com/cloudflare/cloudflared/management" ) const ( @@ -35,8 +38,19 @@ const ( consoleTimeFormat = time.RFC3339 ) +var ( + ManagementLogger *management.Logger +) + func init() { + zerolog.TimeFieldFormat = time.RFC3339 zerolog.TimestampFunc = utcNow + + if features.Contains(features.FeatureManagementLogs) { + // Management logger needs to be initialized before any of the other loggers as to not capture + // it's own logging events. + ManagementLogger = management.NewLogger() + } } func utcNow() time.Time { @@ -50,17 +64,36 @@ func fallbackLogger(err error) *zerolog.Logger { return &failLog } -type resilientMultiWriter struct { - writers []io.Writer -} - -// This custom resilientMultiWriter is an alternative to zerolog's so that we can make it resilient to individual +// resilientMultiWriter is an alternative to zerolog's so that we can make it resilient to individual // writer's errors. E.g., when running as a Windows service, the console writer fails, but we don't want to // allow that to prevent all logging to fail due to breaking the for loop upon an error. +type resilientMultiWriter struct { + level zerolog.Level + writers []io.Writer + managementWriter zerolog.LevelWriter +} + func (t resilientMultiWriter) Write(p []byte) (n int, err error) { for _, w := range t.writers { _, _ = w.Write(p) } + if t.managementWriter != nil { + _, _ = t.managementWriter.Write(p) + } + return len(p), nil +} + +func (t resilientMultiWriter) WriteLevel(level zerolog.Level, p []byte) (n int, err error) { + // Only write the event to normal writers if it exceeds the level, but always write to the + // management logger and let it decided with the provided level of the log event. + if t.level <= level { + for _, w := range t.writers { + _, _ = w.Write(p) + } + } + if t.managementWriter != nil { + _, _ = t.managementWriter.WriteLevel(level, p) + } return len(p), nil } @@ -91,13 +124,18 @@ func newZerolog(loggerConfig *Config) *zerolog.Logger { writers = append(writers, rollingLogger) } - multi := resilientMultiWriter{writers} + var managementWriter zerolog.LevelWriter + if features.Contains(features.FeatureManagementLogs) { + managementWriter = ManagementLogger + } level, levelErr := zerolog.ParseLevel(loggerConfig.MinLevel) if levelErr != nil { level = zerolog.InfoLevel } - log := zerolog.New(multi).With().Timestamp().Logger().Level(level) + + multi := resilientMultiWriter{level, writers, managementWriter} + log := zerolog.New(multi).With().Timestamp().Logger() if !levelErrorLogged && levelErr != nil { log.Error().Msgf("Failed to parse log level %q, using %q instead", loggerConfig.MinLevel, level) levelErrorLogged = true diff --git a/logger/create_test.go b/logger/create_test.go index 21ede396..2e0f9b96 100644 --- a/logger/create_test.go +++ b/logger/create_test.go @@ -9,14 +9,13 @@ import ( "github.com/stretchr/testify/assert" ) -var writeCalls int - type mockedWriter struct { - wantErr bool + wantErr bool + writeCalls int } -func (c mockedWriter) Write(p []byte) (int, error) { - writeCalls++ +func (c *mockedWriter) Write(p []byte) (int, error) { + c.writeCalls++ if c.wantErr { return -1, errors.New("Expected error") @@ -26,65 +25,108 @@ func (c mockedWriter) Write(p []byte) (int, error) { } // Tests that a new writer is only used if it actually works. -func TestResilientMultiWriter(t *testing.T) { +func TestResilientMultiWriter_Errors(t *testing.T) { tests := []struct { name string - writers []io.Writer + writers []*mockedWriter }{ { name: "All valid writers", - writers: []io.Writer{ - mockedWriter{ + writers: []*mockedWriter{ + { wantErr: false, }, - mockedWriter{ + { wantErr: false, }, }, }, { name: "All invalid writers", - writers: []io.Writer{ - mockedWriter{ + writers: []*mockedWriter{ + { wantErr: true, }, - mockedWriter{ + { wantErr: true, }, }, }, { name: "First invalid writer", - writers: []io.Writer{ - mockedWriter{ + writers: []*mockedWriter{ + { wantErr: true, }, - mockedWriter{ + { wantErr: false, }, }, }, { name: "First valid writer", - writers: []io.Writer{ - mockedWriter{ + writers: []*mockedWriter{ + { wantErr: false, }, - mockedWriter{ + { wantErr: true, }, }, }, } - for _, tt := range tests { - writers := tt.writers - multiWriter := resilientMultiWriter{writers} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + writers := []io.Writer{} + for _, w := range test.writers { + writers = append(writers, w) + } + multiWriter := resilientMultiWriter{zerolog.InfoLevel, writers, nil} - logger := zerolog.New(multiWriter).With().Timestamp().Logger().Level(zerolog.InfoLevel) - logger.Info().Msg("Test msg") + logger := zerolog.New(multiWriter).With().Timestamp().Logger() + logger.Info().Msg("Test msg") - assert.Equal(t, len(writers), writeCalls) - writeCalls = 0 + for _, w := range test.writers { + // Expect each writer to be written to regardless of the previous writers returning an error + assert.Equal(t, 1, w.writeCalls) + } + }) + } +} + +type mockedManagementWriter struct { + WriteCalls int +} + +func (c *mockedManagementWriter) Write(p []byte) (int, error) { + return len(p), nil +} + +func (c *mockedManagementWriter) WriteLevel(level zerolog.Level, p []byte) (int, error) { + c.WriteCalls++ + return len(p), nil +} + +// Tests that management writer receives write calls of all levels except Disabled +func TestResilientMultiWriter_Management(t *testing.T) { + for _, level := range []zerolog.Level{ + zerolog.DebugLevel, + zerolog.InfoLevel, + zerolog.WarnLevel, + zerolog.ErrorLevel, + zerolog.FatalLevel, + zerolog.PanicLevel, + } { + t.Run(level.String(), func(t *testing.T) { + managementWriter := mockedManagementWriter{} + multiWriter := resilientMultiWriter{level, []io.Writer{&mockedWriter{}}, &managementWriter} + + logger := zerolog.New(multiWriter).With().Timestamp().Logger() + logger.Info().Msg("Test msg") + + // Always write to management + assert.Equal(t, 1, managementWriter.WriteCalls) + }) } } diff --git a/management/events.go b/management/events.go new file mode 100644 index 00000000..528bae18 --- /dev/null +++ b/management/events.go @@ -0,0 +1,317 @@ +package management + +import ( + "context" + "errors" + "fmt" + "io" + + jsoniter "github.com/json-iterator/go" + "github.com/rs/zerolog" + "nhooyr.io/websocket" +) + +var ( + errInvalidMessageType = fmt.Errorf("invalid message type was provided") +) + +// ServerEventType represents the event types that can come from the server +type ServerEventType string + +// ClientEventType represents the event types that can come from the client +type ClientEventType string + +const ( + UnknownClientEventType ClientEventType = "" + StartStreaming ClientEventType = "start_streaming" + StopStreaming ClientEventType = "stop_streaming" + + UnknownServerEventType ServerEventType = "" + Logs ServerEventType = "logs" +) + +// ServerEvent is the base struct that informs, based of the Type field, which Event type was provided from the server. +type ServerEvent struct { + Type ServerEventType `json:"type,omitempty"` + // The raw json message is provided to allow better deserialization once the type is known + event jsoniter.RawMessage +} + +// ClientEvent is the base struct that informs, based of the Type field, which Event type was provided from the client. +type ClientEvent struct { + Type ClientEventType `json:"type,omitempty"` + // The raw json message is provided to allow better deserialization once the type is known + event jsoniter.RawMessage +} + +// EventStartStreaming signifies that the client wishes to start receiving log events. +// Additional filters can be provided to augment the log events requested. +type EventStartStreaming struct { + ClientEvent + Filters *StreamingFilters `json:"filters,omitempty"` +} + +type StreamingFilters struct { + Events []LogEventType `json:"events,omitempty"` + Level *LogLevel `json:"level,omitempty"` + Sampling float64 `json:"sampling,omitempty"` +} + +// EventStopStreaming signifies that the client wishes to halt receiving log events. +type EventStopStreaming struct { + ClientEvent +} + +// EventLog is the event that the server sends to the client with the log events. +type EventLog struct { + ServerEvent + Logs []*Log `json:"logs"` +} + +// LogEventType is the way that logging messages are able to be filtered. +// Example: assigning LogEventType.Cloudflared to a zerolog event will allow the client to filter for only +// the Cloudflared-related events. +type LogEventType int8 + +const ( + // Cloudflared events are signficant to cloudflared operations like connection state changes. + // Cloudflared is also the default event type for any events that haven't been separated into a proper event type. + Cloudflared LogEventType = iota + HTTP + TCP + UDP +) + +func ParseLogEventType(s string) (LogEventType, bool) { + switch s { + case "cloudflared": + return Cloudflared, true + case "http": + return HTTP, true + case "tcp": + return TCP, true + case "udp": + return UDP, true + } + return -1, false +} + +func (l LogEventType) String() string { + switch l { + case Cloudflared: + return "cloudflared" + case HTTP: + return "http" + case TCP: + return "tcp" + case UDP: + return "udp" + default: + return "" + } +} + +func (l LogEventType) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +func (e *LogEventType) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return errors.New("unable to unmarshal LogEventType string") + } + if event, ok := ParseLogEventType(s); ok { + *e = event + return nil + } + return errors.New("unable to unmarshal LogEventType") +} + +// LogLevel corresponds to the zerolog logging levels +// "panic", "fatal", and "trace" are exempt from this list as they are rarely used and, at least +// the the first two are limited to failure conditions that lead to cloudflared shutting down. +type LogLevel int8 + +const ( + Debug LogLevel = 0 + Info LogLevel = 1 + Warn LogLevel = 2 + Error LogLevel = 3 +) + +func ParseLogLevel(l string) (LogLevel, bool) { + switch l { + case "debug": + return Debug, true + case "info": + return Info, true + case "warn": + return Warn, true + case "error": + return Error, true + } + return -1, false +} + +func (l LogLevel) String() string { + switch l { + case Debug: + return "debug" + case Info: + return "info" + case Warn: + return "warn" + case Error: + return "error" + default: + return "" + } +} + +func (l LogLevel) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +func (l *LogLevel) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return errors.New("unable to unmarshal LogLevel string") + } + if level, ok := ParseLogLevel(s); ok { + *l = level + return nil + } + return fmt.Errorf("unable to unmarshal LogLevel") +} + +const ( + // TimeKey aligns with the zerolog.TimeFieldName + TimeKey = "time" + // LevelKey aligns with the zerolog.LevelFieldName + LevelKey = "level" + // LevelKey aligns with the zerolog.MessageFieldName + MessageKey = "message" + // EventTypeKey is the custom JSON key of the LogEventType in ZeroLogEvent + EventTypeKey = "event" + // FieldsKey is a custom JSON key to match and store every other key for a zerolog event + FieldsKey = "fields" +) + +// Log is the basic structure of the events that are sent to the client. +type Log struct { + Time string `json:"time,omitempty"` + Level LogLevel `json:"level,omitempty"` + Message string `json:"message,omitempty"` + Event LogEventType `json:"event,omitempty"` + Fields map[string]interface{} `json:"fields,omitempty"` +} + +// IntoClientEvent unmarshals the provided ClientEvent into the proper type. +func IntoClientEvent[T EventStartStreaming | EventStopStreaming](e *ClientEvent, eventType ClientEventType) (*T, bool) { + if e.Type != eventType { + return nil, false + } + event := new(T) + err := json.Unmarshal(e.event, event) + if err != nil { + return nil, false + } + return event, true +} + +// IntoServerEvent unmarshals the provided ServerEvent into the proper type. +func IntoServerEvent[T EventLog](e *ServerEvent, eventType ServerEventType) (*T, bool) { + if e.Type != eventType { + return nil, false + } + event := new(T) + err := json.Unmarshal(e.event, event) + if err != nil { + return nil, false + } + return event, true +} + +// ReadEvent will read a message from the websocket connection and parse it into a valid ServerEvent. +func ReadServerEvent(c *websocket.Conn, ctx context.Context) (*ServerEvent, error) { + message, err := readMessage(c, ctx) + if err != nil { + return nil, err + } + event := ServerEvent{} + if err := json.Unmarshal(message, &event); err != nil { + return nil, err + } + switch event.Type { + case Logs: + event.event = message + return &event, nil + case UnknownServerEventType: + return nil, errInvalidMessageType + default: + return nil, fmt.Errorf("invalid server message type was provided: %s", event.Type) + } +} + +// ReadEvent will read a message from the websocket connection and parse it into a valid ClientEvent. +func ReadClientEvent(c *websocket.Conn, ctx context.Context) (*ClientEvent, error) { + message, err := readMessage(c, ctx) + if err != nil { + return nil, err + } + event := ClientEvent{} + if err := json.Unmarshal(message, &event); err != nil { + return nil, err + } + switch event.Type { + case StartStreaming, StopStreaming: + event.event = message + return &event, nil + case UnknownClientEventType: + return nil, errInvalidMessageType + default: + return nil, fmt.Errorf("invalid client message type was provided: %s", event.Type) + } +} + +// readMessage will read a message from the websocket connection and return the payload. +func readMessage(c *websocket.Conn, ctx context.Context) ([]byte, error) { + messageType, reader, err := c.Reader(ctx) + if err != nil { + return nil, err + } + if messageType != websocket.MessageText { + return nil, errInvalidMessageType + } + return io.ReadAll(reader) +} + +// WriteEvent will write a Event type message to the websocket connection. +func WriteEvent(c *websocket.Conn, ctx context.Context, event any) error { + payload, err := json.Marshal(event) + if err != nil { + return err + } + return c.Write(ctx, websocket.MessageText, payload) +} + +// IsClosed returns true if the websocket error is a websocket.CloseError; returns false if not a +// websocket.CloseError +func IsClosed(err error, log *zerolog.Logger) bool { + var closeErr websocket.CloseError + if errors.As(err, &closeErr) { + if closeErr.Code != websocket.StatusNormalClosure { + log.Debug().Msgf("connection is already closed: (%d) %s", closeErr.Code, closeErr.Reason) + } + return true + } + return false +} + +func AsClosed(err error) *websocket.CloseError { + var closeErr websocket.CloseError + if errors.As(err, &closeErr) { + return &closeErr + } + return nil +} diff --git a/management/events_test.go b/management/events_test.go new file mode 100644 index 00000000..1d40e29b --- /dev/null +++ b/management/events_test.go @@ -0,0 +1,247 @@ +package management + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "nhooyr.io/websocket" + + "github.com/cloudflare/cloudflared/internal/test" +) + +var ( + debugLevel *LogLevel + infoLevel *LogLevel + warnLevel *LogLevel + errorLevel *LogLevel +) + +func init() { + // created here because we can't do a reference to a const enum, i.e. &Info + debugLevel := new(LogLevel) + *debugLevel = Debug + infoLevel := new(LogLevel) + *infoLevel = Info + warnLevel := new(LogLevel) + *warnLevel = Warn + errorLevel := new(LogLevel) + *errorLevel = Error +} + +func TestIntoClientEvent_StartStreaming(t *testing.T) { + for _, test := range []struct { + name string + expected EventStartStreaming + }{ + { + name: "no filters", + expected: EventStartStreaming{ClientEvent: ClientEvent{Type: StartStreaming}}, + }, + { + name: "level filter", + expected: EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + Filters: &StreamingFilters{ + Level: infoLevel, + }, + }, + }, + { + name: "events filter", + expected: EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + Filters: &StreamingFilters{ + Events: []LogEventType{Cloudflared, HTTP}, + }, + }, + }, + { + name: "sampling filter", + expected: EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + Filters: &StreamingFilters{ + Sampling: 0.5, + }, + }, + }, + { + name: "level and events filters", + expected: EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + Filters: &StreamingFilters{ + Level: infoLevel, + Events: []LogEventType{Cloudflared}, + Sampling: 0.5, + }, + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + data, err := json.Marshal(test.expected) + require.NoError(t, err) + event := ClientEvent{} + err = json.Unmarshal(data, &event) + require.NoError(t, err) + event.event = data + ce, ok := IntoClientEvent[EventStartStreaming](&event, StartStreaming) + require.True(t, ok) + require.Equal(t, test.expected.ClientEvent, ce.ClientEvent) + if test.expected.Filters != nil { + f := ce.Filters + ef := test.expected.Filters + if ef.Level != nil { + require.Equal(t, *ef.Level, *f.Level) + } + require.ElementsMatch(t, ef.Events, f.Events) + } + }) + } +} + +func TestIntoClientEvent_StopStreaming(t *testing.T) { + event := ClientEvent{ + Type: StopStreaming, + event: []byte(`{"type": "stop_streaming"}`), + } + ce, ok := IntoClientEvent[EventStopStreaming](&event, StopStreaming) + require.True(t, ok) + require.Equal(t, EventStopStreaming{ClientEvent: ClientEvent{Type: StopStreaming}}, *ce) +} + +func TestIntoClientEvent_Invalid(t *testing.T) { + event := ClientEvent{ + Type: UnknownClientEventType, + event: []byte(`{"type": "invalid"}`), + } + _, ok := IntoClientEvent[EventStartStreaming](&event, StartStreaming) + require.False(t, ok) +} + +func TestIntoServerEvent_Logs(t *testing.T) { + event := ServerEvent{ + Type: Logs, + event: []byte(`{"type": "logs"}`), + } + ce, ok := IntoServerEvent(&event, Logs) + require.True(t, ok) + require.Equal(t, EventLog{ServerEvent: ServerEvent{Type: Logs}}, *ce) +} + +func TestIntoServerEvent_Invalid(t *testing.T) { + event := ServerEvent{ + Type: UnknownServerEventType, + event: []byte(`{"type": "invalid"}`), + } + _, ok := IntoServerEvent(&event, Logs) + require.False(t, ok) +} + +func TestReadServerEvent(t *testing.T) { + sentEvent := EventLog{ + ServerEvent: ServerEvent{Type: Logs}, + Logs: []*Log{ + { + Time: time.Now().UTC().Format(time.RFC3339), + Event: HTTP, + Level: Info, + Message: "test", + }, + }, + } + client, server := test.WSPipe(nil, nil) + server.CloseRead(context.Background()) + defer func() { + server.Close(websocket.StatusInternalError, "") + }() + go func() { + err := WriteEvent(server, context.Background(), &sentEvent) + require.NoError(t, err) + }() + event, err := ReadServerEvent(client, context.Background()) + require.NoError(t, err) + require.Equal(t, sentEvent.Type, event.Type) + client.Close(websocket.StatusInternalError, "") +} + +func TestReadServerEvent_InvalidWebSocketMessageType(t *testing.T) { + client, server := test.WSPipe(nil, nil) + server.CloseRead(context.Background()) + defer func() { + server.Close(websocket.StatusInternalError, "") + }() + go func() { + err := server.Write(context.Background(), websocket.MessageBinary, []byte("test1234")) + require.NoError(t, err) + }() + _, err := ReadServerEvent(client, context.Background()) + require.Error(t, err) + client.Close(websocket.StatusInternalError, "") +} + +func TestReadServerEvent_InvalidMessageType(t *testing.T) { + sentEvent := ClientEvent{Type: ClientEventType(UnknownServerEventType)} + client, server := test.WSPipe(nil, nil) + server.CloseRead(context.Background()) + defer func() { + server.Close(websocket.StatusInternalError, "") + }() + go func() { + err := WriteEvent(server, context.Background(), &sentEvent) + require.NoError(t, err) + }() + _, err := ReadServerEvent(client, context.Background()) + require.ErrorIs(t, err, errInvalidMessageType) + client.Close(websocket.StatusInternalError, "") +} + +func TestReadClientEvent(t *testing.T) { + sentEvent := EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + } + client, server := test.WSPipe(nil, nil) + client.CloseRead(context.Background()) + defer func() { + client.Close(websocket.StatusInternalError, "") + }() + go func() { + err := WriteEvent(client, context.Background(), &sentEvent) + require.NoError(t, err) + }() + event, err := ReadClientEvent(server, context.Background()) + require.NoError(t, err) + require.Equal(t, sentEvent.Type, event.Type) + server.Close(websocket.StatusInternalError, "") +} + +func TestReadClientEvent_InvalidWebSocketMessageType(t *testing.T) { + client, server := test.WSPipe(nil, nil) + client.CloseRead(context.Background()) + defer func() { + client.Close(websocket.StatusInternalError, "") + }() + go func() { + err := client.Write(context.Background(), websocket.MessageBinary, []byte("test1234")) + require.NoError(t, err) + }() + _, err := ReadClientEvent(server, context.Background()) + require.Error(t, err) + server.Close(websocket.StatusInternalError, "") +} + +func TestReadClientEvent_InvalidMessageType(t *testing.T) { + sentEvent := ClientEvent{Type: UnknownClientEventType} + client, server := test.WSPipe(nil, nil) + client.CloseRead(context.Background()) + defer func() { + client.Close(websocket.StatusInternalError, "") + }() + go func() { + err := WriteEvent(client, context.Background(), &sentEvent) + require.NoError(t, err) + }() + _, err := ReadClientEvent(server, context.Background()) + require.ErrorIs(t, err, errInvalidMessageType) + server.Close(websocket.StatusInternalError, "") +} diff --git a/management/logger.go b/management/logger.go new file mode 100644 index 00000000..7000a867 --- /dev/null +++ b/management/logger.go @@ -0,0 +1,177 @@ +package management + +import ( + "os" + "sync" + "time" + + jsoniter "github.com/json-iterator/go" + "github.com/rs/zerolog" +) + +var json = jsoniter.ConfigFastest + +// Logger manages the number of management streaming log sessions +type Logger struct { + sessions []*session + mu sync.RWMutex + + // Unique logger that isn't a io.Writer of the list of zerolog writers. This helps prevent management log + // statements from creating infinite recursion to export messages to a session and allows basic debugging and + // error statements to be issued in the management code itself. + Log *zerolog.Logger +} + +func NewLogger() *Logger { + log := zerolog.New(zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, + }).With().Timestamp().Logger().Level(zerolog.InfoLevel) + return &Logger{ + Log: &log, + } +} + +type LoggerListener interface { + // ActiveSession returns the first active session for the requested actor. + ActiveSession(actor) *session + // ActiveSession returns the count of active sessions. + ActiveSessions() int + // Listen appends the session to the list of sessions that receive log events. + Listen(*session) + // Remove a session from the available sessions that were receiving log events. + Remove(*session) +} + +func (l *Logger) ActiveSession(actor actor) *session { + l.mu.RLock() + defer l.mu.RUnlock() + for _, session := range l.sessions { + if session.actor.ID == actor.ID && session.active.Load() { + return session + } + } + return nil +} + +func (l *Logger) ActiveSessions() int { + l.mu.RLock() + defer l.mu.RUnlock() + count := 0 + for _, session := range l.sessions { + if session.active.Load() { + count += 1 + } + } + return count +} + +func (l *Logger) Listen(session *session) { + l.mu.Lock() + defer l.mu.Unlock() + session.active.Store(true) + l.sessions = append(l.sessions, session) +} + +func (l *Logger) Remove(session *session) { + l.mu.Lock() + defer l.mu.Unlock() + index := -1 + for i, v := range l.sessions { + if v == session { + index = i + break + } + } + if index == -1 { + // Not found + return + } + copy(l.sessions[index:], l.sessions[index+1:]) + l.sessions = l.sessions[:len(l.sessions)-1] +} + +// Write will write the log event to all sessions that have available capacity. For those that are full, the message +// will be dropped. +// This function is the interface that zerolog expects to call when a log event is to be written out. +func (l *Logger) Write(p []byte) (int, error) { + l.mu.RLock() + defer l.mu.RUnlock() + // return early if no active sessions + if len(l.sessions) == 0 { + return len(p), nil + } + event, err := parseZerologEvent(p) + // drop event if unable to parse properly + if err != nil { + l.Log.Debug().Msg("unable to parse log event") + return len(p), nil + } + for _, session := range l.sessions { + session.Insert(event) + } + return len(p), nil +} + +func (l *Logger) WriteLevel(level zerolog.Level, p []byte) (n int, err error) { + return l.Write(p) +} + +func parseZerologEvent(p []byte) (*Log, error) { + var fields map[string]interface{} + iter := json.BorrowIterator(p) + defer json.ReturnIterator(iter) + iter.ReadVal(&fields) + if iter.Error != nil { + return nil, iter.Error + } + logTime := time.Now().UTC().Format(zerolog.TimeFieldFormat) + if t, ok := fields[TimeKey]; ok { + if t, ok := t.(string); ok { + logTime = t + } + } + logLevel := Debug + // A zerolog Debug event can be created and then an error can be added + // via .Err(error), if so, we upgrade the level to error. + if _, hasError := fields["error"]; hasError { + logLevel = Error + } else { + if level, ok := fields[LevelKey]; ok { + if level, ok := level.(string); ok { + if logLevel, ok = ParseLogLevel(level); !ok { + logLevel = Debug + } + } + } + } + // Assume the event type is Cloudflared if unable to parse/find. This could be from log events that haven't + // yet been tagged with the appropriate EventType yet. + logEvent := Cloudflared + e := fields[EventTypeKey] + if e != nil { + if eventNumber, ok := e.(float64); ok { + logEvent = LogEventType(eventNumber) + } + } + logMessage := "" + if m, ok := fields[MessageKey]; ok { + if m, ok := m.(string); ok { + logMessage = m + } + } + event := Log{ + Time: logTime, + Level: logLevel, + Event: logEvent, + Message: logMessage, + } + // Remove the keys that have top level keys on Log + delete(fields, TimeKey) + delete(fields, LevelKey) + delete(fields, EventTypeKey) + delete(fields, MessageKey) + // The rest of the keys go into the Fields + event.Fields = fields + return &event, nil +} diff --git a/management/logger_test.go b/management/logger_test.go new file mode 100644 index 00000000..fda7fb4f --- /dev/null +++ b/management/logger_test.go @@ -0,0 +1,152 @@ +package management + +import ( + "context" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// No listening sessions will not write to the channel +func TestLoggerWrite_NoSessions(t *testing.T) { + logger := NewLogger() + zlog := zerolog.New(logger).With().Timestamp().Logger().Level(zerolog.InfoLevel) + + zlog.Info().Msg("hello") +} + +// Validate that the session receives the event +func TestLoggerWrite_OneSession(t *testing.T) { + logger := NewLogger() + zlog := zerolog.New(logger).With().Timestamp().Logger().Level(zerolog.InfoLevel) + _, cancel := context.WithCancel(context.Background()) + defer cancel() + + session := newSession(logWindow, actor{ID: actorID}, cancel) + logger.Listen(session) + defer logger.Remove(session) + assert.Equal(t, 1, logger.ActiveSessions()) + assert.Equal(t, session, logger.ActiveSession(actor{ID: actorID})) + zlog.Info().Int(EventTypeKey, int(HTTP)).Msg("hello") + select { + case event := <-session.listener: + assert.NotEmpty(t, event.Time) + assert.Equal(t, "hello", event.Message) + assert.Equal(t, Info, event.Level) + assert.Equal(t, HTTP, event.Event) + default: + assert.Fail(t, "expected an event to be in the listener") + } +} + +// Validate all sessions receive the same event +func TestLoggerWrite_MultipleSessions(t *testing.T) { + logger := NewLogger() + zlog := zerolog.New(logger).With().Timestamp().Logger().Level(zerolog.InfoLevel) + _, cancel := context.WithCancel(context.Background()) + defer cancel() + + session1 := newSession(logWindow, actor{}, cancel) + logger.Listen(session1) + defer logger.Remove(session1) + assert.Equal(t, 1, logger.ActiveSessions()) + + session2 := newSession(logWindow, actor{}, cancel) + logger.Listen(session2) + assert.Equal(t, 2, logger.ActiveSessions()) + + zlog.Info().Int(EventTypeKey, int(HTTP)).Msg("hello") + for _, session := range []*session{session1, session2} { + select { + case event := <-session.listener: + assert.NotEmpty(t, event.Time) + assert.Equal(t, "hello", event.Message) + assert.Equal(t, Info, event.Level) + assert.Equal(t, HTTP, event.Event) + default: + assert.Fail(t, "expected an event to be in the listener") + } + } + + // Close session2 and make sure session1 still receives events + logger.Remove(session2) + zlog.Info().Int(EventTypeKey, int(HTTP)).Msg("hello2") + select { + case event := <-session1.listener: + assert.NotEmpty(t, event.Time) + assert.Equal(t, "hello2", event.Message) + assert.Equal(t, Info, event.Level) + assert.Equal(t, HTTP, event.Event) + default: + assert.Fail(t, "expected an event to be in the listener") + } + + // Make sure a held reference to session2 doesn't receive events after being closed + select { + case <-session2.listener: + assert.Fail(t, "An event was not expected to be in the session listener") + default: + // pass + } +} + +type mockWriter struct { + event *Log + err error +} + +func (m *mockWriter) Write(p []byte) (int, error) { + m.event, m.err = parseZerologEvent(p) + return len(p), nil +} + +// Validate all event types are set properly +func TestParseZerologEvent_EventTypes(t *testing.T) { + writer := mockWriter{} + zlog := zerolog.New(&writer).With().Timestamp().Logger().Level(zerolog.InfoLevel) + + for _, test := range []LogEventType{ + Cloudflared, + HTTP, + TCP, + UDP, + } { + t.Run(test.String(), func(t *testing.T) { + defer func() { writer.err = nil }() + zlog.Info().Int(EventTypeKey, int(test)).Msg("test") + require.NoError(t, writer.err) + require.Equal(t, test, writer.event.Event) + }) + } + + // Invalid defaults to Cloudflared LogEventType + t.Run("invalid", func(t *testing.T) { + defer func() { writer.err = nil }() + zlog.Info().Str(EventTypeKey, "unknown").Msg("test") + require.NoError(t, writer.err) + require.Equal(t, Cloudflared, writer.event.Event) + }) +} + +// Validate top-level keys are removed from Fields +func TestParseZerologEvent_Fields(t *testing.T) { + writer := mockWriter{} + zlog := zerolog.New(&writer).With().Timestamp().Logger().Level(zerolog.InfoLevel) + zlog.Info().Int(EventTypeKey, int(Cloudflared)).Str("test", "test").Msg("test message") + require.NoError(t, writer.err) + event := writer.event + require.NotEmpty(t, event.Time) + require.Equal(t, Cloudflared, event.Event) + require.Equal(t, Info, event.Level) + require.Equal(t, "test message", event.Message) + + // Make sure Fields doesn't have other set keys used in the Log struct + require.NotEmpty(t, event.Fields) + require.Equal(t, "test", event.Fields["test"]) + require.NotContains(t, event.Fields, EventTypeKey) + require.NotContains(t, event.Fields, LevelKey) + require.NotContains(t, event.Fields, MessageKey) + require.NotContains(t, event.Fields, TimeKey) +} diff --git a/management/middleware.go b/management/middleware.go new file mode 100644 index 00000000..68b4a26e --- /dev/null +++ b/management/middleware.go @@ -0,0 +1,76 @@ +package management + +import ( + "context" + "fmt" + "net/http" +) + +type ctxKey int + +const ( + accessClaimsCtxKey ctxKey = iota +) + +const ( + connectorIDQuery = "connector_id" + accessTokenQuery = "access_token" +) + +var ( + errMissingAccessToken = managementError{Code: 1001, Message: "missing access_token query parameter"} +) + +// HTTP middleware setting the parsed access_token claims in the request context +func ValidateAccessTokenQueryMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate access token + accessToken := r.URL.Query().Get("access_token") + if accessToken == "" { + writeHTTPErrorResponse(w, errMissingAccessToken) + return + } + token, err := parseToken(accessToken) + if err != nil { + writeHTTPErrorResponse(w, errMissingAccessToken) + return + } + r = r.WithContext(context.WithValue(r.Context(), accessClaimsCtxKey, token)) + next.ServeHTTP(w, r) + }) +} + +// Middleware validation error struct for returning to the eyeball +type managementError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (m *managementError) Error() string { + return m.Message +} + +// Middleware validation error HTTP response JSON for returning to the eyeball +type managementErrorResponse struct { + Success bool `json:"success,omitempty"` + Errors []managementError `json:"errors,omitempty"` +} + +// writeErrorResponse will respond to the eyeball with basic HTTP JSON payloads with validation failure information +func writeHTTPErrorResponse(w http.ResponseWriter, errResp managementError) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + err := json.NewEncoder(w).Encode(managementErrorResponse{ + Success: false, + Errors: []managementError{errResp}, + }) + // we have already written the header, so write a basic error response if unable to encode the error + if err != nil { + // fallback to text message + http.Error(w, fmt.Sprintf( + "%d %s", + http.StatusBadRequest, + http.StatusText(http.StatusBadRequest), + ), http.StatusBadRequest) + } +} diff --git a/management/middleware_test.go b/management/middleware_test.go new file mode 100644 index 00000000..5af7e8f3 --- /dev/null +++ b/management/middleware_test.go @@ -0,0 +1,71 @@ +package management + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" +) + +func TestValidateAccessTokenQueryMiddleware(t *testing.T) { + r := chi.NewRouter() + r.Use(ValidateAccessTokenQueryMiddleware) + r.Get("/valid", func(w http.ResponseWriter, r *http.Request) { + claims, ok := r.Context().Value(accessClaimsCtxKey).(*managementTokenClaims) + assert.True(t, ok) + assert.True(t, claims.verify()) + w.WriteHeader(http.StatusOK) + }) + r.Get("/invalid", func(w http.ResponseWriter, r *http.Request) { + _, ok := r.Context().Value(accessClaimsCtxKey).(*managementTokenClaims) + assert.False(t, ok) + w.WriteHeader(http.StatusOK) + }) + + ts := httptest.NewServer(r) + defer ts.Close() + + // valid: with access_token query param + path := "/valid?access_token=" + validToken + resp, _ := testRequest(t, ts, "GET", path, nil) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + // invalid: unset token + path = "/invalid" + resp, err := testRequest(t, ts, "GET", path, nil) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + assert.NotNil(t, err) + assert.Equal(t, errMissingAccessToken, err.Errors[0]) + + // invalid: invalid token + path = "/invalid?access_token=eyJ" + resp, err = testRequest(t, ts, "GET", path, nil) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + assert.NotNil(t, err) + assert.Equal(t, errMissingAccessToken, err.Errors[0]) +} + +func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, *managementErrorResponse) { + req, err := http.NewRequest(method, ts.URL+path, body) + if err != nil { + t.Fatal(err) + return nil, nil + } + + resp, err := ts.Client().Do(req) + if err != nil { + t.Fatal(err) + return nil, nil + } + var claims managementErrorResponse + err = json.NewDecoder(resp.Body).Decode(&claims) + if err != nil { + return resp, nil + } + defer resp.Body.Close() + + return resp, &claims +} diff --git a/management/service.go b/management/service.go new file mode 100644 index 00000000..8964a05d --- /dev/null +++ b/management/service.go @@ -0,0 +1,302 @@ +package management + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "sync" + "time" + + "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "github.com/rs/zerolog" + "nhooyr.io/websocket" +) + +const ( + // In the current state, an invalid command was provided by the client + StatusInvalidCommand websocket.StatusCode = 4001 + reasonInvalidCommand = "expected start streaming as first event" + // There are a limited number of available streaming log sessions that cloudflared will service, exceeding this + // value will return this error to incoming requests. + StatusSessionLimitExceeded websocket.StatusCode = 4002 + reasonSessionLimitExceeded = "limit exceeded for streaming sessions" + // There is a limited idle time while not actively serving a session for a request before dropping the connection. + StatusIdleLimitExceeded websocket.StatusCode = 4003 + reasonIdleLimitExceeded = "session was idle for too long" +) + +type ManagementService struct { + // The management tunnel hostname + Hostname string + + // Host details related configurations + serviceIP string + clientID uuid.UUID + label string + + log *zerolog.Logger + router chi.Router + + // streamingMut is a lock to prevent concurrent requests to start streaming. Utilizing the atomic.Bool is not + // sufficient to complete this operation since many other checks during an incoming new request are needed + // to validate this before setting streaming to true. + streamingMut sync.Mutex + logger LoggerListener +} + +func New(managementHostname string, + serviceIP string, + clientID uuid.UUID, + label string, + log *zerolog.Logger, + logger LoggerListener, +) *ManagementService { + s := &ManagementService{ + Hostname: managementHostname, + log: log, + logger: logger, + serviceIP: serviceIP, + clientID: clientID, + label: label, + } + r := chi.NewRouter() + r.Use(ValidateAccessTokenQueryMiddleware) + r.Get("/ping", ping) + r.Head("/ping", ping) + r.Get("/logs", s.logs) + r.Get("/host_details", s.getHostDetails) + s.router = r + return s +} + +func (m *ManagementService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + m.router.ServeHTTP(w, r) +} + +// Management Ping handler +func ping(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) +} + +// The response provided by the /host_details endpoint +type getHostDetailsResponse struct { + ClientID string `json:"connector_id"` + IP string `json:"ip,omitempty"` + HostName string `json:"hostname,omitempty"` +} + +func (m *ManagementService) getHostDetails(w http.ResponseWriter, r *http.Request) { + var getHostDetailsResponse = getHostDetailsResponse{ + ClientID: m.clientID.String(), + } + if ip, err := getPrivateIP(m.serviceIP); err == nil { + getHostDetailsResponse.IP = ip + } + getHostDetailsResponse.HostName = m.getLabel() + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + json.NewEncoder(w).Encode(getHostDetailsResponse) +} + +func (m *ManagementService) getLabel() string { + if m.label != "" { + return fmt.Sprintf("custom:%s", m.label) + } + + // If no label is provided we return the system hostname. This is not + // a fqdn hostname. + hostname, err := os.Hostname() + if err != nil { + return "unknown" + } + return hostname +} + +// Get preferred private ip of this machine +func getPrivateIP(addr string) (string, error) { + conn, err := net.DialTimeout("tcp", addr, 1*time.Second) + if err != nil { + return "", err + } + defer conn.Close() + localAddr := conn.LocalAddr().String() + host, _, err := net.SplitHostPort(localAddr) + return host, err +} + +// readEvents will loop through all incoming websocket messages from a client and marshal them into the +// proper Event structure and pass through to the events channel. Any invalid messages sent will automatically +// terminate the connection. +func (m *ManagementService) readEvents(c *websocket.Conn, ctx context.Context, events chan<- *ClientEvent) { + for { + event, err := ReadClientEvent(c, ctx) + select { + case <-ctx.Done(): + return + default: + if err != nil { + // If the client (or the server) already closed the connection, don't attempt to close it again + if !IsClosed(err, m.log) { + m.log.Err(err).Send() + m.log.Err(c.Close(websocket.StatusUnsupportedData, err.Error())).Send() + } + // Any errors when reading the messages from the client will close the connection + return + } + events <- event + } + } +} + +// streamLogs will begin the process of reading from the Session listener and write the log events to the client. +func (m *ManagementService) streamLogs(c *websocket.Conn, ctx context.Context, session *session) { + for session.Active() { + select { + case <-ctx.Done(): + session.Stop() + return + case event := <-session.listener: + err := WriteEvent(c, ctx, &EventLog{ + ServerEvent: ServerEvent{Type: Logs}, + Logs: []*Log{event}, + }) + if err != nil { + // If the client (or the server) already closed the connection, don't attempt to close it again + if !IsClosed(err, m.log) { + m.log.Err(err).Send() + m.log.Err(c.Close(websocket.StatusInternalError, err.Error())).Send() + } + // Any errors when writing the messages to the client will stop streaming and close the connection + session.Stop() + return + } + default: + // No messages to send + } + } +} + +// canStartStream will check the conditions of the request and return if the session can begin streaming. +func (m *ManagementService) canStartStream(session *session) bool { + m.streamingMut.Lock() + defer m.streamingMut.Unlock() + // Limits to one actor for streaming logs + if m.logger.ActiveSessions() > 0 { + // Allow the same user to preempt their existing session to disconnect their old session and start streaming + // with this new session instead. + if existingSession := m.logger.ActiveSession(session.actor); existingSession != nil { + m.log.Info(). + Msgf("Another management session request for the same actor was requested; the other session will be disconnected to handle the new request.") + existingSession.Stop() + m.logger.Remove(existingSession) + existingSession.cancel() + } else { + m.log.Warn(). + Msgf("Another management session request was attempted but one session already being served; there is a limit of streaming log sessions to reduce overall performance impact.") + return false + } + } + return true +} + +// parseFilters will check the ClientEvent for start_streaming and assign filters if provided to the session +func (m *ManagementService) parseFilters(c *websocket.Conn, event *ClientEvent, session *session) bool { + // Expect the first incoming request + startEvent, ok := IntoClientEvent[EventStartStreaming](event, StartStreaming) + if !ok { + return false + } + session.Filters(startEvent.Filters) + return true +} + +// Management Streaming Logs accept handler +func (m *ManagementService) logs(w http.ResponseWriter, r *http.Request) { + c, err := websocket.Accept(w, r, nil) + if err != nil { + m.log.Debug().Msgf("management handshake: %s", err.Error()) + return + } + // Make sure the connection is closed if other go routines fail to close the connection after completing. + defer c.Close(websocket.StatusInternalError, "") + ctx, cancel := context.WithCancel(r.Context()) + defer cancel() + events := make(chan *ClientEvent) + go m.readEvents(c, ctx, events) + + // Send a heartbeat ping to hold the connection open even if not streaming. + ping := time.NewTicker(15 * time.Second) + defer ping.Stop() + + // Close the connection if no operation has occurred after the idle timeout. The timeout is halted + // when streaming logs is active. + idleTimeout := 5 * time.Minute + idle := time.NewTimer(idleTimeout) + defer idle.Stop() + + // Fetch the claims from the request context to acquire the actor + claims, ok := ctx.Value(accessClaimsCtxKey).(*managementTokenClaims) + if !ok || claims == nil { + // Typically should never happen as it is provided in the context from the middleware + m.log.Err(c.Close(websocket.StatusInternalError, "missing access_token")).Send() + return + } + + session := newSession(logWindow, claims.Actor, cancel) + defer m.logger.Remove(session) + + for { + select { + case <-ctx.Done(): + m.log.Debug().Msgf("management logs: context cancelled") + c.Close(websocket.StatusNormalClosure, "context closed") + return + case event := <-events: + switch event.Type { + case StartStreaming: + idle.Stop() + // Expect the first incoming request + startEvent, ok := IntoClientEvent[EventStartStreaming](event, StartStreaming) + if !ok { + m.log.Warn().Msgf("expected start_streaming as first recieved event") + m.log.Err(c.Close(StatusInvalidCommand, reasonInvalidCommand)).Send() + return + } + // Make sure the session can start + if !m.canStartStream(session) { + m.log.Err(c.Close(StatusSessionLimitExceeded, reasonSessionLimitExceeded)).Send() + return + } + session.Filters(startEvent.Filters) + m.logger.Listen(session) + m.log.Debug().Msgf("Streaming logs") + go m.streamLogs(c, ctx, session) + continue + case StopStreaming: + idle.Reset(idleTimeout) + // Stop the current session for the current actor who requested it + session.Stop() + m.logger.Remove(session) + case UnknownClientEventType: + fallthrough + default: + // Drop unknown events and close connection + m.log.Debug().Msgf("unexpected management message received: %s", event.Type) + // If the client (or the server) already closed the connection, don't attempt to close it again + if !IsClosed(err, m.log) { + m.log.Err(err).Err(c.Close(websocket.StatusUnsupportedData, err.Error())).Send() + } + return + } + case <-ping.C: + go c.Ping(ctx) + case <-idle.C: + c.Close(StatusIdleLimitExceeded, reasonIdleLimitExceeded) + return + } + } +} diff --git a/management/service_test.go b/management/service_test.go new file mode 100644 index 00000000..7f962998 --- /dev/null +++ b/management/service_test.go @@ -0,0 +1,126 @@ +package management + +import ( + "context" + "io" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "nhooyr.io/websocket" + + "github.com/cloudflare/cloudflared/internal/test" +) + +var ( + noopLogger = zerolog.New(io.Discard) +) + +func TestReadEventsLoop(t *testing.T) { + sentEvent := EventStartStreaming{ + ClientEvent: ClientEvent{Type: StartStreaming}, + } + client, server := test.WSPipe(nil, nil) + client.CloseRead(context.Background()) + defer func() { + client.Close(websocket.StatusInternalError, "") + }() + go func() { + err := WriteEvent(client, context.Background(), &sentEvent) + require.NoError(t, err) + }() + m := ManagementService{ + log: &noopLogger, + } + events := make(chan *ClientEvent) + go m.readEvents(server, context.Background(), events) + event := <-events + require.Equal(t, sentEvent.Type, event.Type) + server.Close(websocket.StatusInternalError, "") +} + +func TestReadEventsLoop_ContextCancelled(t *testing.T) { + client, server := test.WSPipe(nil, nil) + ctx, cancel := context.WithCancel(context.Background()) + client.CloseRead(ctx) + defer func() { + client.Close(websocket.StatusInternalError, "") + }() + m := ManagementService{ + log: &noopLogger, + } + events := make(chan *ClientEvent) + go func() { + time.Sleep(time.Second) + cancel() + }() + // Want to make sure this function returns when context is cancelled + m.readEvents(server, ctx, events) + server.Close(websocket.StatusInternalError, "") +} + +func TestCanStartStream_NoSessions(t *testing.T) { + m := ManagementService{ + log: &noopLogger, + logger: &Logger{ + Log: &noopLogger, + }, + } + _, cancel := context.WithCancel(context.Background()) + session := newSession(0, actor{}, cancel) + assert.True(t, m.canStartStream(session)) +} + +func TestCanStartStream_ExistingSessionDifferentActor(t *testing.T) { + m := ManagementService{ + log: &noopLogger, + logger: &Logger{ + Log: &noopLogger, + }, + } + _, cancel := context.WithCancel(context.Background()) + session1 := newSession(0, actor{ID: "test"}, cancel) + assert.True(t, m.canStartStream(session1)) + m.logger.Listen(session1) + assert.True(t, session1.Active()) + + // Try another session + session2 := newSession(0, actor{ID: "test2"}, cancel) + assert.Equal(t, 1, m.logger.ActiveSessions()) + assert.False(t, m.canStartStream(session2)) + + // Close session1 + m.logger.Remove(session1) + assert.True(t, session1.Active()) // Remove doesn't stop a session + session1.Stop() + assert.False(t, session1.Active()) + assert.Equal(t, 0, m.logger.ActiveSessions()) + + // Try session2 again + assert.True(t, m.canStartStream(session2)) +} + +func TestCanStartStream_ExistingSessionSameActor(t *testing.T) { + m := ManagementService{ + log: &noopLogger, + logger: &Logger{ + Log: &noopLogger, + }, + } + actor := actor{ID: "test"} + _, cancel := context.WithCancel(context.Background()) + session1 := newSession(0, actor, cancel) + assert.True(t, m.canStartStream(session1)) + m.logger.Listen(session1) + assert.True(t, session1.Active()) + + // Try another session + session2 := newSession(0, actor, cancel) + assert.Equal(t, 1, m.logger.ActiveSessions()) + assert.True(t, m.canStartStream(session2)) + // session1 is removed and stopped + assert.Equal(t, 0, m.logger.ActiveSessions()) + assert.False(t, session1.Active()) +} diff --git a/management/session.go b/management/session.go new file mode 100644 index 00000000..8b8ccd89 --- /dev/null +++ b/management/session.go @@ -0,0 +1,120 @@ +package management + +import ( + "context" + "math/rand" + "sync/atomic" +) + +const ( + // Indicates how many log messages the listener will hold before dropping. + // Provides a throttling mechanism to drop latest messages if the sender + // can't keep up with the influx of log messages. + logWindow = 30 +) + +// session captures a streaming logs session for a connection of an actor. +type session struct { + // Indicates if the session is streaming or not. Modifying this will affect the active session. + active atomic.Bool + // Allows the session to control the context of the underlying connection to close it out when done. Mostly + // used by the LoggerListener to close out and cleanup a session. + cancel context.CancelFunc + // Actor who started the session + actor actor + // Buffered channel that holds the recent log events + listener chan *Log + // Types of log events that this session will provide through the listener + filters *StreamingFilters + // Sampling of the log events this session will send (runs after all other filters if available) + sampler *sampler +} + +// NewSession creates a new session. +func newSession(size int, actor actor, cancel context.CancelFunc) *session { + s := &session{ + active: atomic.Bool{}, + cancel: cancel, + actor: actor, + listener: make(chan *Log, size), + filters: &StreamingFilters{}, + } + return s +} + +// Filters assigns the StreamingFilters to the session +func (s *session) Filters(filters *StreamingFilters) { + if filters != nil { + s.filters = filters + sampling := filters.Sampling + // clamp the sampling values between 0 and 1 + if sampling < 0 { + sampling = 0 + } + if sampling > 1 { + sampling = 1 + } + s.filters.Sampling = sampling + if sampling > 0 && sampling < 1 { + s.sampler = &sampler{ + p: int(sampling * 100), + } + } + } else { + s.filters = &StreamingFilters{} + } +} + +// Insert attempts to insert the log to the session. If the log event matches the provided session filters, it +// will be applied to the listener. +func (s *session) Insert(log *Log) { + // Level filters are optional + if s.filters.Level != nil { + if *s.filters.Level > log.Level { + return + } + } + // Event filters are optional + if len(s.filters.Events) != 0 && !contains(s.filters.Events, log.Event) { + return + } + // Sampling is also optional + if s.sampler != nil && !s.sampler.Sample() { + return + } + select { + case s.listener <- log: + default: + // buffer is full, discard + } +} + +// Active returns if the session is active +func (s *session) Active() bool { + return s.active.Load() +} + +// Stop will halt the session +func (s *session) Stop() { + s.active.Store(false) +} + +func contains(array []LogEventType, t LogEventType) bool { + for _, v := range array { + if v == t { + return true + } + } + return false +} + +// sampler will send approximately every p percentage log events out of 100. +type sampler struct { + p int +} + +// Sample returns true if the event should be part of the sample, false if the event should be dropped. +func (s *sampler) Sample() bool { + return rand.Intn(100) <= s.p + +} diff --git a/management/session_test.go b/management/session_test.go new file mode 100644 index 00000000..7e26f1dc --- /dev/null +++ b/management/session_test.go @@ -0,0 +1,147 @@ +package management + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Validate the active states of the session +func TestSession_ActiveControl(t *testing.T) { + _, cancel := context.WithCancel(context.Background()) + defer cancel() + session := newSession(4, actor{}, cancel) + // session starts out not active + assert.False(t, session.Active()) + session.active.Store(true) + assert.True(t, session.Active()) + session.Stop() + assert.False(t, session.Active()) +} + +// Validate that the session filters events +func TestSession_Insert(t *testing.T) { + _, cancel := context.WithCancel(context.Background()) + defer cancel() + infoLevel := new(LogLevel) + *infoLevel = Info + warnLevel := new(LogLevel) + *warnLevel = Warn + for _, test := range []struct { + name string + filters StreamingFilters + expectLog bool + }{ + { + name: "none", + expectLog: true, + }, + { + name: "level", + filters: StreamingFilters{ + Level: infoLevel, + }, + expectLog: true, + }, + { + name: "filtered out level", + filters: StreamingFilters{ + Level: warnLevel, + }, + expectLog: false, + }, + { + name: "events", + filters: StreamingFilters{ + Events: []LogEventType{HTTP}, + }, + expectLog: true, + }, + { + name: "filtered out event", + filters: StreamingFilters{ + Events: []LogEventType{Cloudflared}, + }, + expectLog: false, + }, + { + name: "sampling", + filters: StreamingFilters{ + Sampling: 0.9999999, + }, + expectLog: true, + }, + { + name: "sampling (invalid negative)", + filters: StreamingFilters{ + Sampling: -1.0, + }, + expectLog: true, + }, + { + name: "sampling (invalid too large)", + filters: StreamingFilters{ + Sampling: 2.0, + }, + expectLog: true, + }, + { + name: "filter and event", + filters: StreamingFilters{ + Level: infoLevel, + Events: []LogEventType{HTTP}, + }, + expectLog: true, + }, + } { + t.Run(test.name, func(t *testing.T) { + session := newSession(4, actor{}, cancel) + session.Filters(&test.filters) + log := Log{ + Time: time.Now().UTC().Format(time.RFC3339), + Event: HTTP, + Level: Info, + Message: "test", + } + session.Insert(&log) + select { + case <-session.listener: + require.True(t, test.expectLog) + default: + require.False(t, test.expectLog) + } + }) + } +} + +// Validate that the session has a max amount of events to hold +func TestSession_InsertOverflow(t *testing.T) { + _, cancel := context.WithCancel(context.Background()) + defer cancel() + session := newSession(1, actor{}, cancel) + log := Log{ + Time: time.Now().UTC().Format(time.RFC3339), + Event: HTTP, + Level: Info, + Message: "test", + } + // Insert 2 but only max channel size for 1 + session.Insert(&log) + session.Insert(&log) + select { + case <-session.listener: + // pass + default: + require.Fail(t, "expected one log event") + } + // Second dequeue should fail + select { + case <-session.listener: + require.Fail(t, "expected no more remaining log events") + default: + // pass + } +} diff --git a/management/token.go b/management/token.go new file mode 100644 index 00000000..066c718c --- /dev/null +++ b/management/token.go @@ -0,0 +1,55 @@ +package management + +import ( + "fmt" + + "github.com/go-jose/go-jose/v3/jwt" +) + +type managementTokenClaims struct { + Tunnel tunnel `json:"tun"` + Actor actor `json:"actor"` +} + +// VerifyTunnel compares the tun claim isn't empty +func (c *managementTokenClaims) verify() bool { + return c.Tunnel.verify() && c.Actor.verify() +} + +type tunnel struct { + ID string `json:"id"` + AccountTag string `json:"account_tag"` +} + +// verify compares the tun claim isn't empty +func (t *tunnel) verify() bool { + return t.AccountTag != "" && t.ID != "" +} + +type actor struct { + ID string `json:"id"` + Support bool `json:"support"` +} + +// verify checks the ID claim isn't empty +func (t *actor) verify() bool { + return t.ID != "" +} + +func parseToken(token string) (*managementTokenClaims, error) { + jwt, err := jwt.ParseSigned(token) + if err != nil { + return nil, fmt.Errorf("malformed jwt: %v", err) + } + + var claims managementTokenClaims + // This is actually safe because we verify the token in the edge before it reaches cloudflared + err = jwt.UnsafeClaimsWithoutVerification(&claims) + if err != nil { + return nil, fmt.Errorf("malformed jwt: %v", err) + } + if !claims.verify() { + return nil, fmt.Errorf("invalid management token format provided") + } + return &claims, nil +} diff --git a/management/token_test.go b/management/token_test.go new file mode 100644 index 00000000..9e6e6f86 --- /dev/null +++ b/management/token_test.go @@ -0,0 +1,130 @@ +package management + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/square/go-jose.v2" +) + +const ( + validToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ.eyJ0dW4iOnsiaWQiOiI3YjA5ODE0OS01MWZlLTRlZTUtYTY4Ny0zZTM3NDQ2NmVmYzciLCJhY2NvdW50X3RhZyI6ImNkMzkxZTljMDYyNmE4Zjc2Y2IxZjY3MGY2NTkxYjA1In0sImFjdG9yIjp7ImlkIjoiZGNhcnJAY2xvdWRmbGFyZS5jb20iLCJzdXBwb3J0IjpmYWxzZX0sInJlcyI6WyJsb2dzIl0sImV4cCI6MTY3NzExNzY5NiwiaWF0IjoxNjc3MTE0MDk2LCJpc3MiOiJ0dW5uZWxzdG9yZSJ9.mKenOdOy3Xi4O-grldFnAAemdlE9WajEpTDC_FwezXQTstWiRTLwU65P5jt4vNsIiZA4OJRq7bH-QYID9wf9NA" + + accountTag = "cd391e9c0626a8f76cb1f670f6591b05" + tunnelID = "7b098149-51fe-4ee5-a687-3e374466efc7" + actorID = "45d2751e-6b59-45a9-814d-f630786bd0cd" +) + +type invalidManagementTokenClaims struct { + Invalid string `json:"invalid"` +} + +func TestParseToken(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + for _, test := range []struct { + name string + claims any + err error + }{ + { + name: "Valid", + claims: managementTokenClaims{ + Tunnel: tunnel{ + ID: tunnelID, + AccountTag: accountTag, + }, + Actor: actor{ + ID: actorID, + }, + }, + }, + { + name: "Invalid claims", + claims: invalidManagementTokenClaims{Invalid: "invalid"}, + err: errors.New("invalid management token format provided"), + }, + { + name: "Missing Tunnel", + claims: managementTokenClaims{ + Actor: actor{ + ID: actorID, + }, + }, + err: errors.New("invalid management token format provided"), + }, + { + name: "Missing Tunnel ID", + claims: managementTokenClaims{ + Tunnel: tunnel{ + AccountTag: accountTag, + }, + Actor: actor{ + ID: actorID, + }, + }, + err: errors.New("invalid management token format provided"), + }, + { + name: "Missing Account Tag", + claims: managementTokenClaims{ + Tunnel: tunnel{ + ID: tunnelID, + }, + Actor: actor{ + ID: actorID, + }, + }, + err: errors.New("invalid management token format provided"), + }, + { + name: "Missing Actor", + claims: managementTokenClaims{ + Tunnel: tunnel{ + ID: tunnelID, + AccountTag: accountTag, + }, + }, + err: errors.New("invalid management token format provided"), + }, + { + name: "Missing Actor ID", + claims: managementTokenClaims{ + Tunnel: tunnel{ + ID: tunnelID, + }, + Actor: actor{}, + }, + err: errors.New("invalid management token format provided"), + }, + } { + t.Run(test.name, func(t *testing.T) { + jwt := signToken(t, test.claims, key) + claims, err := parseToken(jwt) + if test.err != nil { + require.EqualError(t, err, test.err.Error()) + return + } + require.Nil(t, err) + require.Equal(t, test.claims, *claims) + }) + } +} + +func signToken(t *testing.T, token any, key *ecdsa.PrivateKey) string { + opts := (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "1") + signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: key}, opts) + require.NoError(t, err) + payload, err := json.Marshal(token) + require.NoError(t, err) + jws, err := signer.Sign(payload) + require.NoError(t, err) + jwt, err := jws.CompactSerialize() + require.NoError(t, err) + return jwt +} diff --git a/metrics/metrics.go b/metrics/metrics.go index f8b3b1bc..1759451e 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" @@ -18,36 +17,41 @@ import ( ) const ( - shutdownTimeout = time.Second * 15 - startupTime = time.Millisecond * 500 + startupTime = time.Millisecond * 500 + defaultShutdownTimeout = time.Second * 15 ) +type Config struct { + ReadyServer *ReadyServer + QuickTunnelHostname string + Orchestrator orchestrator + + ShutdownTimeout time.Duration +} + type orchestrator interface { GetVersionedConfigJSON() ([]byte, error) } func newMetricsHandler( - readyServer *ReadyServer, - quickTunnelHostname string, - orchestrator orchestrator, + config Config, log *zerolog.Logger, -) *mux.Router { - router := mux.NewRouter() - router.PathPrefix("/debug/").Handler(http.DefaultServeMux) - +) *http.ServeMux { + router := http.NewServeMux() + router.Handle("/debug/", http.DefaultServeMux) router.Handle("/metrics", promhttp.Handler()) router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, "OK\n") }) - if readyServer != nil { - router.Handle("/ready", readyServer) + if config.ReadyServer != nil { + router.Handle("/ready", config.ReadyServer) } router.HandleFunc("/quicktunnel", func(w http.ResponseWriter, r *http.Request) { - _, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, quickTunnelHostname) + _, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, config.QuickTunnelHostname) }) - if orchestrator != nil { + if config.Orchestrator != nil { router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { - json, err := orchestrator.GetVersionedConfigJSON() + json, err := config.Orchestrator.GetVersionedConfigJSON() if err != nil { w.WriteHeader(500) _, _ = fmt.Fprintf(w, "ERR: %v", err) @@ -63,10 +67,8 @@ func newMetricsHandler( func ServeMetrics( l net.Listener, - shutdownC <-chan struct{}, - readyServer *ReadyServer, - quickTunnelHostname string, - orchestrator orchestrator, + ctx context.Context, + config Config, log *zerolog.Logger, ) (err error) { var wg sync.WaitGroup @@ -74,7 +76,7 @@ func ServeMetrics( trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true } // TODO: parameterize ReadTimeout and WriteTimeout. The maximum time we can // profile CPU usage depends on WriteTimeout - h := newMetricsHandler(readyServer, quickTunnelHostname, orchestrator, log) + h := newMetricsHandler(config, log) server := &http.Server{ ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, @@ -91,7 +93,12 @@ func ServeMetrics( // fully started up. So add artificial delay. time.Sleep(startupTime) - <-shutdownC + <-ctx.Done() + shutdownTimeout := config.ShutdownTimeout + if shutdownTimeout == 0 { + shutdownTimeout = defaultShutdownTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) _ = server.Shutdown(ctx) cancel() diff --git a/orchestration/orchestrator.go b/orchestration/orchestrator.go index 6bf62a5d..5967ee84 100644 --- a/orchestration/orchestrator.go +++ b/orchestration/orchestrator.go @@ -28,8 +28,9 @@ type Orchestrator struct { lock sync.RWMutex // Underlying value is proxy.Proxy, can be read without the lock, but still needs the lock to update proxy atomic.Value - // TODO: TUN-6815 Use atomic.Bool once we upgrade to go 1.19. 1 Means enabled and 0 means disabled - warpRoutingEnabled uint32 + // Set of local ingress rules defined at cloudflared startup (separate from user-defined ingress rules) + localRules []ingress.Rule + warpRoutingEnabled atomic.Bool config *Config tags []tunnelpogs.Tag log *zerolog.Logger @@ -40,10 +41,11 @@ type Orchestrator struct { proxyShutdownC chan<- struct{} } -func NewOrchestrator(ctx context.Context, config *Config, tags []tunnelpogs.Tag, log *zerolog.Logger) (*Orchestrator, error) { +func NewOrchestrator(ctx context.Context, config *Config, tags []tunnelpogs.Tag, localRules []ingress.Rule, log *zerolog.Logger) (*Orchestrator, error) { o := &Orchestrator{ // Lowest possible version, any remote configuration will have version higher than this currentVersion: 0, + localRules: localRules, config: config, tags: tags, log: log, @@ -112,6 +114,9 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i default: } + // Assign the local ingress rules to the parsed ingress + ingressRules.LocalRules = o.localRules + // Start new proxy before closing the ones from last version. // The upside is we don't need to restart proxy from last version, which can fail // The downside is new version might have ingress rule that require previous version to be shutdown first @@ -120,14 +125,14 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRouting i if err := ingressRules.StartOrigins(o.log, proxyShutdownC); err != nil { return errors.Wrap(err, "failed to start origin") } - newProxy := proxy.NewOriginProxy(ingressRules, warpRouting, o.tags, o.log) - o.proxy.Store(newProxy) + proxy := proxy.NewOriginProxy(ingressRules, warpRouting, o.tags, o.log) + o.proxy.Store(proxy) o.config.Ingress = &ingressRules o.config.WarpRouting = warpRouting if warpRouting.Enabled { - atomic.StoreUint32(&o.warpRoutingEnabled, 1) + o.warpRoutingEnabled.Store(true) } else { - atomic.StoreUint32(&o.warpRoutingEnabled, 0) + o.warpRoutingEnabled.Store(false) } // If proxyShutdownC is nil, there is no previous running proxy @@ -188,7 +193,7 @@ func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) { o.log.Error().Msg(err.Error()) return nil, err } - proxy, ok := val.(*proxy.Proxy) + proxy, ok := val.(connection.OriginProxy) if !ok { err := fmt.Errorf("origin proxy has unexpected value %+v", val) o.log.Error().Msg(err.Error()) @@ -197,12 +202,8 @@ func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) { return proxy, nil } -// TODO: TUN-6815 consider storing WarpRouting.Enabled as atomic.Bool once we upgrade to go 1.19 -func (o *Orchestrator) WarpRoutingEnabled() (enabled bool) { - if atomic.LoadUint32(&o.warpRoutingEnabled) == 0 { - return false - } - return true +func (o *Orchestrator) WarpRoutingEnabled() bool { + return o.warpRoutingEnabled.Load() } func (o *Orchestrator) waitToCloseLastProxy() { diff --git a/orchestration/orchestrator_test.go b/orchestration/orchestrator_test.go index d3e1ee62..17d54f47 100644 --- a/orchestration/orchestrator_test.go +++ b/orchestration/orchestrator_test.go @@ -14,6 +14,7 @@ import ( "time" "github.com/gobwas/ws/wsutil" + "github.com/google/uuid" gows "github.com/gorilla/websocket" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -21,7 +22,7 @@ import ( "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/ingress" - "github.com/cloudflare/cloudflared/proxy" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/tracing" tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" ) @@ -50,11 +51,11 @@ func TestUpdateConfiguration(t *testing.T) { initConfig := &Config{ Ingress: &ingress.Ingress{}, } - orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger) + orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{ingress.NewManagementRule(management.New("management.argotunnel.com", "1.1.1.1:80", uuid.Nil, "", &testLogger, nil))}, &testLogger) require.NoError(t, err) initOriginProxy, err := orchestrator.GetOriginProxy() require.NoError(t, err) - require.IsType(t, &proxy.Proxy{}, initOriginProxy) + require.Implements(t, (*connection.OriginProxy)(nil), initOriginProxy) require.False(t, orchestrator.WarpRoutingEnabled()) configJSONV2 := []byte(` @@ -95,6 +96,10 @@ func TestUpdateConfiguration(t *testing.T) { updateWithValidation(t, orchestrator, 2, configJSONV2) configV2 := orchestrator.config + // Validate local ingress rules + require.Equal(t, "management.argotunnel.com", configV2.Ingress.LocalRules[0].Hostname) + require.True(t, configV2.Ingress.LocalRules[0].Matches("management.argotunnel.com", "/ping")) + require.Equal(t, "management", configV2.Ingress.LocalRules[0].Service.String()) // Validate ingress rule 0 require.Equal(t, "jira.tunnel.org", configV2.Ingress.Rules[0].Hostname) require.True(t, configV2.Ingress.Rules[0].Matches("jira.tunnel.org", "/login")) @@ -128,7 +133,7 @@ func TestUpdateConfiguration(t *testing.T) { originProxyV2, err := orchestrator.GetOriginProxy() require.NoError(t, err) - require.IsType(t, &proxy.Proxy{}, originProxyV2) + require.Implements(t, (*connection.OriginProxy)(nil), originProxyV2) require.NotEqual(t, originProxyV2, initOriginProxy) // Should not downgrade to an older version @@ -172,7 +177,7 @@ func TestUpdateConfiguration(t *testing.T) { originProxyV10, err := orchestrator.GetOriginProxy() require.NoError(t, err) - require.IsType(t, &proxy.Proxy{}, originProxyV10) + require.Implements(t, (*connection.OriginProxy)(nil), originProxyV10) require.NotEqual(t, originProxyV10, originProxyV2) } @@ -257,7 +262,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, &testLogger) + orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, []ingress.Rule{}, &testLogger) require.NoError(t, err) updateWithValidation(t, orchestrator, 1, configJSONV1) @@ -358,7 +363,7 @@ func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Respo return nil, err } - err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, &log), false) + err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, 0, &log), false) if err != nil { return nil, err } @@ -484,7 +489,7 @@ func TestClosePreviousProxies(t *testing.T) { ) ctx, cancel := context.WithCancel(context.Background()) - orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, &testLogger) + orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, []ingress.Rule{}, &testLogger) require.NoError(t, err) updateWithValidation(t, orchestrator, 1, configWithHelloWorld) @@ -539,7 +544,7 @@ func TestPersistentConnection(t *testing.T) { initConfig := &Config{ Ingress: &ingress.Ingress{}, } - orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger) + orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, []ingress.Rule{}, &testLogger) require.NoError(t, err) wsOrigin := httptest.NewServer(http.HandlerFunc(wsEcho)) @@ -608,7 +613,7 @@ func TestPersistentConnection(t *testing.T) { respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket, &log) require.NoError(t, err) - err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, &log), true) + err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, 0, &log), true) require.NoError(t, err) }() diff --git a/proxy/proxy.go b/proxy/proxy.go index 71990cf9..1664ead3 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -16,18 +16,22 @@ import ( "github.com/cloudflare/cloudflared/cfio" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/ingress" + "github.com/cloudflare/cloudflared/management" + "github.com/cloudflare/cloudflared/stream" "github.com/cloudflare/cloudflared/tracing" tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" - "github.com/cloudflare/cloudflared/websocket" ) const ( // TagHeaderNamePrefix indicates a Cloudflared Warp Tag prefix that gets appended for warp traffic stream headers. TagHeaderNamePrefix = "Cf-Warp-Tag-" LogFieldCFRay = "cfRay" + LogFieldLBProbe = "lbProbe" LogFieldRule = "ingressRule" LogFieldOriginService = "originService" LogFieldFlowID = "flowID" + LogFieldConnIndex = "connIndex" + LogFieldDestAddr = "destAddr" trailerHeaderName = "Trailer" ) @@ -36,6 +40,7 @@ const ( type Proxy struct { ingressRules ingress.Ingress warpRouting *ingress.WarpRoutingService + management *ingress.ManagementService tags []tunnelpogs.Tag log *zerolog.Logger } @@ -94,9 +99,10 @@ func (p *Proxy) ProxyHTTP( trace.WithAttributes(attribute.String("req-host", req.Host))) rule, ruleNum := p.ingressRules.FindMatchingRule(req.Host, req.URL.Path) logFields := logFields{ - cfRay: cfRay, - lbProbe: lbProbe, - rule: ruleNum, + cfRay: cfRay, + lbProbe: lbProbe, + rule: ruleNum, + connIndex: tr.ConnIndex, } p.logRequest(req, logFields) ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum)) @@ -138,6 +144,9 @@ func (p *Proxy) ProxyHTTP( return err } return nil + case ingress.HTTPLocalProxy: + p.proxyLocalRequest(originProxy, w, req, isWebsocket) + return nil default: return fmt.Errorf("Unrecognized service: %s, %t", rule.Service, originProxy) } @@ -163,14 +172,24 @@ func (p *Proxy) ProxyTCP( tracedCtx := tracing.NewTracedContext(serveCtx, req.CfTraceID, p.log) - p.log.Debug().Str(LogFieldFlowID, req.FlowID).Msg("tcp proxy stream started") + p.log.Debug(). + Int(management.EventTypeKey, int(management.TCP)). + Str(LogFieldFlowID, req.FlowID). + Str(LogFieldDestAddr, req.Dest). + Uint8(LogFieldConnIndex, req.ConnIndex). + Msg("tcp proxy stream started") if err := p.proxyStream(tracedCtx, rwa, req.Dest, p.warpRouting.Proxy); err != nil { p.logRequestError(err, req.CFRay, req.FlowID, "", ingress.ServiceWarpRouting) return err } - p.log.Debug().Str(LogFieldFlowID, req.FlowID).Msg("tcp proxy stream finished successfully") + p.log.Debug(). + Int(management.EventTypeKey, int(management.TCP)). + Str(LogFieldFlowID, req.FlowID). + Str(LogFieldDestAddr, req.Dest). + Uint8(LogFieldConnIndex, req.ConnIndex). + Msg("tcp proxy stream finished successfully") return nil } @@ -257,11 +276,13 @@ func (p *Proxy) proxyHTTPRequest( reader: tr.Request.Body, } - websocket.Stream(eyeballStream, rwc, p.log) + stream.Pipe(eyeballStream, rwc, p.log) return nil } - _, _ = cfio.Copy(w, resp.Body) + if _, err = cfio.Copy(w, resp.Body); err != nil { + return err + } // copy trailers copyTrailers(w, resp) @@ -286,6 +307,7 @@ func (p *Proxy) proxyStream( return err } connectSpan.End() + defer originConn.Close() encodedSpans := tr.GetSpans() @@ -293,19 +315,21 @@ func (p *Proxy) proxyStream( return err } - streamCtx, cancel := context.WithCancel(ctx) - defer cancel() - - go func() { - // streamCtx is done if req is cancelled or if Stream returns - <-streamCtx.Done() - originConn.Close() - }() - originConn.Stream(ctx, rwa, p.log) return nil } +func (p *Proxy) proxyLocalRequest(proxy ingress.HTTPLocalProxy, w connection.ResponseWriter, req *http.Request, isWebsocket bool) { + if isWebsocket { + // These headers are added since they are stripped off during an eyeball request to origintunneld, but they + // are required during the Handshake process of a WebSocket request. + req.Header.Set("Connection", "Upgrade") + req.Header.Set("Upgrade", "websocket") + req.Header.Set("Sec-Websocket-Version", "13") + } + proxy.ServeHTTP(w, req) +} + type bidirectionalStream struct { reader io.Reader writer io.Writer @@ -326,10 +350,11 @@ func (p *Proxy) appendTagHeaders(r *http.Request) { } type logFields struct { - cfRay string - lbProbe bool - rule interface{} - flowID string + cfRay string + lbProbe bool + rule int + flowID string + connIndex uint8 } func copyTrailers(w connection.ResponseWriter, response *http.Response) { @@ -341,44 +366,41 @@ func copyTrailers(w connection.ResponseWriter, response *http.Response) { } func (p *Proxy) logRequest(r *http.Request, fields logFields) { + log := p.log.With().Int(management.EventTypeKey, int(management.HTTP)).Logger() + event := log.Debug() if fields.cfRay != "" { - p.log.Debug().Msgf("CF-RAY: %s %s %s %s", fields.cfRay, r.Method, r.URL, r.Proto) - } else if fields.lbProbe { - p.log.Debug().Msgf("CF-RAY: %s Load Balancer health check %s %s %s", fields.cfRay, r.Method, r.URL, r.Proto) - } else { - p.log.Debug().Msgf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", r.Method, r.URL, r.Proto) + event = event.Str(LogFieldCFRay, fields.cfRay) } - p.log.Debug(). - Str("CF-RAY", fields.cfRay). - Str("Header", fmt.Sprintf("%+v", r.Header)). + if fields.lbProbe { + event = event.Bool(LogFieldLBProbe, fields.lbProbe) + } + if fields.cfRay == "" && !fields.lbProbe { + log.Debug().Msgf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", r.Method, r.URL, r.Proto) + } + event. + Uint8(LogFieldConnIndex, fields.connIndex). Str("host", r.Host). Str("path", r.URL.Path). - Interface("rule", fields.rule). - Msg("Inbound request") - - if contentLen := r.ContentLength; contentLen == -1 { - p.log.Debug().Msgf("CF-RAY: %s Request Content length unknown", fields.cfRay) - } else { - p.log.Debug().Msgf("CF-RAY: %s Request content length %d", fields.cfRay, contentLen) - } + Interface(LogFieldRule, fields.rule). + Interface("headers", r.Header). + Int64("content-length", r.ContentLength). + Msgf("%s %s %s", r.Method, r.URL, r.Proto) } func (p *Proxy) logOriginResponse(resp *http.Response, fields logFields) { responseByCode.WithLabelValues(strconv.Itoa(resp.StatusCode)).Inc() + event := p.log.Debug() if fields.cfRay != "" { - p.log.Debug().Msgf("CF-RAY: %s Status: %s served by ingress %d", fields.cfRay, resp.Status, fields.rule) - } else if fields.lbProbe { - p.log.Debug().Msgf("Response to Load Balancer health check %s", resp.Status) - } else { - p.log.Debug().Msgf("Status: %s served by ingress %v", resp.Status, fields.rule) + event = event.Str(LogFieldCFRay, fields.cfRay) } - p.log.Debug().Msgf("CF-RAY: %s Response Headers %+v", fields.cfRay, resp.Header) - - if contentLen := resp.ContentLength; contentLen == -1 { - p.log.Debug().Msgf("CF-RAY: %s Response content length unknown", fields.cfRay) - } else { - p.log.Debug().Msgf("CF-RAY: %s Response content length %d", fields.cfRay, contentLen) + if fields.lbProbe { + event = event.Bool(LogFieldLBProbe, fields.lbProbe) } + event. + Int(management.EventTypeKey, int(management.HTTP)). + Uint8(LogFieldConnIndex, fields.connIndex). + Int64("content-length", resp.ContentLength). + Msgf("%s", resp.Status) } func (p *Proxy) logRequestError(err error, cfRay string, flowID string, rule, service string) { @@ -388,7 +410,9 @@ func (p *Proxy) logRequestError(err error, cfRay string, flowID string, rule, se log = log.Str(LogFieldCFRay, cfRay) } if flowID != "" { - log = log.Str(LogFieldFlowID, flowID) + log = log.Str(LogFieldFlowID, flowID).Int(management.EventTypeKey, int(management.TCP)) + } else { + log = log.Int(management.EventTypeKey, int(management.HTTP)) } if rule != "" { log = log.Str(LogFieldRule, rule) @@ -396,7 +420,7 @@ func (p *Proxy) logRequestError(err error, cfRay string, flowID string, rule, se if service != "" { log = log.Str(LogFieldOriginService, service) } - log.Msg("") + log.Send() } func getDestFromRule(rule *ingress.Rule, req *http.Request) (string, error) { diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 384298c3..942bbe30 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -1,6 +1,7 @@ package proxy import ( + "bufio" "bytes" "context" "flag" @@ -23,7 +24,6 @@ import ( "golang.org/x/sync/errgroup" "github.com/cloudflare/cloudflared/cfio" - "github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/hello" @@ -34,7 +34,7 @@ import ( ) var ( - testTags = []tunnelpogs.Tag{tunnelpogs.Tag{Name: "Name", Value: "value"}} + testTags = []tunnelpogs.Tag{{Name: "Name", Value: "value"}} noWarpRouting = ingress.WarpRoutingConfig{} testWarpRouting = ingress.WarpRoutingConfig{ Enabled: true, @@ -77,6 +77,10 @@ func (w *mockHTTPRespWriter) headers() http.Header { return w.Header() } +func (m *mockHTTPRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + panic("Hijack not implemented") +} + type mockWSRespWriter struct { *mockHTTPRespWriter writeNotification chan []byte @@ -110,6 +114,10 @@ func (w *mockWSRespWriter) Read(data []byte) (int, error) { return w.reader.Read(data) } +func (m *mockWSRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + panic("Hijack not implemented") +} + type mockSSERespWriter struct { *mockHTTPRespWriter writeNotification chan []byte @@ -150,8 +158,7 @@ func TestProxySingleOrigin(t *testing.T) { err := cliCtx.Set("hello-world", "true") require.NoError(t, err) - allowURLFromArgs := false - ingressRule, err := ingress.NewSingleOrigin(cliCtx, allowURLFromArgs) + ingressRule, err := ingress.ParseIngressFromConfigAndCLI(&config.Configuration{}, cliCtx, &log) require.NoError(t, err) require.NoError(t, ingressRule.StartOrigins(&log, ctx.Done())) @@ -170,7 +177,7 @@ func testProxyHTTP(proxy connection.OriginProxy) func(t *testing.T) { require.NoError(t, err) log := zerolog.Nop() - err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, &log), false) + err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, 0, &log), false) require.NoError(t, err) for _, tag := range testTags { assert.Equal(t, tag.Value, req.Header.Get(TagHeaderNamePrefix+tag.Name)) @@ -198,7 +205,7 @@ func testProxyWebsocket(proxy connection.OriginProxy) func(t *testing.T) { errGroup, ctx := errgroup.WithContext(ctx) errGroup.Go(func() error { log := zerolog.Nop() - err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, &log), true) + err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, 0, &log), true) require.NoError(t, err) require.Equal(t, http.StatusSwitchingProtocols, responseWriter.Code) @@ -260,8 +267,8 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) { go func() { defer wg.Done() log := zerolog.Nop() - err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, &log), false) - require.NoError(t, err) + err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, 0, &log), false) + require.Equal(t, err.Error(), "context canceled") require.Equal(t, http.StatusOK, responseWriter.Code) }() @@ -367,7 +374,7 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat req, err := http.NewRequest(http.MethodGet, test.url, nil) require.NoError(t, err) - err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, &log), false) + err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, 0, &log), false) require.NoError(t, err) assert.Equal(t, test.expectedStatus, responseWriter.Code) @@ -414,7 +421,7 @@ func TestProxyError(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil) assert.NoError(t, err) - assert.Error(t, proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, &log), false)) + assert.Error(t, proxy.ProxyHTTP(responseWriter, tracing.NewTracedHTTPRequest(req, 0, &log), false)) } type replayer struct { @@ -695,14 +702,13 @@ func TestConnections(t *testing.T) { err = proxy.ProxyTCP(ctx, rwa, &connection.TCPRequest{Dest: dest}) } else { log := zerolog.Nop() - err = proxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, &log), test.args.connectionType == connection.TypeWebsocket) + err = proxy.ProxyHTTP(respWriter, tracing.NewTracedHTTPRequest(req, 0, &log), test.args.connectionType == connection.TypeWebsocket) } cancel() assert.Equal(t, test.want.err, err != nil) assert.Equal(t, test.want.message, replayer.Bytes()) - respPrinter := respWriter.(responsePrinter) - assert.Equal(t, test.want.headers, respPrinter.headers()) + assert.Equal(t, test.want.headers, respWriter.Header()) replayer.rw.Reset() }) } @@ -795,10 +801,6 @@ func (p *pipedRequestBody) Close() error { return nil } -type responsePrinter interface { - headers() http.Header -} - type wsRespWriter struct { w io.Writer responseHeaders http.Header @@ -837,12 +839,20 @@ func (w *wsRespWriter) AddTrailer(trailerName, trailerValue string) { } // respHeaders is a test function to read respHeaders -func (w *wsRespWriter) headers() http.Header { +func (w *wsRespWriter) Header() http.Header { // Removing indeterminstic header because it cannot be asserted. w.responseHeaders.Del("Date") return w.responseHeaders } +func (w *wsRespWriter) WriteHeader(status int) { + // unused +} + +func (m *wsRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + panic("Hijack not implemented") +} + type mockTCPRespWriter struct { w io.Writer responseHeaders http.Header @@ -863,7 +873,7 @@ func (m *mockTCPRespWriter) Write(p []byte) (n int, err error) { return m.w.Write(p) } -func (w *mockTCPRespWriter) AddTrailer(trailerName, trailerValue string) { +func (m *mockTCPRespWriter) AddTrailer(trailerName, trailerValue string) { // do nothing } @@ -874,10 +884,18 @@ func (m *mockTCPRespWriter) WriteRespHeaders(status int, header http.Header) err } // respHeaders is a test function to read respHeaders -func (m *mockTCPRespWriter) headers() http.Header { +func (m *mockTCPRespWriter) Header() http.Header { return m.responseHeaders } +func (m *mockTCPRespWriter) WriteHeader(status int) { + // do nothing +} + +func (m *mockTCPRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + panic("Hijack not implemented") +} + func createSingleIngressConfig(t *testing.T, service string) ingress.Ingress { ingressConfig := &config.Configuration{ Ingress: []config.UnvalidatedIngressRule{ diff --git a/quic/metrics.go b/quic/metrics.go index 631cbbb6..900ac172 100644 --- a/quic/metrics.go +++ b/quic/metrics.go @@ -15,7 +15,7 @@ var ( clientConnLabels = []string{"conn_index"} clientMetrics = struct { totalConnections prometheus.Counter - closedConnections *prometheus.CounterVec + closedConnections prometheus.Counter sentPackets *prometheus.CounterVec sentBytes *prometheus.CounterVec receivePackets *prometheus.CounterVec @@ -30,9 +30,8 @@ var ( totalConnections: prometheus.NewCounter( totalConnectionsOpts(logging.PerspectiveClient), ), - closedConnections: prometheus.NewCounterVec( + closedConnections: prometheus.NewCounter( closedConnectionsOpts(logging.PerspectiveClient), - []string{"error"}, ), sentPackets: prometheus.NewCounterVec( sentPacketsOpts(logging.PerspectiveClient), @@ -284,7 +283,7 @@ func (cc *clientCollector) startedConnection() { } func (cc *clientCollector) closedConnection(err error) { - clientMetrics.closedConnections.WithLabelValues(err.Error()).Inc() + clientMetrics.closedConnections.Inc() } func (cc *clientCollector) sentPackets(size logging.ByteCount) { diff --git a/quic/safe_stream.go b/quic/safe_stream.go index c151ff2e..e19faa5c 100644 --- a/quic/safe_stream.go +++ b/quic/safe_stream.go @@ -52,3 +52,7 @@ func (s *SafeStreamCloser) CloseWrite() error { // We can still read from this stream. return s.stream.Close() } + +func (s *SafeStreamCloser) SetDeadline(deadline time.Time) error { + return s.stream.SetDeadline(deadline) +} diff --git a/stream/stream.go b/stream/stream.go new file mode 100644 index 00000000..8634b630 --- /dev/null +++ b/stream/stream.go @@ -0,0 +1,135 @@ +package stream + +import ( + "encoding/hex" + "fmt" + "io" + "runtime/debug" + "sync/atomic" + "time" + + "github.com/getsentry/raven-go" + "github.com/pkg/errors" + "github.com/rs/zerolog" + + "github.com/cloudflare/cloudflared/cfio" +) + +type bidirectionalStreamStatus struct { + doneChan chan struct{} + anyDone uint32 +} + +func newBiStreamStatus() *bidirectionalStreamStatus { + return &bidirectionalStreamStatus{ + doneChan: make(chan struct{}, 2), + anyDone: 0, + } +} + +func (s *bidirectionalStreamStatus) markUniStreamDone() { + atomic.StoreUint32(&s.anyDone, 1) + s.doneChan <- struct{}{} +} + +func (s *bidirectionalStreamStatus) waitAnyDone() { + <-s.doneChan +} +func (s *bidirectionalStreamStatus) isAnyDone() bool { + return atomic.LoadUint32(&s.anyDone) > 0 +} + +// Pipe copies copy data to & from provided io.ReadWriters. +func Pipe(tunnelConn, originConn io.ReadWriter, log *zerolog.Logger) { + status := newBiStreamStatus() + + go unidirectionalStream(tunnelConn, originConn, "origin->tunnel", status, log) + go unidirectionalStream(originConn, tunnelConn, "tunnel->origin", status, log) + + // If one side is done, we are done. + status.waitAnyDone() +} + +func unidirectionalStream(dst io.Writer, src io.Reader, dir string, status *bidirectionalStreamStatus, log *zerolog.Logger) { + defer func() { + // The bidirectional streaming spawns 2 goroutines to stream each direction. + // If any ends, the callstack returns, meaning the Tunnel request/stream (depending on http2 vs quic) will + // close. In such case, if the other direction did not stop (due to application level stopping, e.g., if a + // server/origin listens forever until closure), it may read/write from the underlying ReadWriter (backed by + // the Edge<->cloudflared transport) in an unexpected state. + // Because of this, we set this recover() logic. + if r := recover(); r != nil { + if status.isAnyDone() { + // We handle such unexpected errors only when we detect that one side of the streaming is done. + log.Debug().Msgf("Gracefully handled error %v in Streaming for %s, error %s", r, dir, debug.Stack()) + } else { + // Otherwise, this is unexpected, but we prevent the program from crashing anyway. + log.Warn().Msgf("Gracefully handled unexpected error %v in Streaming for %s, error %s", r, dir, debug.Stack()) + + tags := make(map[string]string) + tags["root"] = "websocket.stream" + tags["dir"] = dir + switch rval := r.(type) { + case error: + raven.CaptureError(rval, tags) + default: + rvalStr := fmt.Sprint(rval) + raven.CaptureMessage(rvalStr, tags) + } + } + } + }() + + _, err := copyData(dst, src, dir) + if err != nil { + log.Debug().Msgf("%s copy: %v", dir, err) + } + status.markUniStreamDone() +} + +// when set to true, enables logging of content copied to/from origin and tunnel +const debugCopy = false + +func copyData(dst io.Writer, src io.Reader, dir string) (written int64, err error) { + if debugCopy { + // copyBuffer is based on stdio Copy implementation but shows copied data + copyBuffer := func(dst io.Writer, src io.Reader, dir string) (written int64, err error) { + var buf []byte + size := 32 * 1024 + buf = make([]byte, size) + for { + t := time.Now() + nr, er := src.Read(buf) + if nr > 0 { + fmt.Println(dir, t.UnixNano(), "\n"+hex.Dump(buf[0:nr])) + nw, ew := dst.Write(buf[0:nr]) + if nw < 0 || nr < nw { + nw = 0 + if ew == nil { + ew = errors.New("invalid write") + } + } + written += int64(nw) + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err + } + return copyBuffer(dst, src, dir) + } else { + return cfio.Copy(dst, src) + } +} diff --git a/supervisor/supervisor.go b/supervisor/supervisor.go index 9f6a2fe2..d345fe7c 100644 --- a/supervisor/supervisor.go +++ b/supervisor/supervisor.go @@ -3,29 +3,22 @@ package supervisor import ( "context" "errors" - "fmt" "net" "strings" "time" - "github.com/google/uuid" "github.com/lucas-clemente/quic-go" "github.com/rs/zerolog" "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/edgediscovery" - "github.com/cloudflare/cloudflared/edgediscovery/allregions" - "github.com/cloudflare/cloudflared/h2mux" "github.com/cloudflare/cloudflared/orchestration" "github.com/cloudflare/cloudflared/retry" "github.com/cloudflare/cloudflared/signal" - tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" "github.com/cloudflare/cloudflared/tunnelstate" ) const ( - // SRV and TXT record resolution TTL - ResolveTTL = time.Hour // Waiting time before retrying a failed tunnel connection tunnelRetryDuration = time.Second * 10 // Interval between registering new tunnels @@ -41,11 +34,10 @@ const ( // Supervisor manages non-declarative tunnels. Establishes TCP connections with the edge, and // reconnects them if they disconnect. type Supervisor struct { - cloudflaredUUID uuid.UUID config *TunnelConfig orchestrator *orchestration.Orchestrator edgeIPs *edgediscovery.Edge - edgeTunnelServer *EdgeTunnelServer + edgeTunnelServer TunnelServer tunnelErrors chan tunnelError tunnelsConnecting map[int]chan struct{} tunnelsProtocolFallback map[int]*protocolFallback @@ -58,7 +50,6 @@ type Supervisor struct { logTransport *zerolog.Logger reconnectCredentialManager *reconnectCredentialManager - useReconnectToken bool reconnectCh chan ReconnectSignal gracefulShutdownC <-chan struct{} @@ -72,13 +63,9 @@ type tunnelError struct { } func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrator, reconnectCh chan ReconnectSignal, gracefulShutdownC <-chan struct{}) (*Supervisor, error) { - cloudflaredUUID, err := uuid.NewRandom() - if err != nil { - return nil, fmt.Errorf("failed to generate cloudflared instance ID: %w", err) - } - isStaticEdge := len(config.EdgeAddrs) > 0 + var err error var edgeIPs *edgediscovery.Edge if isStaticEdge { // static edge addresses edgeIPs, err = edgediscovery.StaticEdge(config.Log, config.EdgeAddrs) @@ -94,35 +81,23 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato tracker := tunnelstate.NewConnTracker(config.Log) log := NewConnAwareLogger(config.Log, tracker, config.Observer) - var edgeAddrHandler EdgeAddrHandler - if isStaticEdge { // static edge addresses - edgeAddrHandler = &IPAddrFallback{} - } else if config.EdgeIPVersion == allregions.IPv6Only || config.EdgeIPVersion == allregions.Auto { - edgeAddrHandler = &IPAddrFallback{} - } else { // IPv4Only - edgeAddrHandler = &DefaultAddrFallback{} - } + edgeAddrHandler := NewIPAddrFallback(config.MaxEdgeAddrRetries) + edgeBindAddr := config.EdgeBindAddr edgeTunnelServer := EdgeTunnelServer{ config: config, - cloudflaredUUID: cloudflaredUUID, orchestrator: orchestrator, credentialManager: reconnectCredentialManager, edgeAddrs: edgeIPs, edgeAddrHandler: edgeAddrHandler, + edgeBindAddr: edgeBindAddr, tracker: tracker, reconnectCh: reconnectCh, gracefulShutdownC: gracefulShutdownC, connAwareLogger: log, } - useReconnectToken := false - if config.ClassicTunnel != nil { - useReconnectToken = config.ClassicTunnel.UseReconnectToken - } - return &Supervisor{ - cloudflaredUUID: cloudflaredUUID, config: config, orchestrator: orchestrator, edgeIPs: edgeIPs, @@ -133,7 +108,6 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato log: log, logTransport: config.LogTransport, reconnectCredentialManager: reconnectCredentialManager, - useReconnectToken: useReconnectToken, reconnectCh: reconnectCh, gracefulShutdownC: gracefulShutdownC, }, nil @@ -167,20 +141,6 @@ func (s *Supervisor) Run( backoff := retry.BackoffHandler{MaxRetries: s.config.Retries, BaseTime: tunnelRetryDuration, RetryForever: true} var backoffTimer <-chan time.Time - refreshAuthBackoff := &retry.BackoffHandler{MaxRetries: refreshAuthMaxBackoff, BaseTime: refreshAuthRetryDuration, RetryForever: true} - var refreshAuthBackoffTimer <-chan time.Time - - if s.useReconnectToken { - if timer, err := s.reconnectCredentialManager.RefreshAuth(ctx, refreshAuthBackoff, s.authenticate); err == nil { - refreshAuthBackoffTimer = timer - } else { - s.log.Logger().Err(err). - Dur("refreshAuthRetryDuration", refreshAuthRetryDuration). - Msgf("supervisor: initial refreshAuth failed, retrying in %v", refreshAuthRetryDuration) - refreshAuthBackoffTimer = time.After(refreshAuthRetryDuration) - } - } - shuttingDown := false for { select { @@ -227,16 +187,6 @@ func (s *Supervisor) Run( } tunnelsActive += len(tunnelsWaiting) tunnelsWaiting = nil - // Time to call Authenticate - case <-refreshAuthBackoffTimer: - newTimer, err := s.reconnectCredentialManager.RefreshAuth(ctx, refreshAuthBackoff, s.authenticate) - if err != nil { - s.log.Logger().Err(err).Msg("supervisor: Authentication failed") - // Permanent failure. Leave the `select` without setting the - // channel to be non-null, so we'll never hit this case of the `select` again. - continue - } - refreshAuthBackoffTimer = newTimer // Tunnel successfully connected case <-s.nextConnectedSignal: if !s.waitForNextTunnel(s.nextConnectedIndex) && len(tunnelsWaiting) == 0 { @@ -285,7 +235,8 @@ func (s *Supervisor) initialize( for i := 1; i < s.config.HAConnections; i++ { s.tunnelsProtocolFallback[i] = &protocolFallback{ retry.BackoffHandler{MaxRetries: s.config.Retries, RetryForever: true}, - s.config.ProtocolSelector.Current(), + // Set the protocol we know the first tunnel connected with. + s.tunnelsProtocolFallback[0].protocol, false, } go s.startTunnel(ctx, i, s.newConnectedTunnelSignal(i)) @@ -335,6 +286,7 @@ func (s *Supervisor) startFirstTunnel( } case connection.DupConnRegisterTunnelError, *quic.IdleTimeoutError, + *quic.ApplicationError, edgediscovery.DialError, *connection.EdgeQuicDialError: // Try again for these types of errors @@ -384,44 +336,3 @@ func (s *Supervisor) waitForNextTunnel(index int) bool { func (s *Supervisor) unusedIPs() bool { return s.edgeIPs.AvailableAddrs() > s.config.HAConnections } - -func (s *Supervisor) authenticate(ctx context.Context, numPreviousAttempts int) (tunnelpogs.AuthOutcome, error) { - arbitraryEdgeIP, err := s.edgeIPs.GetAddrForRPC() - if err != nil { - return nil, err - } - - edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, s.config.EdgeTLSConfigs[connection.H2mux], arbitraryEdgeIP.TCP) - if err != nil { - return nil, err - } - defer edgeConn.Close() - - handler := h2mux.MuxedStreamFunc(func(*h2mux.MuxedStream) error { - // This callback is invoked by h2mux when the edge initiates a stream. - return nil // noop - }) - muxerConfig := s.config.MuxerConfig.H2MuxerConfig(handler, s.logTransport) - muxer, err := h2mux.Handshake(edgeConn, edgeConn, *muxerConfig, h2mux.ActiveStreams) - if err != nil { - return nil, err - } - go muxer.Serve(ctx) - defer func() { - // If we don't wait for the muxer shutdown here, edgeConn.Close() runs before the muxer connections are done, - // and the user sees log noise: "error writing data", "connection closed unexpectedly" - <-muxer.Shutdown() - }() - - stream, err := muxer.OpenRPCStream(ctx) - if err != nil { - return nil, err - } - rpcClient := connection.NewTunnelServerClient(ctx, stream, s.log.Logger()) - defer rpcClient.Close() - - const arbitraryConnectionID = uint8(0) - registrationOptions := s.config.registrationOptions(arbitraryConnectionID, edgeConn.LocalAddr().String(), s.cloudflaredUUID) - registrationOptions.NumPreviousAttempts = uint8(numPreviousAttempts) - return rpcClient.Authenticate(ctx, s.config.ClassicTunnel, registrationOptions) -} diff --git a/supervisor/tunnel.go b/supervisor/tunnel.go index e4a0a08a..1b2e132f 100644 --- a/supervisor/tunnel.go +++ b/supervisor/tunnel.go @@ -19,8 +19,10 @@ import ( "github.com/cloudflare/cloudflared/connection" "github.com/cloudflare/cloudflared/edgediscovery" "github.com/cloudflare/cloudflared/edgediscovery/allregions" + "github.com/cloudflare/cloudflared/features" "github.com/cloudflare/cloudflared/h2mux" "github.com/cloudflare/cloudflared/ingress" + "github.com/cloudflare/cloudflared/management" "github.com/cloudflare/cloudflared/orchestration" quicpogs "github.com/cloudflare/cloudflared/quic" "github.com/cloudflare/cloudflared/retry" @@ -31,35 +33,31 @@ import ( ) const ( - dialTimeout = 15 * time.Second - FeatureSerializedHeaders = "serialized_headers" - FeatureQuickReconnects = "quick_reconnects" - FeatureAllowRemoteConfig = "allow_remote_config" - FeatureDatagramV2 = "support_datagram_v2" - FeaturePostQuantum = "postquantum" - FeatureQUICSupportEOF = "support_quic_eof" + dialTimeout = 15 * time.Second ) type TunnelConfig struct { - GracePeriod time.Duration - ReplaceExisting bool - OSArch string - ClientID string - CloseConnOnce *sync.Once // Used to close connectedSignal no more than once - EdgeAddrs []string - Region string - EdgeIPVersion allregions.ConfigIPVersion - HAConnections int - IncidentLookup IncidentLookup - IsAutoupdated bool - LBPool string - Tags []tunnelpogs.Tag - Log *zerolog.Logger - LogTransport *zerolog.Logger - Observer *connection.Observer - ReportedVersion string - Retries uint - RunFromTerminal bool + GracePeriod time.Duration + ReplaceExisting bool + OSArch string + ClientID string + CloseConnOnce *sync.Once // Used to close connectedSignal no more than once + EdgeAddrs []string + Region string + EdgeIPVersion allregions.ConfigIPVersion + EdgeBindAddr net.IP + HAConnections int + IncidentLookup IncidentLookup + IsAutoupdated bool + LBPool string + Tags []tunnelpogs.Tag + Log *zerolog.Logger + LogTransport *zerolog.Logger + Observer *connection.Observer + ReportedVersion string + Retries uint + MaxEdgeAddrRetries uint8 + RunFromTerminal bool NeedPQ bool @@ -67,8 +65,6 @@ type TunnelConfig struct { PQKexIdx int NamedTunnel *connection.NamedTunnelProperties - ClassicTunnel *connection.ClassicTunnelProperties - MuxerConfig *connection.MuxerConfig ProtocolSelector connection.ProtocolSelector EdgeTLSConfigs map[connection.Protocol]*tls.Config PacketConfig *ingress.GlobalRouterConfig @@ -90,7 +86,7 @@ func (c *TunnelConfig) registrationOptions(connectionID uint8, OriginLocalIP str OriginLocalIP: OriginLocalIP, IsAutoupdated: c.IsAutoupdated, RunFromTerminal: c.RunFromTerminal, - CompressionQuality: uint64(c.MuxerConfig.CompressionSetting), + CompressionQuality: 0, UUID: uuid.String(), Features: c.SupportedFeatures(), } @@ -105,17 +101,17 @@ func (c *TunnelConfig) connectionOptions(originLocalAddr string, numPreviousAtte Client: c.NamedTunnel.Client, OriginLocalIP: originIP, ReplaceExisting: c.ReplaceExisting, - CompressionQuality: uint8(c.MuxerConfig.CompressionSetting), + CompressionQuality: 0, NumPreviousAttempts: numPreviousAttempts, } } func (c *TunnelConfig) SupportedFeatures() []string { - features := []string{FeatureSerializedHeaders} + supported := []string{features.FeatureSerializedHeaders} if c.NamedTunnel == nil { - features = append(features, FeatureQuickReconnects) + supported = append(supported, features.FeatureQuickReconnects) } - return features + return supported } func StartTunnelDaemon( @@ -133,6 +129,24 @@ func StartTunnelDaemon( return s.Run(ctx, connectedSignal) } +type ConnectivityError struct { + reachedMaxRetries bool +} + +func NewConnectivityError(hasReachedMaxRetries bool) *ConnectivityError { + return &ConnectivityError{ + reachedMaxRetries: hasReachedMaxRetries, + } +} + +func (e *ConnectivityError) Error() string { + return fmt.Sprintf("connectivity error - reached max retries: %t", e.HasReachedMaxRetries()) +} + +func (e *ConnectivityError) HasReachedMaxRetries() bool { + return e.reachedMaxRetries +} + // EdgeAddrHandler provides a mechanism switch between behaviors in ServeTunnel // for handling the errors when attempting to make edge connections. type EdgeAddrHandler interface { @@ -140,65 +154,56 @@ type EdgeAddrHandler interface { // the edge address should be replaced with a new one. Also, will return if the // error should be recognized as a connectivity error, or otherwise, a general // application error. - ShouldGetNewAddress(err error) (needsNewAddress bool, isConnectivityError bool) + ShouldGetNewAddress(connIndex uint8, err error) (needsNewAddress bool, connectivityError error) } -// DefaultAddrFallback will always return false for isConnectivityError since this -// handler is a way to provide the legacy behavior in the new edge discovery algorithm. -type DefaultAddrFallback struct { - edgeErrors int -} - -func (f DefaultAddrFallback) ShouldGetNewAddress(err error) (needsNewAddress bool, isConnectivityError bool) { - switch err.(type) { - case nil: // maintain current IP address - // DupConnRegisterTunnelError should indicate to get a new address immediately - case connection.DupConnRegisterTunnelError: - return true, false - // Try the next address if it was a quic.IdleTimeoutError - case *quic.IdleTimeoutError, - edgediscovery.DialError, - *connection.EdgeQuicDialError: - // Wait for two failures before falling back to a new address - f.edgeErrors++ - if f.edgeErrors >= 2 { - f.edgeErrors = 0 - return true, false - } - default: // maintain current IP address +func NewIPAddrFallback(maxRetries uint8) *ipAddrFallback { + return &ipAddrFallback{ + retriesByConnIndex: make(map[uint8]uint8), + maxRetries: maxRetries, } - return false, false } -// IPAddrFallback will have more conditions to fall back to a new address for certain +// ipAddrFallback will have more conditions to fall back to a new address for certain // edge connection errors. This means that this handler will return true for isConnectivityError // for more cases like duplicate connection register and edge quic dial errors. -type IPAddrFallback struct{} +type ipAddrFallback struct { + m sync.Mutex + retriesByConnIndex map[uint8]uint8 + maxRetries uint8 +} -func (f IPAddrFallback) ShouldGetNewAddress(err error) (needsNewAddress bool, isConnectivityError bool) { +func (f *ipAddrFallback) ShouldGetNewAddress(connIndex uint8, err error) (needsNewAddress bool, connectivityError error) { + f.m.Lock() + defer f.m.Unlock() switch err.(type) { case nil: // maintain current IP address // Try the next address if it was a quic.IdleTimeoutError // DupConnRegisterTunnelError needs to also receive a new ip address case connection.DupConnRegisterTunnelError, *quic.IdleTimeoutError: - return true, false + return true, nil // Network problems should be retried with new address immediately and report // as connectivity error case edgediscovery.DialError, *connection.EdgeQuicDialError: - return true, true + if f.retriesByConnIndex[connIndex] >= f.maxRetries { + f.retriesByConnIndex[connIndex] = 0 + return true, NewConnectivityError(true) + } + f.retriesByConnIndex[connIndex]++ + return true, NewConnectivityError(false) default: // maintain current IP address } - return false, false + return false, nil } type EdgeTunnelServer struct { config *TunnelConfig - cloudflaredUUID uuid.UUID orchestrator *orchestration.Orchestrator credentialManager *reconnectCredentialManager edgeAddrHandler EdgeAddrHandler edgeAddrs *edgediscovery.Edge + edgeBindAddr net.IP reconnectCh chan ReconnectSignal gracefulShutdownC <-chan struct{} tracker *tunnelstate.ConnTracker @@ -206,6 +211,10 @@ type EdgeTunnelServer struct { connAwareLogger *ConnAwareLogger } +type TunnelServer interface { + Serve(ctx context.Context, connIndex uint8, protocolFallback *protocolFallback, connectedSignal *signal.Signal) error +} + func (e *EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolFallback *protocolFallback, connectedSignal *signal.Signal) error { haConnections.Inc() defer haConnections.Dec() @@ -230,15 +239,17 @@ func (e *EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolF } logger := e.config.Log.With(). + Int(management.EventTypeKey, int(management.Cloudflared)). IPAddr(connection.LogFieldIPAddress, addr.UDP.IP). Uint8(connection.LogFieldConnIndex, connIndex). Logger() connLog := e.connAwareLogger.ReplaceLogger(&logger) + // Each connection to keep its own copy of protocol, because individual connections might fallback // to another protocol when a particular metal doesn't support new protocol // Each connection can also have it's own IP version because individual connections might fallback // to another IP version. - err, recoverable := e.serveTunnel( + err, shouldFallbackProtocol := e.serveTunnel( ctx, connLog, addr, @@ -248,34 +259,39 @@ func (e *EdgeTunnelServer) Serve(ctx context.Context, connIndex uint8, protocolF protocolFallback.protocol, ) - // If the connection is recoverable, we want to maintain the same IP - // but backoff a reconnect with some duration. - if recoverable { - duration, ok := protocolFallback.GetMaxBackoffDuration(ctx) - if !ok { - return err - } - - e.config.Observer.SendReconnect(connIndex) - connLog.Logger().Info().Msgf("Retrying connection in up to %s", duration) - } - // Check if the connection error was from an IP issue with the host or // establishing a connection to the edge and if so, rotate the IP address. - yes, hasConnectivityError := e.edgeAddrHandler.ShouldGetNewAddress(err) - if yes { - if _, err := e.edgeAddrs.GetDifferentAddr(int(connIndex), hasConnectivityError); err != nil { + shouldRotateEdgeIP, cErr := e.edgeAddrHandler.ShouldGetNewAddress(connIndex, err) + if shouldRotateEdgeIP { + // rotate IP, but forcing internal state to assign a new IP to connection index. + if _, err := e.edgeAddrs.GetDifferentAddr(int(connIndex), true); err != nil { return err } + + // In addition, if it is a connectivity error, and we have exhausted the configurable maximum edge IPs to rotate, + // then just fallback protocol on next iteration run. + connectivityErr, ok := cErr.(*ConnectivityError) + if ok { + shouldFallbackProtocol = connectivityErr.HasReachedMaxRetries() + } } + // set connection has re-connecting and log the next retrying backoff + duration, ok := protocolFallback.GetMaxBackoffDuration(ctx) + if !ok { + return err + } + e.config.Observer.SendReconnect(connIndex) + connLog.Logger().Info().Msgf("Retrying connection in up to %s", duration) + select { case <-ctx.Done(): return ctx.Err() case <-e.gracefulShutdownC: return nil case <-protocolFallback.BackoffTimer(): - if !recoverable { + // should we fallback protocol? If not, just return. Otherwise, set new protocol for next method call. + if !shouldFallbackProtocol { return err } @@ -422,7 +438,7 @@ func (e *EdgeTunnelServer) serveTunnel( } return err.Cause, !err.Permanent case *connection.EdgeQuicDialError: - return err, true + return err, false case ReconnectSignal: connLog.Logger().Info(). IPAddr(connection.LogFieldIPAddress, addr.UDP.IP). @@ -469,7 +485,7 @@ func (e *EdgeTunnelServer) serveConnection( ) switch protocol { - case connection.QUIC, connection.QUICWarp: + case connection.QUIC: connOptions := e.config.connectionOptions(addr.UDP.String(), uint8(backoff.Retries())) return e.serveQUIC(ctx, addr.UDP, @@ -478,8 +494,8 @@ func (e *EdgeTunnelServer) serveConnection( controlStream, connIndex) - case connection.HTTP2, connection.HTTP2Warp: - edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, e.config.EdgeTLSConfigs[protocol], addr.TCP) + case connection.HTTP2: + edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, e.config.EdgeTLSConfigs[protocol], addr.TCP, e.edgeBindAddr) if err != nil { connLog.ConnAwareLogger().Err(err).Msg("Unable to establish connection with Cloudflare edge") return err, true @@ -498,21 +514,7 @@ func (e *EdgeTunnelServer) serveConnection( } default: - edgeConn, err := edgediscovery.DialEdge(ctx, dialTimeout, e.config.EdgeTLSConfigs[protocol], addr.TCP) - if err != nil { - connLog.ConnAwareLogger().Err(err).Msg("Unable to establish connection with Cloudflare edge") - return err, true - } - - if err := e.serveH2mux( - ctx, - connLog, - edgeConn, - connIndex, - connectedFuse, - ); err != nil { - return err, false - } + return fmt.Errorf("invalid protocol selected: %s", protocol), false } return } @@ -525,59 +527,6 @@ func (r unrecoverableError) Error() string { return r.err.Error() } -func (e *EdgeTunnelServer) serveH2mux( - ctx context.Context, - connLog *ConnAwareLogger, - edgeConn net.Conn, - connIndex uint8, - connectedFuse *connectedFuse, -) error { - if e.config.NeedPQ { - return unrecoverableError{errors.New("H2Mux transport does not support post-quantum")} - } - connLog.Logger().Debug().Msgf("Connecting via h2mux") - // Returns error from parsing the origin URL or handshake errors - handler, err, recoverable := connection.NewH2muxConnection( - e.orchestrator, - e.config.GracePeriod, - e.config.MuxerConfig, - edgeConn, - connIndex, - e.config.Observer, - e.gracefulShutdownC, - e.config.Log, - ) - if err != nil { - if !recoverable { - return unrecoverableError{err} - } - return err - } - - errGroup, serveCtx := errgroup.WithContext(ctx) - - errGroup.Go(func() error { - if e.config.NamedTunnel != nil { - connOptions := e.config.connectionOptions(edgeConn.LocalAddr().String(), uint8(connectedFuse.backoff.Retries())) - return handler.ServeNamedTunnel(serveCtx, e.config.NamedTunnel, connOptions, connectedFuse) - } - registrationOptions := e.config.registrationOptions(connIndex, edgeConn.LocalAddr().String(), e.cloudflaredUUID) - return handler.ServeClassicTunnel(serveCtx, e.config.ClassicTunnel, e.credentialManager, registrationOptions, connectedFuse) - }) - - errGroup.Go(func() error { - err := listenReconnect(serveCtx, e.reconnectCh, e.gracefulShutdownC) - if err != nil { - // forcefully break the connection (this is only used for testing) - // errgroup will return context canceled for the handler.ServeClassicTunnel - connLog.Logger().Debug().Msg("Forcefully breaking h2mux connection") - } - return err - }) - - return errGroup.Wait() -} - func (e *EdgeTunnelServer) serveHTTP2( ctx context.Context, connLog *ConnAwareLogger, @@ -658,6 +607,7 @@ func (e *EdgeTunnelServer) serveQUIC( quicConn, err := connection.NewQUICConnection( quicConfig, edgeAddr, + e.edgeBindAddr, connIndex, tlsConfig, e.orchestrator, diff --git a/supervisor/tunnel_test.go b/supervisor/tunnel_test.go index 3f9ae62c..9a0e9653 100644 --- a/supervisor/tunnel_test.go +++ b/supervisor/tunnel_test.go @@ -18,7 +18,7 @@ type dynamicMockFetcher struct { err error } -func (dmf *dynamicMockFetcher) fetch() connection.PercentageFetcher { +func (dmf *dynamicMockFetcher) fetch() edgediscovery.PercentageFetcher { return func() (edgediscovery.ProtocolPercents, error) { return dmf.protocolPercents, dmf.err } @@ -32,24 +32,22 @@ func TestWaitForBackoffFallback(t *testing.T) { } log := zerolog.Nop() resolveTTL := time.Duration(0) - namedTunnel := &connection.NamedTunnelProperties{} mockFetcher := dynamicMockFetcher{ - protocolPercents: edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}}, + protocolPercents: edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}, } - warpRoutingEnabled := false protocolSelector, err := connection.NewProtocolSelector( "auto", - warpRoutingEnabled, - namedTunnel, + "", + false, + false, mockFetcher.fetch(), resolveTTL, &log, - false, ) assert.NoError(t, err) initProtocol := protocolSelector.Current() - assert.Equal(t, connection.HTTP2, initProtocol) + assert.Equal(t, connection.QUIC, initProtocol) protoFallback := &protocolFallback{ backoff, @@ -100,12 +98,12 @@ func TestWaitForBackoffFallback(t *testing.T) { // The reason why there's no fallback available is because we pick a specific protocol instead of letting it be auto. protocolSelector, err = connection.NewProtocolSelector( "quic", - warpRoutingEnabled, - namedTunnel, + "", + false, + false, mockFetcher.fetch(), resolveTTL, &log, - false, ) assert.NoError(t, err) protoFallback = &protocolFallback{backoff, protocolSelector.Current(), false} diff --git a/tlsconfig/certreloader.go b/tlsconfig/certreloader.go index f2edce78..41e37c69 100644 --- a/tlsconfig/certreloader.go +++ b/tlsconfig/certreloader.go @@ -8,7 +8,7 @@ import ( "runtime" "sync" - "github.com/getsentry/raven-go" + "github.com/getsentry/sentry-go" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/urfave/cli/v2" @@ -65,7 +65,7 @@ func (cr *CertReloader) LoadCert() error { // Keep the old certificate if there's a problem reading the new one. if err != nil { - raven.CaptureError(fmt.Errorf("Error parsing X509 key pair: %v", err), nil) + sentry.CaptureException(fmt.Errorf("Error parsing X509 key pair: %v", err)) return err } cr.certificate = &cert diff --git a/token/token.go b/token/token.go index 329ace79..68ac70ae 100644 --- a/token/token.go +++ b/token/token.go @@ -214,19 +214,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog. return appToken, nil } } - return getTokensFromEdge(appURL, appTokenPath, orgTokenPath, useHostOnly, log) + return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, log) } // getTokensFromEdge will attempt to use the transfer service to retrieve an app and org token, save them to disk, // and return the app token. -func getTokensFromEdge(appURL *url.URL, appTokenPath, orgTokenPath string, useHostOnly bool, log *zerolog.Logger) (string, error) { +func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, log *zerolog.Logger) (string, error) { // If no org token exists or if it couldn't be exchanged for an app token, then run the transfer service flow. // this weird parameter is the resource name (token) and the key/value // we want to send to the transfer service. the key is token and the value // is blank (basically just the id generated in the transfer service) - resourceData, err := RunTransfer(appURL, keyName, keyName, "", true, useHostOnly, log) + resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, log) if err != nil { return "", errors.Wrap(err, "failed to run transfer service") } diff --git a/token/transfer.go b/token/transfer.go index cc40cb69..9b035537 100644 --- a/token/transfer.go +++ b/token/transfer.go @@ -25,12 +25,12 @@ const ( // The "dance" we refer to is building a HTTP request, opening that in a browser waiting for // the user to complete an action, while it long polls in the background waiting for an // action to be completed to download the resource. -func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) { +func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) { encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem") if err != nil { return nil, err } - requestURL, err := buildRequestURL(transferURL, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly) + requestURL, err := buildRequestURL(transferURL, appAUD, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly) if err != nil { return nil, err } @@ -76,9 +76,10 @@ func RunTransfer(transferURL *url.URL, resourceName, key, value string, shouldEn // BuildRequestURL creates a request suitable for a resource transfer. // it will return a constructed url based off the base url and query key/value provided. // cli will build a url for cli transfer request. -func buildRequestURL(baseURL *url.URL, key, value string, cli, useHostOnly bool) (string, error) { +func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, useHostOnly bool) (string, error) { q := baseURL.Query() q.Set(key, value) + q.Set("aud", appAUD) baseURL.RawQuery = q.Encode() if useHostOnly { baseURL.Path = "" diff --git a/tracing/tracing.go b/tracing/tracing.go index 57744120..f7bc59fc 100644 --- a/tracing/tracing.go +++ b/tracing/tracing.go @@ -73,15 +73,16 @@ func Init(version string) { type TracedHTTPRequest struct { *http.Request *cfdTracer + ConnIndex uint8 // The connection index used to proxy the request } // NewTracedHTTPRequest creates a new tracer for the current HTTP request context. -func NewTracedHTTPRequest(req *http.Request, log *zerolog.Logger) *TracedHTTPRequest { +func NewTracedHTTPRequest(req *http.Request, connIndex uint8, log *zerolog.Logger) *TracedHTTPRequest { ctx, exists := extractTrace(req) if !exists { - return &TracedHTTPRequest{req, &cfdTracer{trace.NewNoopTracerProvider(), &NoopOtlpClient{}, log}} + return &TracedHTTPRequest{req, &cfdTracer{trace.NewNoopTracerProvider(), &NoopOtlpClient{}, log}, connIndex} } - return &TracedHTTPRequest{req.WithContext(ctx), newCfdTracer(ctx, log)} + return &TracedHTTPRequest{req.WithContext(ctx), newCfdTracer(ctx, log), connIndex} } func (tr *TracedHTTPRequest) ToTracedContext() *TracedContext { diff --git a/tracing/tracing_test.go b/tracing/tracing_test.go index 5750056e..5c478ed6 100644 --- a/tracing/tracing_test.go +++ b/tracing/tracing_test.go @@ -17,7 +17,7 @@ func TestNewCfTracer(t *testing.T) { log := zerolog.Nop() req := httptest.NewRequest("GET", "http://localhost", nil) req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1") - tr := NewTracedHTTPRequest(req, &log) + tr := NewTracedHTTPRequest(req, 0, &log) assert.NotNil(t, tr) assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider) assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter) @@ -28,7 +28,7 @@ func TestNewCfTracerMultiple(t *testing.T) { req := httptest.NewRequest("GET", "http://localhost", nil) req.Header.Add(TracerContextName, "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1") req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1") - tr := NewTracedHTTPRequest(req, &log) + tr := NewTracedHTTPRequest(req, 0, &log) assert.NotNil(t, tr) assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider) assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter) @@ -38,7 +38,7 @@ func TestNewCfTracerNilHeader(t *testing.T) { log := zerolog.Nop() req := httptest.NewRequest("GET", "http://localhost", nil) req.Header[http.CanonicalHeaderKey(TracerContextName)] = nil - tr := NewTracedHTTPRequest(req, &log) + tr := NewTracedHTTPRequest(req, 0, &log) assert.NotNil(t, tr) assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider) assert.IsType(t, &NoopOtlpClient{}, tr.exporter) @@ -49,7 +49,7 @@ func TestNewCfTracerInvalidHeaders(t *testing.T) { req := httptest.NewRequest("GET", "http://localhost", nil) for _, test := range [][]string{nil, {""}} { req.Header[http.CanonicalHeaderKey(TracerContextName)] = test - tr := NewTracedHTTPRequest(req, &log) + tr := NewTracedHTTPRequest(req, 0, &log) assert.NotNil(t, tr) assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider) assert.IsType(t, &NoopOtlpClient{}, tr.exporter) @@ -60,7 +60,7 @@ func TestAddingSpansWithNilMap(t *testing.T) { log := zerolog.Nop() req := httptest.NewRequest("GET", "http://localhost", nil) req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1") - tr := NewTracedHTTPRequest(req, &log) + tr := NewTracedHTTPRequest(req, 0, &log) exporter := tr.exporter.(*InMemoryOtlpClient) diff --git a/tunneldns/metrics.go b/tunneldns/metrics.go index 49357a1e..6db06cc5 100644 --- a/tunneldns/metrics.go +++ b/tunneldns/metrics.go @@ -35,7 +35,7 @@ func (p MetricsPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn // Update built-in metrics server := metrics.WithServer(ctx) - vars.Report(server, state, ".", rcode.ToString(rw.Rcode), pluginName, rw.Len, rw.Start) + vars.Report(server, state, ".", "", rcode.ToString(rw.Rcode), pluginName, rw.Len, rw.Start) return status, err } diff --git a/tunnelrpc/logtransport.go b/tunnelrpc/logtransport.go index 265bcc02..ab404786 100644 --- a/tunnelrpc/logtransport.go +++ b/tunnelrpc/logtransport.go @@ -24,17 +24,17 @@ func NewTransportLogger(log *zerolog.Logger, t rpc.Transport) rpc.Transport { } func (t *transport) SendMessage(ctx context.Context, msg rpccapnp.Message) error { - t.log.Debug().Msgf("rpcconnect: tx %s", formatMsg(msg)) + t.log.Trace().Msgf("rpc tx: %s", formatMsg(msg)) return t.Transport.SendMessage(ctx, msg) } func (t *transport) RecvMessage(ctx context.Context) (rpccapnp.Message, error) { msg, err := t.Transport.RecvMessage(ctx) if err != nil { - t.log.Debug().Msgf("rpcconnect: rx error: %s", err) + t.log.Debug().Msgf("rpc rx error: %s", err) return msg, err } - t.log.Debug().Msgf("rpcconnect: rx %s", formatMsg(msg)) + t.log.Trace().Msgf("rpc rx: %s", formatMsg(msg)) return msg, nil } diff --git a/vendor/github.com/BurntSushi/toml/.gitignore b/vendor/github.com/BurntSushi/toml/.gitignore index 0cd38003..fe79e3ad 100644 --- a/vendor/github.com/BurntSushi/toml/.gitignore +++ b/vendor/github.com/BurntSushi/toml/.gitignore @@ -1,5 +1,2 @@ -TAGS -tags -.*.swp -tomlcheck/tomlcheck -toml.test +/toml.test +/toml-test diff --git a/vendor/github.com/BurntSushi/toml/.travis.yml b/vendor/github.com/BurntSushi/toml/.travis.yml deleted file mode 100644 index 8b8afc4f..00000000 --- a/vendor/github.com/BurntSushi/toml/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -go: - - 1.1 - - 1.2 - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - tip -install: - - go install ./... - - go get github.com/BurntSushi/toml-test -script: - - export PATH="$PATH:$HOME/gopath/bin" - - make test diff --git a/vendor/github.com/BurntSushi/toml/COMPATIBLE b/vendor/github.com/BurntSushi/toml/COMPATIBLE deleted file mode 100644 index 6efcfd0c..00000000 --- a/vendor/github.com/BurntSushi/toml/COMPATIBLE +++ /dev/null @@ -1,3 +0,0 @@ -Compatible with TOML version -[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md) - diff --git a/vendor/github.com/BurntSushi/toml/Makefile b/vendor/github.com/BurntSushi/toml/Makefile deleted file mode 100644 index 3600848d..00000000 --- a/vendor/github.com/BurntSushi/toml/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -install: - go install ./... - -test: install - go test -v - toml-test toml-test-decoder - toml-test -encoder toml-test-encoder - -fmt: - gofmt -w *.go */*.go - colcheck *.go */*.go - -tags: - find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS - -push: - git push origin master - git push github master - diff --git a/vendor/github.com/BurntSushi/toml/README.md b/vendor/github.com/BurntSushi/toml/README.md index 7c1b37ec..3651cfa9 100644 --- a/vendor/github.com/BurntSushi/toml/README.md +++ b/vendor/github.com/BurntSushi/toml/README.md @@ -1,46 +1,26 @@ -## TOML parser and encoder for Go with reflection - TOML stands for Tom's Obvious, Minimal Language. This Go package provides a -reflection interface similar to Go's standard library `json` and `xml` -packages. This package also supports the `encoding.TextUnmarshaler` and -`encoding.TextMarshaler` interfaces so that you can define custom data -representations. (There is an example of this below.) +reflection interface similar to Go's standard library `json` and `xml` packages. -Spec: https://github.com/toml-lang/toml +Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). -Compatible with TOML version -[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) +Documentation: https://godocs.io/github.com/BurntSushi/toml -Documentation: https://godoc.org/github.com/BurntSushi/toml +See the [releases page](https://github.com/BurntSushi/toml/releases) for a +changelog; this information is also in the git tag annotations (e.g. `git show +v0.4.0`). -Installation: +This library requires Go 1.13 or newer; add it to your go.mod with: -```bash -go get github.com/BurntSushi/toml -``` + % go get github.com/BurntSushi/toml@latest -Try the toml validator: +It also comes with a TOML validator CLI tool: -```bash -go get github.com/BurntSushi/toml/cmd/tomlv -tomlv some-toml-file.toml -``` - -[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml) - -### Testing - -This package passes all tests in -[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder -and the encoder. + % go install github.com/BurntSushi/toml/cmd/tomlv@latest + % tomlv some-toml-file.toml ### Examples - -This package works similarly to how the Go standard library handles `XML` -and `JSON`. Namely, data is loaded into Go values via reflection. - -For the simplest example, consider some TOML file as just a list of keys -and values: +For the simplest example, consider some TOML file as just a list of keys and +values: ```toml Age = 25 @@ -50,29 +30,23 @@ Perfection = [ 6, 28, 496, 8128 ] DOB = 1987-07-05T05:45:00Z ``` -Which could be defined in Go as: +Which can be decoded with: ```go type Config struct { - Age int - Cats []string - Pi float64 - Perfection []int - DOB time.Time // requires `import time` + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time } -``` -And then decoded with: - -```go var conf Config -if _, err := toml.Decode(tomlData, &conf); err != nil { - // handle error -} +_, err := toml.Decode(tomlData, &conf) ``` -You can also use struct tags if your struct field name doesn't map to a TOML -key value directly: +You can also use struct tags if your struct field name doesn't map to a TOML key +value directly: ```toml some_key_NAME = "wat" @@ -80,139 +54,67 @@ some_key_NAME = "wat" ```go type TOML struct { - ObscureKey string `toml:"some_key_NAME"` + ObscureKey string `toml:"some_key_NAME"` } ``` -### Using the `encoding.TextUnmarshaler` interface +Beware that like other decoders **only exported fields** are considered when +encoding and decoding; private fields are silently ignored. -Here's an example that automatically parses duration strings into -`time.Duration` values: +### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces +Here's an example that automatically parses values in a `mail.Address`: ```toml -[[song]] -name = "Thunder Road" -duration = "4m49s" - -[[song]] -name = "Stairway to Heaven" -duration = "8m03s" -``` - -Which can be decoded with: - -```go -type song struct { - Name string - Duration duration -} -type songs struct { - Song []song -} -var favorites songs -if _, err := toml.Decode(blob, &favorites); err != nil { - log.Fatal(err) -} - -for _, s := range favorites.Song { - fmt.Printf("%s (%s)\n", s.Name, s.Duration) -} -``` - -And you'll also need a `duration` type that satisfies the -`encoding.TextUnmarshaler` interface: - -```go -type duration struct { - time.Duration -} - -func (d *duration) UnmarshalText(text []byte) error { - var err error - d.Duration, err = time.ParseDuration(string(text)) - return err -} -``` - -### More complex usage - -Here's an example of how to load the example from the official spec page: - -```toml -# This is a TOML document. Boom. - -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -organization = "GitHub" -bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." -dob = 1979-05-27T07:32:00Z # First class dates? Why not? - -[database] -server = "192.168.1.1" -ports = [ 8001, 8001, 8002 ] -connection_max = 5000 -enabled = true - -[servers] - - # You can indent as you please. Tabs or spaces. TOML don't care. - [servers.alpha] - ip = "10.0.0.1" - dc = "eqdc10" - - [servers.beta] - ip = "10.0.0.2" - dc = "eqdc10" - -[clients] -data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it - -# Line breaks are OK when inside arrays -hosts = [ - "alpha", - "omega" +contacts = [ + "Donald Duck ", + "Scrooge McDuck ", ] ``` -And the corresponding Go types are: +Can be decoded with: ```go -type tomlConfig struct { - Title string - Owner ownerInfo - DB database `toml:"database"` - Servers map[string]server - Clients clients +// Create address type which satisfies the encoding.TextUnmarshaler interface. +type address struct { + *mail.Address } -type ownerInfo struct { - Name string - Org string `toml:"organization"` - Bio string - DOB time.Time +func (a *address) UnmarshalText(text []byte) error { + var err error + a.Address, err = mail.ParseAddress(string(text)) + return err } -type database struct { - Server string - Ports []int - ConnMax int `toml:"connection_max"` - Enabled bool -} +// Decode it. +func decode() { + blob := ` + contacts = [ + "Donald Duck ", + "Scrooge McDuck ", + ] + ` -type server struct { - IP string - DC string -} + var contacts struct { + Contacts []address + } -type clients struct { - Data [][]interface{} - Hosts []string + _, err := toml.Decode(blob, &contacts) + if err != nil { + log.Fatal(err) + } + + for _, c := range contacts.Contacts { + fmt.Printf("%#v\n", c.Address) + } + + // Output: + // &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"} + // &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"} } ``` -Note that a case insensitive match will be tried if an exact match can't be -found. +To target TOML specifically you can implement `UnmarshalTOML` TOML interface in +a similar way. -A working example of the above can be found in `_examples/example.{go,toml}`. +### More complex usage +See the [`_example/`](/_example) directory for a more complex example. diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go index b0fd51d5..09523315 100644 --- a/vendor/github.com/BurntSushi/toml/decode.go +++ b/vendor/github.com/BurntSushi/toml/decode.go @@ -1,54 +1,171 @@ package toml import ( + "bytes" + "encoding" + "encoding/json" "fmt" "io" "io/ioutil" "math" + "os" "reflect" + "strconv" "strings" "time" ) -func e(format string, args ...interface{}) error { - return fmt.Errorf("toml: "+format, args...) -} - // Unmarshaler is the interface implemented by objects that can unmarshal a // TOML description of themselves. type Unmarshaler interface { UnmarshalTOML(interface{}) error } -// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. -func Unmarshal(p []byte, v interface{}) error { - _, err := Decode(string(p), v) +// Unmarshal decodes the contents of `data` in TOML format into a pointer `v`. +func Unmarshal(data []byte, v interface{}) error { + _, err := NewDecoder(bytes.NewReader(data)).Decode(v) return err } +// Decode the TOML data in to the pointer v. +// +// See the documentation on Decoder for a description of the decoding process. +func Decode(data string, v interface{}) (MetaData, error) { + return NewDecoder(strings.NewReader(data)).Decode(v) +} + +// DecodeFile is just like Decode, except it will automatically read the +// contents of the file at path and decode it for you. +func DecodeFile(path string, v interface{}) (MetaData, error) { + fp, err := os.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} + // Primitive is a TOML value that hasn't been decoded into a Go value. -// When using the various `Decode*` functions, the type `Primitive` may -// be given to any value, and its decoding will be delayed. // -// A `Primitive` value can be decoded using the `PrimitiveDecode` function. +// This type can be used for any value, which will cause decoding to be delayed. +// You can use the PrimitiveDecode() function to "manually" decode these values. // -// The underlying representation of a `Primitive` value is subject to change. -// Do not rely on it. +// NOTE: The underlying representation of a `Primitive` value is subject to +// change. Do not rely on it. // -// N.B. Primitive values are still parsed, so using them will only avoid -// the overhead of reflection. They can be useful when you don't know the -// exact type of TOML data until run time. +// NOTE: Primitive values are still parsed, so using them will only avoid the +// overhead of reflection. They can be useful when you don't know the exact type +// of TOML data until runtime. type Primitive struct { undecoded interface{} context Key } -// DEPRECATED! +// The significand precision for float32 and float64 is 24 and 53 bits; this is +// the range a natural number can be stored in a float without loss of data. +const ( + maxSafeFloat32Int = 16777215 // 2^24-1 + maxSafeFloat64Int = int64(9007199254740991) // 2^53-1 +) + +// Decoder decodes TOML data. // -// Use MetaData.PrimitiveDecode instead. -func PrimitiveDecode(primValue Primitive, v interface{}) error { - md := MetaData{decoded: make(map[string]bool)} - return md.unify(primValue.undecoded, rvalue(v)) +// TOML tables correspond to Go structs or maps (dealer's choice – they can be +// used interchangeably). +// +// TOML table arrays correspond to either a slice of structs or a slice of maps. +// +// TOML datetimes correspond to Go time.Time values. Local datetimes are parsed +// in the local timezone. +// +// time.Duration types are treated as nanoseconds if the TOML value is an +// integer, or they're parsed with time.ParseDuration() if they're strings. +// +// All other TOML types (float, string, int, bool and array) correspond to the +// obvious Go types. +// +// An exception to the above rules is if a type implements the TextUnmarshaler +// interface, in which case any primitive TOML value (floats, strings, integers, +// booleans, datetimes) will be converted to a []byte and given to the value's +// UnmarshalText method. See the Unmarshaler example for a demonstration with +// email addresses. +// +// Key mapping +// +// TOML keys can map to either keys in a Go map or field names in a Go struct. +// The special `toml` struct tag can be used to map TOML keys to struct fields +// that don't match the key name exactly (see the example). A case insensitive +// match to struct names will be tried if an exact match can't be found. +// +// The mapping between TOML values and Go values is loose. That is, there may +// exist TOML values that cannot be placed into your representation, and there +// may be parts of your representation that do not correspond to TOML values. +// This loose mapping can be made stricter by using the IsDefined and/or +// Undecoded methods on the MetaData returned. +// +// This decoder does not handle cyclic types. Decode will not terminate if a +// cyclic type is passed. +type Decoder struct { + r io.Reader +} + +// NewDecoder creates a new Decoder. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +var ( + unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem() +) + +// Decode TOML data in to the pointer `v`. +func (dec *Decoder) Decode(v interface{}) (MetaData, error) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + s := "%q" + if reflect.TypeOf(v) == nil { + s = "%v" + } + + return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v)) + } + if rv.IsNil() { + return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v)) + } + + // Check if this is a supported type: struct, map, interface{}, or something + // that implements UnmarshalTOML or UnmarshalText. + rv = indirect(rv) + rt := rv.Type() + if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map && + !(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) && + !rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) { + return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt) + } + + // TODO: parser should read from io.Reader? Or at the very least, make it + // read from []byte rather than string + data, err := ioutil.ReadAll(dec.r) + if err != nil { + return MetaData{}, err + } + + p, err := parse(string(data)) + if err != nil { + return MetaData{}, err + } + + md := MetaData{ + mapping: p.mapping, + keyInfo: p.keyInfo, + keys: p.ordered, + decoded: make(map[string]struct{}, len(p.ordered)), + context: nil, + data: data, + } + return md, md.unify(p.mapping, rv) } // PrimitiveDecode is just like the other `Decode*` functions, except it @@ -68,90 +185,15 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { return md.unify(primValue.undecoded, rvalue(v)) } -// Decode will decode the contents of `data` in TOML format into a pointer -// `v`. -// -// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be -// used interchangeably.) -// -// TOML arrays of tables correspond to either a slice of structs or a slice -// of maps. -// -// TOML datetimes correspond to Go `time.Time` values. -// -// All other TOML types (float, string, int, bool and array) correspond -// to the obvious Go types. -// -// An exception to the above rules is if a type implements the -// encoding.TextUnmarshaler interface. In this case, any primitive TOML value -// (floats, strings, integers, booleans and datetimes) will be converted to -// a byte string and given to the value's UnmarshalText method. See the -// Unmarshaler example for a demonstration with time duration strings. -// -// Key mapping -// -// TOML keys can map to either keys in a Go map or field names in a Go -// struct. The special `toml` struct tag may be used to map TOML keys to -// struct fields that don't match the key name exactly. (See the example.) -// A case insensitive match to struct names will be tried if an exact match -// can't be found. -// -// The mapping between TOML values and Go values is loose. That is, there -// may exist TOML values that cannot be placed into your representation, and -// there may be parts of your representation that do not correspond to -// TOML values. This loose mapping can be made stricter by using the IsDefined -// and/or Undecoded methods on the MetaData returned. -// -// This decoder will not handle cyclic types. If a cyclic type is passed, -// `Decode` will not terminate. -func Decode(data string, v interface{}) (MetaData, error) { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr { - return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) - } - if rv.IsNil() { - return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) - } - p, err := parse(data) - if err != nil { - return MetaData{}, err - } - md := MetaData{ - p.mapping, p.types, p.ordered, - make(map[string]bool, len(p.ordered)), nil, - } - return md, md.unify(p.mapping, indirect(rv)) -} - -// DecodeFile is just like Decode, except it will automatically read the -// contents of the file at `fpath` and decode it for you. -func DecodeFile(fpath string, v interface{}) (MetaData, error) { - bs, err := ioutil.ReadFile(fpath) - if err != nil { - return MetaData{}, err - } - return Decode(string(bs), v) -} - -// DecodeReader is just like Decode, except it will consume all bytes -// from the reader and decode it for you. -func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { - bs, err := ioutil.ReadAll(r) - if err != nil { - return MetaData{}, err - } - return Decode(string(bs), v) -} - // unify performs a sort of type unification based on the structure of `rv`, // which is the client representation. // // Any type mismatch produces an error. Finding a type that we don't know // how to handle produces an unsupported type error. func (md *MetaData) unify(data interface{}, rv reflect.Value) error { - // Special case. Look for a `Primitive` value. - if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { + // TODO: #76 would make this superfluous after implemented. + if rv.Type() == primitiveType { // Save the undecoded data and the key context into the primitive // value. context := make(Key, len(md.context)) @@ -163,36 +205,24 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error { return nil } - // Special case. Unmarshaler Interface support. - if rv.CanAddr() { - if v, ok := rv.Addr().Interface().(Unmarshaler); ok { - return v.UnmarshalTOML(data) - } + rvi := rv.Interface() + if v, ok := rvi.(Unmarshaler); ok { + return v.UnmarshalTOML(data) } - - // Special case. Handle time.Time values specifically. - // TODO: Remove this code when we decide to drop support for Go 1.1. - // This isn't necessary in Go 1.2 because time.Time satisfies the encoding - // interfaces. - if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { - return md.unifyDatetime(data, rv) - } - - // Special case. Look for a value satisfying the TextUnmarshaler interface. - if v, ok := rv.Interface().(TextUnmarshaler); ok { + if v, ok := rvi.(encoding.TextUnmarshaler); ok { return md.unifyText(data, v) } - // BUG(burntsushi) + + // TODO: // The behavior here is incorrect whenever a Go type satisfies the - // encoding.TextUnmarshaler interface but also corresponds to a TOML - // hash or array. In particular, the unmarshaler should only be applied - // to primitive TOML values. But at this point, it will be applied to - // all kinds of values and produce an incorrect error whenever those values - // are hashes or arrays (including arrays of tables). + // encoding.TextUnmarshaler interface but also corresponds to a TOML hash or + // array. In particular, the unmarshaler should only be applied to primitive + // TOML values. But at this point, it will be applied to all kinds of values + // and produce an incorrect error whenever those values are hashes or arrays + // (including arrays of tables). k := rv.Kind() - // laziness if k >= reflect.Int && k <= reflect.Uint64 { return md.unifyInt(data, rv) } @@ -218,17 +248,14 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error { case reflect.Bool: return md.unifyBool(data, rv) case reflect.Interface: - // we only support empty interfaces. - if rv.NumMethod() > 0 { - return e("unsupported type %s", rv.Type()) + if rv.NumMethod() > 0 { // Only support empty interfaces are supported. + return md.e("unsupported type %s", rv.Type()) } return md.unifyAnything(data, rv) - case reflect.Float32: - fallthrough - case reflect.Float64: + case reflect.Float32, reflect.Float64: return md.unifyFloat64(data, rv) } - return e("unsupported type %s", rv.Kind()) + return md.e("unsupported type %s", rv.Kind()) } func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { @@ -237,7 +264,7 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { if mapping == nil { return nil } - return e("type mismatch for %s: expected table but found %T", + return md.e("type mismatch for %s: expected table but found %T", rv.Type().String(), mapping) } @@ -259,17 +286,18 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { for _, i := range f.index { subv = indirect(subv.Field(i)) } + if isUnifiable(subv) { - md.decoded[md.context.add(key).String()] = true + md.decoded[md.context.add(key).String()] = struct{}{} md.context = append(md.context, key) - if err := md.unify(datum, subv); err != nil { + + err := md.unify(datum, subv) + if err != nil { return err } md.context = md.context[0 : len(md.context)-1] } else if f.name != "" { - // Bad user! No soup for you! - return e("cannot write unexported field %s.%s", - rv.Type().String(), f.name) + return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name) } } } @@ -277,28 +305,43 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { } func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { + keyType := rv.Type().Key().Kind() + if keyType != reflect.String && keyType != reflect.Interface { + return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)", + keyType, rv.Type()) + } + tmap, ok := mapping.(map[string]interface{}) if !ok { if tmap == nil { return nil } - return badtype("map", mapping) + return md.badtype("map", mapping) } if rv.IsNil() { rv.Set(reflect.MakeMap(rv.Type())) } for k, v := range tmap { - md.decoded[md.context.add(k).String()] = true + md.decoded[md.context.add(k).String()] = struct{}{} md.context = append(md.context, k) - rvkey := indirect(reflect.New(rv.Type().Key())) rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) - if err := md.unify(v, rvval); err != nil { + + err := md.unify(v, indirect(rvval)) + if err != nil { return err } md.context = md.context[0 : len(md.context)-1] - rvkey.SetString(k) + rvkey := indirect(reflect.New(rv.Type().Key())) + + switch keyType { + case reflect.Interface: + rvkey.Set(reflect.ValueOf(k)) + case reflect.String: + rvkey.SetString(k) + } + rv.SetMapIndex(rvkey, rvval) } return nil @@ -310,12 +353,10 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { if !datav.IsValid() { return nil } - return badtype("slice", data) + return md.badtype("slice", data) } - sliceLen := datav.Len() - if sliceLen != rv.Len() { - return e("expected array length %d; got TOML array of length %d", - rv.Len(), sliceLen) + if l := datav.Len(); l != rv.Len() { + return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l) } return md.unifySliceArray(datav, rv) } @@ -326,7 +367,7 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { if !datav.IsValid() { return nil } - return badtype("slice", data) + return md.badtype("slice", data) } n := datav.Len() if rv.IsNil() || rv.Cap() < n { @@ -337,37 +378,45 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { } func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { - sliceLen := data.Len() - for i := 0; i < sliceLen; i++ { - v := data.Index(i).Interface() - sliceval := indirect(rv.Index(i)) - if err := md.unify(v, sliceval); err != nil { + l := data.Len() + for i := 0; i < l; i++ { + err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i))) + if err != nil { return err } } return nil } -func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { - if _, ok := data.(time.Time); ok { - rv.Set(reflect.ValueOf(data)) +func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { + _, ok := rv.Interface().(json.Number) + if ok { + if i, ok := data.(int64); ok { + rv.SetString(strconv.FormatInt(i, 10)) + } else if f, ok := data.(float64); ok { + rv.SetString(strconv.FormatFloat(f, 'f', -1, 64)) + } else { + return md.badtype("string", data) + } return nil } - return badtype("time.Time", data) -} -func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { if s, ok := data.(string); ok { rv.SetString(s) return nil } - return badtype("string", data) + return md.badtype("string", data) } func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { + rvk := rv.Kind() + if num, ok := data.(float64); ok { - switch rv.Kind() { + switch rvk { case reflect.Float32: + if num < -math.MaxFloat32 || num > math.MaxFloat32 { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } fallthrough case reflect.Float64: rv.SetFloat(num) @@ -376,54 +425,60 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { } return nil } - return badtype("float", data) + + if num, ok := data.(int64); ok { + if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) || + (rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetFloat(float64(num)) + return nil + } + + return md.badtype("float", data) } func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { - if num, ok := data.(int64); ok { - if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { - switch rv.Kind() { - case reflect.Int, reflect.Int64: - // No bounds checking necessary. - case reflect.Int8: - if num < math.MinInt8 || num > math.MaxInt8 { - return e("value %d is out of range for int8", num) - } - case reflect.Int16: - if num < math.MinInt16 || num > math.MaxInt16 { - return e("value %d is out of range for int16", num) - } - case reflect.Int32: - if num < math.MinInt32 || num > math.MaxInt32 { - return e("value %d is out of range for int32", num) - } + _, ok := rv.Interface().(time.Duration) + if ok { + // Parse as string duration, and fall back to regular integer parsing + // (as nanosecond) if this is not a string. + if s, ok := data.(string); ok { + dur, err := time.ParseDuration(s) + if err != nil { + return md.parseErr(errParseDuration{s}) } - rv.SetInt(num) - } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { - unum := uint64(num) - switch rv.Kind() { - case reflect.Uint, reflect.Uint64: - // No bounds checking necessary. - case reflect.Uint8: - if num < 0 || unum > math.MaxUint8 { - return e("value %d is out of range for uint8", num) - } - case reflect.Uint16: - if num < 0 || unum > math.MaxUint16 { - return e("value %d is out of range for uint16", num) - } - case reflect.Uint32: - if num < 0 || unum > math.MaxUint32 { - return e("value %d is out of range for uint32", num) - } - } - rv.SetUint(unum) - } else { - panic("unreachable") + rv.SetInt(int64(dur)) + return nil } - return nil } - return badtype("integer", data) + + num, ok := data.(int64) + if !ok { + return md.badtype("integer", data) + } + + rvk := rv.Kind() + switch { + case rvk >= reflect.Int && rvk <= reflect.Int64: + if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) || + (rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) || + (rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetInt(num) + case rvk >= reflect.Uint && rvk <= reflect.Uint64: + unum := uint64(num) + if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) || + rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) || + rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetUint(unum) + default: + panic("unreachable") + } + return nil } func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { @@ -431,7 +486,7 @@ func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { rv.SetBool(b) return nil } - return badtype("boolean", data) + return md.badtype("boolean", data) } func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { @@ -439,10 +494,16 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { return nil } -func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { +func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error { var s string switch sdata := data.(type) { - case TextMarshaler: + case Marshaler: + text, err := sdata.MarshalTOML() + if err != nil { + return err + } + s = string(text) + case encoding.TextMarshaler: text, err := sdata.MarshalText() if err != nil { return err @@ -459,7 +520,7 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { case float64: s = fmt.Sprintf("%f", sdata) default: - return badtype("primitive (string-like)", data) + return md.badtype("primitive (string-like)", data) } if err := v.UnmarshalText([]byte(s)); err != nil { return err @@ -467,22 +528,54 @@ func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { return nil } +func (md *MetaData) badtype(dst string, data interface{}) error { + return md.e("incompatible types: TOML value has type %T; destination has type %s", data, dst) +} + +func (md *MetaData) parseErr(err error) error { + k := md.context.String() + return ParseError{ + LastKey: k, + Position: md.keyInfo[k].pos, + Line: md.keyInfo[k].pos.Line, + err: err, + input: string(md.data), + } +} + +func (md *MetaData) e(format string, args ...interface{}) error { + f := "toml: " + if len(md.context) > 0 { + f = fmt.Sprintf("toml: (last key %q): ", md.context) + p := md.keyInfo[md.context.String()].pos + if p.Line > 0 { + f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context) + } + } + return fmt.Errorf(f+format, args...) +} + // rvalue returns a reflect.Value of `v`. All pointers are resolved. func rvalue(v interface{}) reflect.Value { return indirect(reflect.ValueOf(v)) } // indirect returns the value pointed to by a pointer. -// Pointers are followed until the value is not a pointer. -// New values are allocated for each nil pointer. // -// An exception to this rule is if the value satisfies an interface of -// interest to us (like encoding.TextUnmarshaler). +// Pointers are followed until the value is not a pointer. New values are +// allocated for each nil pointer. +// +// An exception to this rule is if the value satisfies an interface of interest +// to us (like encoding.TextUnmarshaler). func indirect(v reflect.Value) reflect.Value { if v.Kind() != reflect.Ptr { if v.CanSet() { pv := v.Addr() - if _, ok := pv.Interface().(TextUnmarshaler); ok { + pvi := pv.Interface() + if _, ok := pvi.(encoding.TextUnmarshaler); ok { + return pv + } + if _, ok := pvi.(Unmarshaler); ok { return pv } } @@ -498,12 +591,12 @@ func isUnifiable(rv reflect.Value) bool { if rv.CanSet() { return true } - if _, ok := rv.Interface().(TextUnmarshaler); ok { + rvi := rv.Interface() + if _, ok := rvi.(encoding.TextUnmarshaler); ok { + return true + } + if _, ok := rvi.(Unmarshaler); ok { return true } return false } - -func badtype(expected string, data interface{}) error { - return e("cannot load TOML value of type %T into a Go %s", data, expected) -} diff --git a/vendor/github.com/BurntSushi/toml/decode_go116.go b/vendor/github.com/BurntSushi/toml/decode_go116.go new file mode 100644 index 00000000..eddfb641 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/decode_go116.go @@ -0,0 +1,19 @@ +//go:build go1.16 +// +build go1.16 + +package toml + +import ( + "io/fs" +) + +// DecodeFS is just like Decode, except it will automatically read the contents +// of the file at `path` from a fs.FS instance. +func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) { + fp, err := fsys.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} diff --git a/vendor/github.com/BurntSushi/toml/deprecated.go b/vendor/github.com/BurntSushi/toml/deprecated.go new file mode 100644 index 00000000..c6af3f23 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/deprecated.go @@ -0,0 +1,21 @@ +package toml + +import ( + "encoding" + "io" +) + +// Deprecated: use encoding.TextMarshaler +type TextMarshaler encoding.TextMarshaler + +// Deprecated: use encoding.TextUnmarshaler +type TextUnmarshaler encoding.TextUnmarshaler + +// Deprecated: use MetaData.PrimitiveDecode. +func PrimitiveDecode(primValue Primitive, v interface{}) error { + md := MetaData{decoded: make(map[string]struct{})} + return md.unify(primValue.undecoded, rvalue(v)) +} + +// Deprecated: use NewDecoder(reader).Decode(&value). +func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) } diff --git a/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/BurntSushi/toml/doc.go index b371f396..099c4a77 100644 --- a/vendor/github.com/BurntSushi/toml/doc.go +++ b/vendor/github.com/BurntSushi/toml/doc.go @@ -1,27 +1,13 @@ /* -Package toml provides facilities for decoding and encoding TOML configuration -files via reflection. There is also support for delaying decoding with -the Primitive type, and querying the set of keys in a TOML document with the -MetaData type. +Package toml implements decoding and encoding of TOML files. -The specification implemented: https://github.com/toml-lang/toml +This package supports TOML v1.0.0, as listed on https://toml.io -The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify -whether a file is a valid TOML document. It can also be used to print the -type of each key in a TOML document. +There is also support for delaying decoding with the Primitive type, and +querying the set of keys in a TOML document with the MetaData type. -Testing - -There are two important types of tests used for this package. The first is -contained inside '*_test.go' files and uses the standard Go unit testing -framework. These tests are primarily devoted to holistically testing the -decoder and encoder. - -The second type of testing is used to verify the implementation's adherence -to the TOML specification. These tests have been factored into their own -project: https://github.com/BurntSushi/toml-test - -The reason the tests are in a separate project is so that they can be used by -any implementation of TOML. Namely, it is language agnostic. +The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator, +and can be used to verify if TOML document is valid. It can also be used to +print the type of each key. */ package toml diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go index d905c21a..dc8568d1 100644 --- a/vendor/github.com/BurntSushi/toml/encode.go +++ b/vendor/github.com/BurntSushi/toml/encode.go @@ -2,57 +2,127 @@ package toml import ( "bufio" + "encoding" + "encoding/json" "errors" "fmt" "io" + "math" "reflect" "sort" "strconv" "strings" "time" + + "github.com/BurntSushi/toml/internal" ) type tomlEncodeError struct{ error } var ( - errArrayMixedElementTypes = errors.New( - "toml: cannot encode array with mixed element types") - errArrayNilElement = errors.New( - "toml: cannot encode array with nil element") - errNonString = errors.New( - "toml: cannot encode a map with non-string key type") - errAnonNonStruct = errors.New( - "toml: cannot encode an anonymous field that is not a struct") - errArrayNoTable = errors.New( - "toml: TOML array element cannot contain a table") - errNoKey = errors.New( - "toml: top-level values must be Go maps or structs") - errAnything = errors.New("") // used in testing + errArrayNilElement = errors.New("toml: cannot encode array with nil element") + errNonString = errors.New("toml: cannot encode a map with non-string key type") + errNoKey = errors.New("toml: top-level values must be Go maps or structs") + errAnything = errors.New("") // used in testing ) -var quotedReplacer = strings.NewReplacer( - "\t", "\\t", - "\n", "\\n", - "\r", "\\r", +var dblQuotedReplacer = strings.NewReplacer( "\"", "\\\"", "\\", "\\\\", + "\x00", `\u0000`, + "\x01", `\u0001`, + "\x02", `\u0002`, + "\x03", `\u0003`, + "\x04", `\u0004`, + "\x05", `\u0005`, + "\x06", `\u0006`, + "\x07", `\u0007`, + "\b", `\b`, + "\t", `\t`, + "\n", `\n`, + "\x0b", `\u000b`, + "\f", `\f`, + "\r", `\r`, + "\x0e", `\u000e`, + "\x0f", `\u000f`, + "\x10", `\u0010`, + "\x11", `\u0011`, + "\x12", `\u0012`, + "\x13", `\u0013`, + "\x14", `\u0014`, + "\x15", `\u0015`, + "\x16", `\u0016`, + "\x17", `\u0017`, + "\x18", `\u0018`, + "\x19", `\u0019`, + "\x1a", `\u001a`, + "\x1b", `\u001b`, + "\x1c", `\u001c`, + "\x1d", `\u001d`, + "\x1e", `\u001e`, + "\x1f", `\u001f`, + "\x7f", `\u007f`, ) -// Encoder controls the encoding of Go values to a TOML document to some -// io.Writer. -// -// The indentation level can be controlled with the Indent field. -type Encoder struct { - // A single indentation level. By default it is two spaces. - Indent string +var ( + marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem() + marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +) - // hasWritten is whether we have written any output to w yet. - hasWritten bool - w *bufio.Writer +// Marshaler is the interface implemented by types that can marshal themselves +// into valid TOML. +type Marshaler interface { + MarshalTOML() ([]byte, error) } -// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer -// given. By default, a single indentation level is 2 spaces. +// Encoder encodes a Go to a TOML document. +// +// The mapping between Go values and TOML values should be precisely the same as +// for the Decode* functions. +// +// time.Time is encoded as a RFC 3339 string, and time.Duration as its string +// representation. +// +// The toml.Marshaler and encoder.TextMarshaler interfaces are supported to +// encoding the value as custom TOML. +// +// If you want to write arbitrary binary data then you will need to use +// something like base64 since TOML does not have any binary types. +// +// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes +// are encoded first. +// +// Go maps will be sorted alphabetically by key for deterministic output. +// +// The toml struct tag can be used to provide the key name; if omitted the +// struct field name will be used. If the "omitempty" option is present the +// following value will be skipped: +// +// - arrays, slices, maps, and string with len of 0 +// - struct with all zero values +// - bool false +// +// If omitzero is given all int and float types with a value of 0 will be +// skipped. +// +// Encoding Go values without a corresponding TOML representation will return an +// error. Examples of this includes maps with non-string keys, slices with nil +// elements, embedded non-struct types, and nested slices containing maps or +// structs. (e.g. [][]map[string]string is not allowed but []map[string]string +// is okay, as is []map[string][]string). +// +// NOTE: only exported keys are encoded due to the use of reflection. Unexported +// keys are silently discarded. +type Encoder struct { + // String to use for a single indentation level; default is two spaces. + Indent string + + w *bufio.Writer + hasWritten bool // written any output to w yet? +} + +// NewEncoder create a new Encoder. func NewEncoder(w io.Writer) *Encoder { return &Encoder{ w: bufio.NewWriter(w), @@ -60,29 +130,10 @@ func NewEncoder(w io.Writer) *Encoder { } } -// Encode writes a TOML representation of the Go value to the underlying -// io.Writer. If the value given cannot be encoded to a valid TOML document, -// then an error is returned. +// Encode writes a TOML representation of the Go value to the Encoder's writer. // -// The mapping between Go values and TOML values should be precisely the same -// as for the Decode* functions. Similarly, the TextMarshaler interface is -// supported by encoding the resulting bytes as strings. (If you want to write -// arbitrary binary data then you will need to use something like base64 since -// TOML does not have any binary types.) -// -// When encoding TOML hashes (i.e., Go maps or structs), keys without any -// sub-hashes are encoded first. -// -// If a Go map is encoded, then its keys are sorted alphabetically for -// deterministic output. More control over this behavior may be provided if -// there is demand for it. -// -// Encoding Go values without a corresponding TOML representation---like map -// types with non-string keys---will cause an error to be returned. Similarly -// for mixed arrays/slices, arrays/slices with nil elements, embedded -// non-struct types and nested slices containing maps or structs. -// (e.g., [][]map[string]string is not allowed but []map[string]string is OK -// and so is []map[string][]string.) +// An error is returned if the value given cannot be encoded to a valid TOML +// document. func (enc *Encoder) Encode(v interface{}) error { rv := eindirect(reflect.ValueOf(v)) if err := enc.safeEncode(Key([]string{}), rv); err != nil { @@ -106,13 +157,15 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { } func (enc *Encoder) encode(key Key, rv reflect.Value) { - // Special case. Time needs to be in ISO8601 format. - // Special case. If we can marshal the type to text, then we used that. - // Basically, this prevents the encoder for handling these types as - // generic structs (or whatever the underlying type of a TextMarshaler is). - switch rv.Interface().(type) { - case time.Time, TextMarshaler: - enc.keyEqElement(key, rv) + // If we can marshal the type to text, then we use that. This prevents the + // encoder for handling these types as generic structs (or whatever the + // underlying type of a TextMarshaler is). + switch { + case isMarshaler(rv): + enc.writeKeyValue(key, rv, false) + return + case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented. + enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded)) return } @@ -123,12 +176,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: - enc.keyEqElement(key, rv) + enc.writeKeyValue(key, rv, false) case reflect.Array, reflect.Slice: if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { enc.eArrayOfTables(key, rv) } else { - enc.keyEqElement(key, rv) + enc.writeKeyValue(key, rv, false) } case reflect.Interface: if rv.IsNil() { @@ -148,55 +201,114 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { case reflect.Struct: enc.eTable(key, rv) default: - panic(e("unsupported type for key '%s': %s", key, k)) + encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k)) } } -// eElement encodes any value that can be an array element (primitives and -// arrays). +// eElement encodes any value that can be an array element. func (enc *Encoder) eElement(rv reflect.Value) { switch v := rv.Interface().(type) { - case time.Time: - // Special case time.Time as a primitive. Has to come before - // TextMarshaler below because time.Time implements - // encoding.TextMarshaler, but we need to always use UTC. - enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) - return - case TextMarshaler: - // Special case. Use text marshaler if it's available for this value. - if s, err := v.MarshalText(); err != nil { - encPanic(err) - } else { - enc.writeQuoted(string(s)) + case time.Time: // Using TextMarshaler adds extra quotes, which we don't want. + format := time.RFC3339Nano + switch v.Location() { + case internal.LocalDatetime: + format = "2006-01-02T15:04:05.999999999" + case internal.LocalDate: + format = "2006-01-02" + case internal.LocalTime: + format = "15:04:05.999999999" + } + switch v.Location() { + default: + enc.wf(v.Format(format)) + case internal.LocalDatetime, internal.LocalDate, internal.LocalTime: + enc.wf(v.In(time.UTC).Format(format)) } return + case Marshaler: + s, err := v.MarshalTOML() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalTOML returned nil and no error")) + } + enc.w.Write(s) + return + case encoding.TextMarshaler: + s, err := v.MarshalText() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalText returned nil and no error")) + } + enc.writeQuoted(string(s)) + return + case time.Duration: + enc.writeQuoted(v.String()) + return + case json.Number: + n, _ := rv.Interface().(json.Number) + + if n == "" { /// Useful zero value. + enc.w.WriteByte('0') + return + } else if v, err := n.Int64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } else if v, err := n.Float64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } + encPanic(errors.New(fmt.Sprintf("Unable to convert \"%s\" to neither int64 nor float64", n))) } + switch rv.Kind() { - case reflect.Bool: - enc.wf(strconv.FormatBool(rv.Bool())) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, - reflect.Int64: - enc.wf(strconv.FormatInt(rv.Int(), 10)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, - reflect.Uint32, reflect.Uint64: - enc.wf(strconv.FormatUint(rv.Uint(), 10)) - case reflect.Float32: - enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) - case reflect.Float64: - enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) - case reflect.Array, reflect.Slice: - enc.eArrayOrSliceElement(rv) - case reflect.Interface: + case reflect.Ptr: enc.eElement(rv.Elem()) + return case reflect.String: enc.writeQuoted(rv.String()) + case reflect.Bool: + enc.wf(strconv.FormatBool(rv.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + enc.wf(strconv.FormatInt(rv.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + enc.wf(strconv.FormatUint(rv.Uint(), 10)) + case reflect.Float32: + f := rv.Float() + if math.IsNaN(f) { + enc.wf("nan") + } else if math.IsInf(f, 0) { + enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) + } + case reflect.Float64: + f := rv.Float() + if math.IsNaN(f) { + enc.wf("nan") + } else if math.IsInf(f, 0) { + enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) + } + case reflect.Array, reflect.Slice: + enc.eArrayOrSliceElement(rv) + case reflect.Struct: + enc.eStruct(nil, rv, true) + case reflect.Map: + enc.eMap(nil, rv, true) + case reflect.Interface: + enc.eElement(rv.Elem()) default: - panic(e("unexpected primitive type: %s", rv.Kind())) + encPanic(fmt.Errorf("unexpected type: %T", rv.Interface())) } } -// By the TOML spec, all floats must have a decimal with at least one -// number on either side. +// By the TOML spec, all floats must have a decimal with at least one number on +// either side. func floatAddDecimal(fstr string) string { if !strings.Contains(fstr, ".") { return fstr + ".0" @@ -205,14 +317,14 @@ func floatAddDecimal(fstr string) string { } func (enc *Encoder) writeQuoted(s string) { - enc.wf("\"%s\"", quotedReplacer.Replace(s)) + enc.wf("\"%s\"", dblQuotedReplacer.Replace(s)) } func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { length := rv.Len() enc.wf("[") for i := 0; i < length; i++ { - elem := rv.Index(i) + elem := eindirect(rv.Index(i)) enc.eElement(elem) if i != length-1 { enc.wf(", ") @@ -226,44 +338,43 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { encPanic(errNoKey) } for i := 0; i < rv.Len(); i++ { - trv := rv.Index(i) + trv := eindirect(rv.Index(i)) if isNil(trv) { continue } - panicIfInvalidKey(key) enc.newline() - enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) + enc.wf("%s[[%s]]", enc.indentStr(key), key) enc.newline() - enc.eMapOrStruct(key, trv) + enc.eMapOrStruct(key, trv, false) } } func (enc *Encoder) eTable(key Key, rv reflect.Value) { - panicIfInvalidKey(key) if len(key) == 1 { // Output an extra newline between top-level tables. // (The newline isn't written if nothing else has been written though.) enc.newline() } if len(key) > 0 { - enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) + enc.wf("%s[%s]", enc.indentStr(key), key) enc.newline() } - enc.eMapOrStruct(key, rv) + enc.eMapOrStruct(key, rv, false) } -func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { - switch rv := eindirect(rv); rv.Kind() { +func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) { + switch rv.Kind() { case reflect.Map: - enc.eMap(key, rv) + enc.eMap(key, rv, inline) case reflect.Struct: - enc.eStruct(key, rv) + enc.eStruct(key, rv, inline) default: + // Should never happen? panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) } } -func (enc *Encoder) eMap(key Key, rv reflect.Value) { +func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) { rt := rv.Type() if rt.Key().Kind() != reflect.String { encPanic(errNonString) @@ -274,118 +385,179 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) { var mapKeysDirect, mapKeysSub []string for _, mapKey := range rv.MapKeys() { k := mapKey.String() - if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { + if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) { mapKeysSub = append(mapKeysSub, k) } else { mapKeysDirect = append(mapKeysDirect, k) } } - var writeMapKeys = func(mapKeys []string) { + var writeMapKeys = func(mapKeys []string, trailC bool) { sort.Strings(mapKeys) - for _, mapKey := range mapKeys { - mrv := rv.MapIndex(reflect.ValueOf(mapKey)) - if isNil(mrv) { - // Don't write anything for nil fields. + for i, mapKey := range mapKeys { + val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey))) + if isNil(val) { continue } - enc.encode(key.add(mapKey), mrv) + + if inline { + enc.writeKeyValue(Key{mapKey}, val, true) + if trailC || i != len(mapKeys)-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(mapKey), val) + } } } - writeMapKeys(mapKeysDirect) - writeMapKeys(mapKeysSub) + + if inline { + enc.wf("{") + } + writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0) + writeMapKeys(mapKeysSub, false) + if inline { + enc.wf("}") + } } -func (enc *Encoder) eStruct(key Key, rv reflect.Value) { +const is32Bit = (32 << (^uint(0) >> 63)) == 32 + +func pointerTo(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + return pointerTo(t.Elem()) + } + return t +} + +func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { // Write keys for fields directly under this key first, because if we write - // a field that creates a new table, then all keys under it will be in that + // a field that creates a new table then all keys under it will be in that // table (not the one we're writing here). - rt := rv.Type() - var fieldsDirect, fieldsSub [][]int - var addFields func(rt reflect.Type, rv reflect.Value, start []int) + // + // Fields is a [][]int: for fieldsDirect this always has one entry (the + // struct index). For fieldsSub it contains two entries: the parent field + // index from tv, and the field indexes for the fields of the sub. + var ( + rt = rv.Type() + fieldsDirect, fieldsSub [][]int + addFields func(rt reflect.Type, rv reflect.Value, start []int) + ) addFields = func(rt reflect.Type, rv reflect.Value, start []int) { for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) - // skip unexported fields - if f.PkgPath != "" && !f.Anonymous { + isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct + if f.PkgPath != "" && !isEmbed { /// Skip unexported fields. continue } - frv := rv.Field(i) - if f.Anonymous { - t := f.Type - switch t.Kind() { - case reflect.Struct: - // Treat anonymous struct fields with - // tag names as though they are not - // anonymous, like encoding/json does. - if getOptions(f.Tag).name == "" { - addFields(t, frv, f.Index) - continue - } - case reflect.Ptr: - if t.Elem().Kind() == reflect.Struct && - getOptions(f.Tag).name == "" { - if !frv.IsNil() { - addFields(t.Elem(), frv.Elem(), f.Index) - } - continue - } - // Fall through to the normal field encoding logic below - // for non-struct anonymous fields. + opts := getOptions(f.Tag) + if opts.skip { + continue + } + + frv := eindirect(rv.Field(i)) + + // Treat anonymous struct fields with tag names as though they are + // not anonymous, like encoding/json does. + // + // Non-struct anonymous fields use the normal encoding logic. + if isEmbed { + if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct { + addFields(frv.Type(), frv, append(start, f.Index...)) + continue } } - if typeIsHash(tomlTypeOfGo(frv)) { + if typeIsTable(tomlTypeOfGo(frv)) { fieldsSub = append(fieldsSub, append(start, f.Index...)) } else { - fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + // Copy so it works correct on 32bit archs; not clear why this + // is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4 + // This also works fine on 64bit, but 32bit archs are somewhat + // rare and this is a wee bit faster. + if is32Bit { + copyStart := make([]int, len(start)) + copy(copyStart, start) + fieldsDirect = append(fieldsDirect, append(copyStart, f.Index...)) + } else { + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + } } } } addFields(rt, rv, nil) - var writeFields = func(fields [][]int) { + writeFields := func(fields [][]int) { for _, fieldIndex := range fields { - sft := rt.FieldByIndex(fieldIndex) - sf := rv.FieldByIndex(fieldIndex) - if isNil(sf) { - // Don't write anything for nil fields. + fieldType := rt.FieldByIndex(fieldIndex) + fieldVal := eindirect(rv.FieldByIndex(fieldIndex)) + + if isNil(fieldVal) { /// Don't write anything for nil fields. continue } - opts := getOptions(sft.Tag) + opts := getOptions(fieldType.Tag) if opts.skip { continue } - keyName := sft.Name + keyName := fieldType.Name if opts.name != "" { keyName = opts.name } - if opts.omitempty && isEmpty(sf) { + if opts.omitempty && isEmpty(fieldVal) { continue } - if opts.omitzero && isZero(sf) { + if opts.omitzero && isZero(fieldVal) { continue } - enc.encode(key.add(keyName), sf) + if inline { + enc.writeKeyValue(Key{keyName}, fieldVal, true) + if fieldIndex[0] != len(fields)-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(keyName), fieldVal) + } } } + + if inline { + enc.wf("{") + } writeFields(fieldsDirect) writeFields(fieldsSub) + if inline { + enc.wf("}") + } } -// tomlTypeName returns the TOML type name of the Go value's type. It is -// used to determine whether the types of array elements are mixed (which is -// forbidden). If the Go value is nil, then it is illegal for it to be an array -// element, and valueIsNil is returned as true. - -// Returns the TOML type of a Go value. The type may be `nil`, which means -// no concrete TOML type could be found. +// tomlTypeOfGo returns the TOML type name of the Go value's type. +// +// It is used to determine whether the types of array elements are mixed (which +// is forbidden). If the Go value is nil, then it is illegal for it to be an +// array element, and valueIsNil is returned as true. +// +// The type may be `nil`, which means no concrete TOML type could be found. func tomlTypeOfGo(rv reflect.Value) tomlType { if isNil(rv) || !rv.IsValid() { return nil } + + if rv.Kind() == reflect.Struct { + if rv.Type() == timeType { + return tomlDatetime + } + if isMarshaler(rv) { + return tomlString + } + return tomlHash + } + + if isMarshaler(rv) { + return tomlString + } + switch rv.Kind() { case reflect.Bool: return tomlBool @@ -397,7 +569,7 @@ func tomlTypeOfGo(rv reflect.Value) tomlType { case reflect.Float32, reflect.Float64: return tomlFloat case reflect.Array, reflect.Slice: - if typeEqual(tomlHash, tomlArrayType(rv)) { + if isTableArray(rv) { return tomlArrayHash } return tomlArray @@ -407,54 +579,35 @@ func tomlTypeOfGo(rv reflect.Value) tomlType { return tomlString case reflect.Map: return tomlHash - case reflect.Struct: - switch rv.Interface().(type) { - case time.Time: - return tomlDatetime - case TextMarshaler: - return tomlString - default: - return tomlHash - } default: - panic("unexpected reflect.Kind: " + rv.Kind().String()) + encPanic(errors.New("unsupported type: " + rv.Kind().String())) + panic("unreachable") } } -// tomlArrayType returns the element type of a TOML array. The type returned -// may be nil if it cannot be determined (e.g., a nil slice or a zero length -// slize). This function may also panic if it finds a type that cannot be -// expressed in TOML (such as nil elements, heterogeneous arrays or directly -// nested arrays of tables). -func tomlArrayType(rv reflect.Value) tomlType { - if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { - return nil - } - firstType := tomlTypeOfGo(rv.Index(0)) - if firstType == nil { - encPanic(errArrayNilElement) +func isMarshaler(rv reflect.Value) bool { + return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml) +} + +// isTableArray reports if all entries in the array or slice are a table. +func isTableArray(arr reflect.Value) bool { + if isNil(arr) || !arr.IsValid() || arr.Len() == 0 { + return false } - rvlen := rv.Len() - for i := 1; i < rvlen; i++ { - elem := rv.Index(i) - switch elemType := tomlTypeOfGo(elem); { - case elemType == nil: + ret := true + for i := 0; i < arr.Len(); i++ { + tt := tomlTypeOfGo(eindirect(arr.Index(i))) + // Don't allow nil. + if tt == nil { encPanic(errArrayNilElement) - case !typeEqual(firstType, elemType): - encPanic(errArrayMixedElementTypes) + } + + if ret && !typeEqual(tomlHash, tt) { + ret = false } } - // If we have a nested array, then we must make sure that the nested - // array contains ONLY primitives. - // This checks arbitrarily nested arrays. - if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { - nest := tomlArrayType(eindirect(rv.Index(0))) - if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { - encPanic(errArrayNoTable) - } - } - return firstType + return ret } type tagOptions struct { @@ -499,6 +652,8 @@ func isEmpty(rv reflect.Value) bool { switch rv.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return rv.Len() == 0 + case reflect.Struct: + return reflect.Zero(rv.Type()).Interface() == rv.Interface() case reflect.Bool: return !rv.Bool() } @@ -511,18 +666,32 @@ func (enc *Encoder) newline() { } } -func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { +// Write a key/value pair: +// +// key = +// +// This is also used for "k = v" in inline tables; so something like this will +// be written in three calls: +// +// ┌────────────────────┐ +// │ ┌───┐ ┌─────┐│ +// v v v v vv +// key = {k = v, k2 = v2} +// +func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { if len(key) == 0 { encPanic(errNoKey) } - panicIfInvalidKey(key) enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) enc.eElement(val) - enc.newline() + if !inline { + enc.newline() + } } func (enc *Encoder) wf(format string, v ...interface{}) { - if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { + _, err := fmt.Fprintf(enc.w, format, v...) + if err != nil { encPanic(err) } enc.hasWritten = true @@ -536,13 +705,25 @@ func encPanic(err error) { panic(tomlEncodeError{err}) } +// Resolve any level of pointers to the actual value (e.g. **string → string). func eindirect(v reflect.Value) reflect.Value { - switch v.Kind() { - case reflect.Ptr, reflect.Interface: - return eindirect(v.Elem()) - default: + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + if isMarshaler(v) { + return v + } + if v.CanAddr() { /// Special case for marshalers; see #358. + if pv := v.Addr(); isMarshaler(pv) { + return pv + } + } return v } + + if v.IsNil() { + return v + } + + return eindirect(v.Elem()) } func isNil(rv reflect.Value) bool { @@ -553,16 +734,3 @@ func isNil(rv reflect.Value) bool { return false } } - -func panicIfInvalidKey(key Key) { - for _, k := range key { - if len(k) == 0 { - encPanic(e("Key '%s' is not a valid table name. Key names "+ - "cannot be empty.", key.maybeQuotedAll())) - } - } -} - -func isValidKeyName(s string) bool { - return len(s) != 0 -} diff --git a/vendor/github.com/BurntSushi/toml/encoding_types.go b/vendor/github.com/BurntSushi/toml/encoding_types.go deleted file mode 100644 index d36e1dd6..00000000 --- a/vendor/github.com/BurntSushi/toml/encoding_types.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build go1.2 - -package toml - -// In order to support Go 1.1, we define our own TextMarshaler and -// TextUnmarshaler types. For Go 1.2+, we just alias them with the -// standard library interfaces. - -import ( - "encoding" -) - -// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here -// so that Go 1.1 can be supported. -type TextMarshaler encoding.TextMarshaler - -// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined -// here so that Go 1.1 can be supported. -type TextUnmarshaler encoding.TextUnmarshaler diff --git a/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go b/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go deleted file mode 100644 index e8d503d0..00000000 --- a/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build !go1.2 - -package toml - -// These interfaces were introduced in Go 1.2, so we add them manually when -// compiling for Go 1.1. - -// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here -// so that Go 1.1 can be supported. -type TextMarshaler interface { - MarshalText() (text []byte, err error) -} - -// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined -// here so that Go 1.1 can be supported. -type TextUnmarshaler interface { - UnmarshalText(text []byte) error -} diff --git a/vendor/github.com/BurntSushi/toml/error.go b/vendor/github.com/BurntSushi/toml/error.go new file mode 100644 index 00000000..2ac24e77 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/error.go @@ -0,0 +1,276 @@ +package toml + +import ( + "fmt" + "strings" +) + +// ParseError is returned when there is an error parsing the TOML syntax. +// +// For example invalid syntax, duplicate keys, etc. +// +// In addition to the error message itself, you can also print detailed location +// information with context by using ErrorWithPosition(): +// +// toml: error: Key 'fruit' was already created and cannot be used as an array. +// +// At line 4, column 2-7: +// +// 2 | fruit = [] +// 3 | +// 4 | [[fruit]] # Not allowed +// ^^^^^ +// +// Furthermore, the ErrorWithUsage() can be used to print the above with some +// more detailed usage guidance: +// +// toml: error: newlines not allowed within inline tables +// +// At line 1, column 18: +// +// 1 | x = [{ key = 42 # +// ^ +// +// Error help: +// +// Inline tables must always be on a single line: +// +// table = {key = 42, second = 43} +// +// It is invalid to split them over multiple lines like so: +// +// # INVALID +// table = { +// key = 42, +// second = 43 +// } +// +// Use regular for this: +// +// [table] +// key = 42 +// second = 43 +type ParseError struct { + Message string // Short technical message. + Usage string // Longer message with usage guidance; may be blank. + Position Position // Position of the error + LastKey string // Last parsed key, may be blank. + Line int // Line the error occurred. Deprecated: use Position. + + err error + input string +} + +// Position of an error. +type Position struct { + Line int // Line number, starting at 1. + Start int // Start of error, as byte offset starting at 0. + Len int // Lenght in bytes. +} + +func (pe ParseError) Error() string { + msg := pe.Message + if msg == "" { // Error from errorf() + msg = pe.err.Error() + } + + if pe.LastKey == "" { + return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg) + } + return fmt.Sprintf("toml: line %d (last key %q): %s", + pe.Position.Line, pe.LastKey, msg) +} + +// ErrorWithUsage() returns the error with detailed location context. +// +// See the documentation on ParseError. +func (pe ParseError) ErrorWithPosition() string { + if pe.input == "" { // Should never happen, but just in case. + return pe.Error() + } + + var ( + lines = strings.Split(pe.input, "\n") + col = pe.column(lines) + b = new(strings.Builder) + ) + + msg := pe.Message + if msg == "" { + msg = pe.err.Error() + } + + // TODO: don't show control characters as literals? This may not show up + // well everywhere. + + if pe.Position.Len == 1 { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n", + msg, pe.Position.Line, col+1) + } else { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n", + msg, pe.Position.Line, col, col+pe.Position.Len) + } + if pe.Position.Line > 2 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3]) + } + if pe.Position.Line > 1 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2]) + } + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1]) + fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len)) + return b.String() +} + +// ErrorWithUsage() returns the error with detailed location context and usage +// guidance. +// +// See the documentation on ParseError. +func (pe ParseError) ErrorWithUsage() string { + m := pe.ErrorWithPosition() + if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" { + lines := strings.Split(strings.TrimSpace(u.Usage()), "\n") + for i := range lines { + if lines[i] != "" { + lines[i] = " " + lines[i] + } + } + return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n" + } + return m +} + +func (pe ParseError) column(lines []string) int { + var pos, col int + for i := range lines { + ll := len(lines[i]) + 1 // +1 for the removed newline + if pos+ll >= pe.Position.Start { + col = pe.Position.Start - pos + if col < 0 { // Should never happen, but just in case. + col = 0 + } + break + } + pos += ll + } + + return col +} + +type ( + errLexControl struct{ r rune } + errLexEscape struct{ r rune } + errLexUTF8 struct{ b byte } + errLexInvalidNum struct{ v string } + errLexInvalidDate struct{ v string } + errLexInlineTableNL struct{} + errLexStringNL struct{} + errParseRange struct { + i interface{} // int or float + size string // "int64", "uint16", etc. + } + errParseDuration struct{ d string } +) + +func (e errLexControl) Error() string { + return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r) +} +func (e errLexControl) Usage() string { return "" } + +func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) } +func (e errLexEscape) Usage() string { return usageEscape } +func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) } +func (e errLexUTF8) Usage() string { return "" } +func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) } +func (e errLexInvalidNum) Usage() string { return "" } +func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) } +func (e errLexInvalidDate) Usage() string { return "" } +func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" } +func (e errLexInlineTableNL) Usage() string { return usageInlineNewline } +func (e errLexStringNL) Error() string { return "strings cannot contain newlines" } +func (e errLexStringNL) Usage() string { return usageStringNewline } +func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) } +func (e errParseRange) Usage() string { return usageIntOverflow } +func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) } +func (e errParseDuration) Usage() string { return usageDuration } + +const usageEscape = ` +A '\' inside a "-delimited string is interpreted as an escape character. + +The following escape sequences are supported: +\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX + +To prevent a '\' from being recognized as an escape character, use either: + +- a ' or '''-delimited string; escape characters aren't processed in them; or +- write two backslashes to get a single backslash: '\\'. + +If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/' +instead of '\' will usually also work: "C:/Users/martin". +` + +const usageInlineNewline = ` +Inline tables must always be on a single line: + + table = {key = 42, second = 43} + +It is invalid to split them over multiple lines like so: + + # INVALID + table = { + key = 42, + second = 43 + } + +Use regular for this: + + [table] + key = 42 + second = 43 +` + +const usageStringNewline = ` +Strings must always be on a single line, and cannot span more than one line: + + # INVALID + string = "Hello, + world!" + +Instead use """ or ''' to split strings over multiple lines: + + string = """Hello, + world!""" +` + +const usageIntOverflow = ` +This number is too large; this may be an error in the TOML, but it can also be a +bug in the program that uses too small of an integer. + +The maximum and minimum values are: + + size │ lowest │ highest + ───────┼────────────────┼────────── + int8 │ -128 │ 127 + int16 │ -32,768 │ 32,767 + int32 │ -2,147,483,648 │ 2,147,483,647 + int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ + uint8 │ 0 │ 255 + uint16 │ 0 │ 65535 + uint32 │ 0 │ 4294967295 + uint64 │ 0 │ 1.8 × 10¹⁸ + +int refers to int32 on 32-bit systems and int64 on 64-bit systems. +` + +const usageDuration = ` +A duration must be as "number", without any spaces. Valid units are: + + ns nanoseconds (billionth of a second) + us, µs microseconds (millionth of a second) + ms milliseconds (thousands of a second) + s seconds + m minutes + h hours + +You can combine multiple units; for example "5m10s" for 5 minutes and 10 +seconds. +` diff --git a/vendor/github.com/BurntSushi/toml/internal/tz.go b/vendor/github.com/BurntSushi/toml/internal/tz.go new file mode 100644 index 00000000..022f15bc --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/internal/tz.go @@ -0,0 +1,36 @@ +package internal + +import "time" + +// Timezones used for local datetime, date, and time TOML types. +// +// The exact way times and dates without a timezone should be interpreted is not +// well-defined in the TOML specification and left to the implementation. These +// defaults to current local timezone offset of the computer, but this can be +// changed by changing these variables before decoding. +// +// TODO: +// Ideally we'd like to offer people the ability to configure the used timezone +// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit +// tricky: the reason we use three different variables for this is to support +// round-tripping – without these specific TZ names we wouldn't know which +// format to use. +// +// There isn't a good way to encode this right now though, and passing this sort +// of information also ties in to various related issues such as string format +// encoding, encoding of comments, etc. +// +// So, for the time being, just put this in internal until we can write a good +// comprehensive API for doing all of this. +// +// The reason they're exported is because they're referred from in e.g. +// internal/tag. +// +// Note that this behaviour is valid according to the TOML spec as the exact +// behaviour is left up to implementations. +var ( + localOffset = func() int { _, o := time.Now().Zone(); return o }() + LocalDatetime = time.FixedZone("datetime-local", localOffset) + LocalDate = time.FixedZone("date-local", localOffset) + LocalTime = time.FixedZone("time-local", localOffset) +) diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go index e0a742a8..28ed4dd3 100644 --- a/vendor/github.com/BurntSushi/toml/lex.go +++ b/vendor/github.com/BurntSushi/toml/lex.go @@ -2,6 +2,8 @@ package toml import ( "fmt" + "reflect" + "runtime" "strings" "unicode" "unicode/utf8" @@ -29,33 +31,20 @@ const ( itemArrayTableStart itemArrayTableEnd itemKeyStart + itemKeyEnd itemCommentStart itemInlineTableStart itemInlineTableEnd ) -const ( - eof = 0 - comma = ',' - tableStart = '[' - tableEnd = ']' - arrayTableStart = '[' - arrayTableEnd = ']' - tableSep = '.' - keySep = '=' - arrayStart = '[' - arrayEnd = ']' - commentStart = '#' - stringStart = '"' - stringEnd = '"' - rawStringStart = '\'' - rawStringEnd = '\'' - inlineTableStart = '{' - inlineTableEnd = '}' -) +const eof = 0 type stateFn func(lx *lexer) stateFn +func (p Position) String() string { + return fmt.Sprintf("at line %d; start %d; length %d", p.Line, p.Start, p.Len) +} + type lexer struct { input string start int @@ -64,26 +53,26 @@ type lexer struct { state stateFn items chan item - // Allow for backing up up to three runes. - // This is necessary because TOML contains 3-rune tokens (""" and '''). - prevWidths [3]int - nprev int // how many of prevWidths are in use - // If we emit an eof, we can still back up, but it is not OK to call - // next again. - atEOF bool + // Allow for backing up up to 4 runes. This is necessary because TOML + // contains 3-rune tokens (""" and '''). + prevWidths [4]int + nprev int // how many of prevWidths are in use + atEOF bool // If we emit an eof, we can still back up, but it is not OK to call next again. // A stack of state functions used to maintain context. - // The idea is to reuse parts of the state machine in various places. - // For example, values can appear at the top level or within arbitrarily - // nested arrays. The last state on the stack is used after a value has - // been lexed. Similarly for comments. + // + // The idea is to reuse parts of the state machine in various places. For + // example, values can appear at the top level or within arbitrarily nested + // arrays. The last state on the stack is used after a value has been lexed. + // Similarly for comments. stack []stateFn } type item struct { - typ itemType - val string - line int + typ itemType + val string + err error + pos Position } func (lx *lexer) nextItem() item { @@ -93,6 +82,7 @@ func (lx *lexer) nextItem() item { return item default: lx.state = lx.state(lx) + //fmt.Printf(" STATE %-24s current: %-10s stack: %s\n", lx.state, lx.current(), lx.stack) } } } @@ -101,9 +91,9 @@ func lex(input string) *lexer { lx := &lexer{ input: input, state: lexTop, - line: 1, items: make(chan item, 10), stack: make([]stateFn, 0, 10), + line: 1, } return lx } @@ -125,19 +115,36 @@ func (lx *lexer) current() string { return lx.input[lx.start:lx.pos] } +func (lx lexer) getPos() Position { + p := Position{ + Line: lx.line, + Start: lx.start, + Len: lx.pos - lx.start, + } + if p.Len <= 0 { + p.Len = 1 + } + return p +} + func (lx *lexer) emit(typ itemType) { - lx.items <- item{typ, lx.current(), lx.line} + // Needed for multiline strings ending with an incomplete UTF-8 sequence. + if lx.start > lx.pos { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return + } + lx.items <- item{typ: typ, pos: lx.getPos(), val: lx.current()} lx.start = lx.pos } func (lx *lexer) emitTrim(typ itemType) { - lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line} + lx.items <- item{typ: typ, pos: lx.getPos(), val: strings.TrimSpace(lx.current())} lx.start = lx.pos } func (lx *lexer) next() (r rune) { if lx.atEOF { - panic("next called after EOF") + panic("BUG in lexer: next called after EOF") } if lx.pos >= len(lx.input) { lx.atEOF = true @@ -147,12 +154,25 @@ func (lx *lexer) next() (r rune) { if lx.input[lx.pos] == '\n' { lx.line++ } + lx.prevWidths[3] = lx.prevWidths[2] lx.prevWidths[2] = lx.prevWidths[1] lx.prevWidths[1] = lx.prevWidths[0] - if lx.nprev < 3 { + if lx.nprev < 4 { lx.nprev++ } + r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) + if r == utf8.RuneError { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return utf8.RuneError + } + + // Note: don't use peek() here, as this calls next(). + if isControl(r) || (r == '\r' && (len(lx.input)-1 == lx.pos || lx.input[lx.pos+1] != '\n')) { + lx.errorControlChar(r) + return utf8.RuneError + } + lx.prevWidths[0] = w lx.pos += w return r @@ -163,19 +183,21 @@ func (lx *lexer) ignore() { lx.start = lx.pos } -// backup steps back one rune. Can be called only twice between calls to next. +// backup steps back one rune. Can be called 4 times between calls to next. func (lx *lexer) backup() { if lx.atEOF { lx.atEOF = false return } if lx.nprev < 1 { - panic("backed up too far") + panic("BUG in lexer: backed up too far") } w := lx.prevWidths[0] lx.prevWidths[0] = lx.prevWidths[1] lx.prevWidths[1] = lx.prevWidths[2] + lx.prevWidths[2] = lx.prevWidths[3] lx.nprev-- + lx.pos -= w if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { lx.line-- @@ -211,18 +233,58 @@ func (lx *lexer) skip(pred func(rune) bool) { } } -// errorf stops all lexing by emitting an error and returning `nil`. +// error stops all lexing by emitting an error and returning `nil`. +// // Note that any value that is a character is escaped if it's a special // character (newlines, tabs, etc.). -func (lx *lexer) errorf(format string, values ...interface{}) stateFn { - lx.items <- item{ - itemError, - fmt.Sprintf(format, values...), - lx.line, +func (lx *lexer) error(err error) stateFn { + if lx.atEOF { + return lx.errorPrevLine(err) } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: err} return nil } +// errorfPrevline is like error(), but sets the position to the last column of +// the previous line. +// +// This is so that unexpected EOF or NL errors don't show on a new blank line. +func (lx *lexer) errorPrevLine(err error) stateFn { + pos := lx.getPos() + pos.Line-- + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorPos is like error(), but allows explicitly setting the position. +func (lx *lexer) errorPos(start, length int, err error) stateFn { + pos := lx.getPos() + pos.Start = start + pos.Len = length + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorf is like error, and creates a new error. +func (lx *lexer) errorf(format string, values ...interface{}) stateFn { + if lx.atEOF { + pos := lx.getPos() + pos.Line-- + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: fmt.Errorf(format, values...)} + return nil + } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: fmt.Errorf(format, values...)} + return nil +} + +func (lx *lexer) errorControlChar(cc rune) stateFn { + return lx.errorPos(lx.pos-1, 1, errLexControl{cc}) +} + // lexTop consumes elements at the top level of TOML data. func lexTop(lx *lexer) stateFn { r := lx.next() @@ -230,10 +292,10 @@ func lexTop(lx *lexer) stateFn { return lexSkip(lx, lexTop) } switch r { - case commentStart: + case '#': lx.push(lexTop) return lexCommentStart - case tableStart: + case '[': return lexTableStart case eof: if lx.pos > lx.start { @@ -256,7 +318,7 @@ func lexTop(lx *lexer) stateFn { func lexTopEnd(lx *lexer) stateFn { r := lx.next() switch { - case r == commentStart: + case r == '#': // a comment will read to a newline for us. lx.push(lexTop) return lexCommentStart @@ -269,8 +331,9 @@ func lexTopEnd(lx *lexer) stateFn { lx.emit(itemEOF) return nil } - return lx.errorf("expected a top-level item to end with a newline, "+ - "comment, or EOF, but got %q instead", r) + return lx.errorf( + "expected a top-level item to end with a newline, comment, or EOF, but got %q instead", + r) } // lexTable lexes the beginning of a table. Namely, it makes sure that @@ -279,7 +342,7 @@ func lexTopEnd(lx *lexer) stateFn { // It also handles the case that this is an item in an array of tables. // e.g., '[[name]]'. func lexTableStart(lx *lexer) stateFn { - if lx.peek() == arrayTableStart { + if lx.peek() == '[' { lx.next() lx.emit(itemArrayTableStart) lx.push(lexArrayTableEnd) @@ -296,9 +359,8 @@ func lexTableEnd(lx *lexer) stateFn { } func lexArrayTableEnd(lx *lexer) stateFn { - if r := lx.next(); r != arrayTableEnd { - return lx.errorf("expected end of table array name delimiter %q, "+ - "but got %q instead", arrayTableEnd, r) + if r := lx.next(); r != ']' { + return lx.errorf("expected end of table array name delimiter ']', but got %q instead", r) } lx.emit(itemArrayTableEnd) return lexTopEnd @@ -307,33 +369,20 @@ func lexArrayTableEnd(lx *lexer) stateFn { func lexTableNameStart(lx *lexer) stateFn { lx.skip(isWhitespace) switch r := lx.peek(); { - case r == tableEnd || r == eof: - return lx.errorf("unexpected end of table name " + - "(table names cannot be empty)") - case r == tableSep: - return lx.errorf("unexpected table separator " + - "(table names cannot be empty)") - case r == stringStart || r == rawStringStart: + case r == ']' || r == eof: + return lx.errorf("unexpected end of table name (table names cannot be empty)") + case r == '.': + return lx.errorf("unexpected table separator (table names cannot be empty)") + case r == '"' || r == '\'': lx.ignore() lx.push(lexTableNameEnd) - return lexValue // reuse string lexing + return lexQuotedName default: - return lexBareTableName + lx.push(lexTableNameEnd) + return lexBareName } } -// lexBareTableName lexes the name of a table. It assumes that at least one -// valid character for the table has already been read. -func lexBareTableName(lx *lexer) stateFn { - r := lx.next() - if isBareKeyChar(r) { - return lexBareTableName - } - lx.backup() - lx.emit(itemText) - return lexTableNameEnd -} - // lexTableNameEnd reads the end of a piece of a table name, optionally // consuming whitespace. func lexTableNameEnd(lx *lexer) stateFn { @@ -341,69 +390,107 @@ func lexTableNameEnd(lx *lexer) stateFn { switch r := lx.next(); { case isWhitespace(r): return lexTableNameEnd - case r == tableSep: + case r == '.': lx.ignore() return lexTableNameStart - case r == tableEnd: + case r == ']': return lx.pop() default: - return lx.errorf("expected '.' or ']' to end table name, "+ - "but got %q instead", r) + return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r) } } -// lexKeyStart consumes a key name up until the first non-whitespace character. -// lexKeyStart will ignore whitespace. -func lexKeyStart(lx *lexer) stateFn { - r := lx.peek() +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only 'a' inside 'a.b'. +func lexBareName(lx *lexer) stateFn { + r := lx.next() + if isBareKeyChar(r) { + return lexBareName + } + lx.backup() + lx.emit(itemText) + return lx.pop() +} + +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only '"a"' inside '"a".b'. +func lexQuotedName(lx *lexer) stateFn { + r := lx.next() switch { - case r == keySep: - return lx.errorf("unexpected key separator %q", keySep) - case isWhitespace(r) || isNL(r): - lx.next() - return lexSkip(lx, lexKeyStart) - case r == stringStart || r == rawStringStart: - lx.ignore() - lx.emit(itemKeyStart) - lx.push(lexKeyEnd) - return lexValue // reuse string lexing + case isWhitespace(r): + return lexSkip(lx, lexValue) + case r == '"': + lx.ignore() // ignore the '"' + return lexString + case r == '\'': + lx.ignore() // ignore the "'" + return lexRawString + case r == eof: + return lx.errorf("unexpected EOF; expected value") default: - lx.ignore() - lx.emit(itemKeyStart) - return lexBareKey + return lx.errorf("expected value but found %q instead", r) } } -// lexBareKey consumes the text of a bare key. Assumes that the first character -// (which is not whitespace) has not yet been consumed. -func lexBareKey(lx *lexer) stateFn { - switch r := lx.next(); { - case isBareKeyChar(r): - return lexBareKey - case isWhitespace(r): - lx.backup() - lx.emit(itemText) - return lexKeyEnd - case r == keySep: - lx.backup() - lx.emit(itemText) - return lexKeyEnd +// lexKeyStart consumes all key parts until a '='. +func lexKeyStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '=': key name appears blank") + case r == '.': + return lx.errorf("unexpected '.': keys cannot start with a '.'") + case r == '"' || r == '\'': + lx.ignore() + fallthrough + default: // Bare key + lx.emit(itemKeyStart) + return lexKeyNameStart + } +} + +func lexKeyNameStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '='") + case r == '.': + return lx.errorf("unexpected '.'") + case r == '"' || r == '\'': + lx.ignore() + lx.push(lexKeyEnd) + return lexQuotedName default: - return lx.errorf("bare keys cannot contain %q", r) + lx.push(lexKeyEnd) + return lexBareName } } // lexKeyEnd consumes the end of a key and trims whitespace (up to the key // separator). func lexKeyEnd(lx *lexer) stateFn { + lx.skip(isWhitespace) switch r := lx.next(); { - case r == keySep: - return lexSkip(lx, lexValue) case isWhitespace(r): return lexSkip(lx, lexKeyEnd) + case r == eof: + return lx.errorf("unexpected EOF; expected key separator '='") + case r == '.': + lx.ignore() + return lexKeyNameStart + case r == '=': + lx.emit(itemKeyEnd) + return lexSkip(lx, lexValue) default: - return lx.errorf("expected key separator %q, but got %q instead", - keySep, r) + return lx.errorf("expected '.' or '=', but got %q instead", r) } } @@ -422,17 +509,17 @@ func lexValue(lx *lexer) stateFn { return lexNumberOrDateStart } switch r { - case arrayStart: + case '[': lx.ignore() lx.emit(itemArray) return lexArrayValue - case inlineTableStart: + case '{': lx.ignore() lx.emit(itemInlineTableStart) return lexInlineTableValue - case stringStart: - if lx.accept(stringStart) { - if lx.accept(stringStart) { + case '"': + if lx.accept('"') { + if lx.accept('"') { lx.ignore() // Ignore """ return lexMultilineString } @@ -440,9 +527,9 @@ func lexValue(lx *lexer) stateFn { } lx.ignore() // ignore the '"' return lexString - case rawStringStart: - if lx.accept(rawStringStart) { - if lx.accept(rawStringStart) { + case '\'': + if lx.accept('\'') { + if lx.accept('\'') { lx.ignore() // Ignore """ return lexMultilineRawString } @@ -450,10 +537,15 @@ func lexValue(lx *lexer) stateFn { } lx.ignore() // ignore the "'" return lexRawString - case '+', '-': - return lexNumberStart case '.': // special error case, be kind to users return lx.errorf("floats must start with a digit, not '.'") + case 'i', 'n': + if (lx.accept('n') && lx.accept('f')) || (lx.accept('a') && lx.accept('n')) { + lx.emit(itemFloat) + return lx.pop() + } + case '-', '+': + return lexDecimalNumberStart } if unicode.IsLetter(r) { // Be permissive here; lexBool will give a nice error if the @@ -463,6 +555,9 @@ func lexValue(lx *lexer) stateFn { lx.backup() return lexBool } + if r == eof { + return lx.errorf("unexpected EOF; expected value") + } return lx.errorf("expected value but found %q instead", r) } @@ -473,14 +568,12 @@ func lexArrayValue(lx *lexer) stateFn { switch { case isWhitespace(r) || isNL(r): return lexSkip(lx, lexArrayValue) - case r == commentStart: + case r == '#': lx.push(lexArrayValue) return lexCommentStart - case r == comma: + case r == ',': return lx.errorf("unexpected comma") - case r == arrayEnd: - // NOTE(caleb): The spec isn't clear about whether you can have - // a trailing comma or not, so we'll allow it. + case r == ']': return lexArrayEnd } @@ -493,23 +586,20 @@ func lexArrayValue(lx *lexer) stateFn { // the next value (or the end of the array): it ignores whitespace and newlines // and expects either a ',' or a ']'. func lexArrayValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { + switch r := lx.next(); { case isWhitespace(r) || isNL(r): return lexSkip(lx, lexArrayValueEnd) - case r == commentStart: + case r == '#': lx.push(lexArrayValueEnd) return lexCommentStart - case r == comma: + case r == ',': lx.ignore() return lexArrayValue // move on to the next value - case r == arrayEnd: + case r == ']': return lexArrayEnd + default: + return lx.errorf("expected a comma (',') or array terminator (']'), but got %s", runeOrEOF(r)) } - return lx.errorf( - "expected a comma or array terminator %q, but got %q instead", - arrayEnd, r, - ) } // lexArrayEnd finishes the lexing of an array. @@ -528,13 +618,13 @@ func lexInlineTableValue(lx *lexer) stateFn { case isWhitespace(r): return lexSkip(lx, lexInlineTableValue) case isNL(r): - return lx.errorf("newlines not allowed within inline tables") - case r == commentStart: + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': lx.push(lexInlineTableValue) return lexCommentStart - case r == comma: + case r == ',': return lx.errorf("unexpected comma") - case r == inlineTableEnd: + case r == '}': return lexInlineTableEnd } lx.backup() @@ -546,23 +636,33 @@ func lexInlineTableValue(lx *lexer) stateFn { // key/value pair and the next pair (or the end of the table): // it ignores whitespace and expects either a ',' or a '}'. func lexInlineTableValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { + switch r := lx.next(); { case isWhitespace(r): return lexSkip(lx, lexInlineTableValueEnd) case isNL(r): - return lx.errorf("newlines not allowed within inline tables") - case r == commentStart: + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': lx.push(lexInlineTableValueEnd) return lexCommentStart - case r == comma: + case r == ',': lx.ignore() + lx.skip(isWhitespace) + if lx.peek() == '}' { + return lx.errorf("trailing comma not allowed in inline tables") + } return lexInlineTableValue - case r == inlineTableEnd: + case r == '}': return lexInlineTableEnd + default: + return lx.errorf("expected a comma or an inline table terminator '}', but got %s instead", runeOrEOF(r)) } - return lx.errorf("expected a comma or an inline table terminator %q, "+ - "but got %q instead", inlineTableEnd, r) +} + +func runeOrEOF(r rune) string { + if r == eof { + return "end of file" + } + return "'" + string(r) + "'" } // lexInlineTableEnd finishes the lexing of an inline table. @@ -579,13 +679,13 @@ func lexString(lx *lexer) stateFn { r := lx.next() switch { case r == eof: - return lx.errorf("unexpected EOF") + return lx.errorf(`unexpected EOF; expected '"'`) case isNL(r): - return lx.errorf("strings cannot contain newlines") + return lx.errorPrevLine(errLexStringNL{}) case r == '\\': lx.push(lexString) return lexStringEscape - case r == stringEnd: + case r == '"': lx.backup() lx.emit(itemString) lx.next() @@ -598,19 +698,47 @@ func lexString(lx *lexer) stateFn { // lexMultilineString consumes the inner contents of a string. It assumes that // the beginning '"""' has already been consumed and ignored. func lexMultilineString(lx *lexer) stateFn { - switch lx.next() { + r := lx.next() + switch r { + default: + return lexMultilineString case eof: - return lx.errorf("unexpected EOF") + return lx.errorf(`unexpected EOF; expected '"""'`) case '\\': return lexMultilineStringEscape - case stringEnd: - if lx.accept(stringEnd) { - if lx.accept(stringEnd) { - lx.backup() + case '"': + /// Found " → try to read two more "". + if lx.accept('"') { + if lx.accept('"') { + /// Peek ahead: the string can contain " and "", including at the + /// end: """str""""" + /// 6 or more at the end, however, is an error. + if lx.peek() == '"' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + /// + /// Second check is for the edge case: + /// + /// two quotes allowed. + /// vv + /// """lol \"""""" + /// ^^ ^^^---- closing three + /// escaped + /// + /// But ugly, but it works + if strings.HasSuffix(lx.current(), `"""""`) && !strings.HasSuffix(lx.current(), `\"""""`) { + return lx.errorf(`unexpected '""""""'`) + } + lx.backup() + lx.backup() + return lexMultilineString + } + + lx.backup() /// backup: don't include the """ in the item. lx.backup() lx.backup() lx.emit(itemMultilineString) - lx.next() + lx.next() /// Read over ''' again and discard it. lx.next() lx.next() lx.ignore() @@ -618,8 +746,8 @@ func lexMultilineString(lx *lexer) stateFn { } lx.backup() } + return lexMultilineString } - return lexMultilineString } // lexRawString consumes a raw string. Nothing can be escaped in such a string. @@ -627,35 +755,54 @@ func lexMultilineString(lx *lexer) stateFn { func lexRawString(lx *lexer) stateFn { r := lx.next() switch { + default: + return lexRawString case r == eof: - return lx.errorf("unexpected EOF") + return lx.errorf(`unexpected EOF; expected "'"`) case isNL(r): - return lx.errorf("strings cannot contain newlines") - case r == rawStringEnd: + return lx.errorPrevLine(errLexStringNL{}) + case r == '\'': lx.backup() lx.emit(itemRawString) lx.next() lx.ignore() return lx.pop() } - return lexRawString } // lexMultilineRawString consumes a raw string. Nothing can be escaped in such // a string. It assumes that the beginning "'''" has already been consumed and // ignored. func lexMultilineRawString(lx *lexer) stateFn { - switch lx.next() { + r := lx.next() + switch r { + default: + return lexMultilineRawString case eof: - return lx.errorf("unexpected EOF") - case rawStringEnd: - if lx.accept(rawStringEnd) { - if lx.accept(rawStringEnd) { - lx.backup() + return lx.errorf(`unexpected EOF; expected "'''"`) + case '\'': + /// Found ' → try to read two more ''. + if lx.accept('\'') { + if lx.accept('\'') { + /// Peek ahead: the string can contain ' and '', including at the + /// end: '''str''''' + /// 6 or more at the end, however, is an error. + if lx.peek() == '\'' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + if strings.HasSuffix(lx.current(), "'''''") { + return lx.errorf(`unexpected "''''''"`) + } + lx.backup() + lx.backup() + return lexMultilineRawString + } + + lx.backup() /// backup: don't include the ''' in the item. lx.backup() lx.backup() lx.emit(itemRawMultilineString) - lx.next() + lx.next() /// Read over ''' again and discard it. lx.next() lx.next() lx.ignore() @@ -663,15 +810,14 @@ func lexMultilineRawString(lx *lexer) stateFn { } lx.backup() } + return lexMultilineRawString } - return lexMultilineRawString } // lexMultilineStringEscape consumes an escaped character. It assumes that the // preceding '\\' has already been consumed. func lexMultilineStringEscape(lx *lexer) stateFn { - // Handle the special case first: - if isNL(lx.next()) { + if isNL(lx.next()) { /// \ escaping newline. return lexMultilineString } lx.backup() @@ -694,6 +840,10 @@ func lexStringEscape(lx *lexer) stateFn { fallthrough case '"': fallthrough + case ' ', '\t': + // Inside """ .. """ strings you can use \ to escape newlines, and any + // amount of whitespace can be between the \ and \n. + fallthrough case '\\': return lx.pop() case 'u': @@ -701,9 +851,7 @@ func lexStringEscape(lx *lexer) stateFn { case 'U': return lexLongUnicodeEscape } - return lx.errorf("invalid escape character %q; only the following "+ - "escape characters are allowed: "+ - `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) + return lx.error(errLexEscape{r}) } func lexShortUnicodeEscape(lx *lexer) stateFn { @@ -711,8 +859,9 @@ func lexShortUnicodeEscape(lx *lexer) stateFn { for i := 0; i < 4; i++ { r = lx.next() if !isHexadecimal(r) { - return lx.errorf(`expected four hexadecimal digits after '\u', `+ - "but got %q instead", lx.current()) + return lx.errorf( + `expected four hexadecimal digits after '\u', but got %q instead`, + lx.current()) } } return lx.pop() @@ -723,28 +872,33 @@ func lexLongUnicodeEscape(lx *lexer) stateFn { for i := 0; i < 8; i++ { r = lx.next() if !isHexadecimal(r) { - return lx.errorf(`expected eight hexadecimal digits after '\U', `+ - "but got %q instead", lx.current()) + return lx.errorf( + `expected eight hexadecimal digits after '\U', but got %q instead`, + lx.current()) } } return lx.pop() } -// lexNumberOrDateStart consumes either an integer, a float, or datetime. +// lexNumberOrDateStart processes the first character of a value which begins +// with a digit. It exists to catch values starting with '0', so that +// lexBaseNumberOrDate can differentiate base prefixed integers from other +// types. func lexNumberOrDateStart(lx *lexer) stateFn { r := lx.next() - if isDigit(r) { - return lexNumberOrDate - } switch r { - case '_': - return lexNumber - case 'e', 'E': - return lexFloat - case '.': - return lx.errorf("floats must start with a digit, not '.'") + case '0': + return lexBaseNumberOrDate } - return lx.errorf("expected a digit but got %q", r) + + if !isDigit(r) { + // The only way to reach this state is if the value starts + // with a digit, so specifically treat anything else as an + // error. + return lx.errorf("expected a digit but got %q", r) + } + + return lexNumberOrDate } // lexNumberOrDate consumes either an integer, float or datetime. @@ -754,10 +908,10 @@ func lexNumberOrDate(lx *lexer) stateFn { return lexNumberOrDate } switch r { - case '-': + case '-', ':': return lexDatetime case '_': - return lexNumber + return lexDecimalNumber case '.', 'e', 'E': return lexFloat } @@ -775,41 +929,156 @@ func lexDatetime(lx *lexer) stateFn { return lexDatetime } switch r { - case '-', 'T', ':', '.', 'Z', '+': + case '-', ':', 'T', 't', ' ', '.', 'Z', 'z', '+': return lexDatetime } lx.backup() - lx.emit(itemDatetime) + lx.emitTrim(itemDatetime) return lx.pop() } -// lexNumberStart consumes either an integer or a float. It assumes that a sign -// has already been read, but that *no* digits have been consumed. -// lexNumberStart will move to the appropriate integer or float states. -func lexNumberStart(lx *lexer) stateFn { - // We MUST see a digit. Even floats have to start with a digit. +// lexHexInteger consumes a hexadecimal integer after seeing the '0x' prefix. +func lexHexInteger(lx *lexer) stateFn { r := lx.next() - if !isDigit(r) { - if r == '.' { - return lx.errorf("floats must start with a digit, not '.'") - } - return lx.errorf("expected a digit but got %q", r) - } - return lexNumber -} - -// lexNumber consumes an integer or a float after seeing the first digit. -func lexNumber(lx *lexer) stateFn { - r := lx.next() - if isDigit(r) { - return lexNumber + if isHexadecimal(r) { + return lexHexInteger } switch r { case '_': - return lexNumber + return lexHexInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexOctalInteger consumes an octal integer after seeing the '0o' prefix. +func lexOctalInteger(lx *lexer) stateFn { + r := lx.next() + if isOctal(r) { + return lexOctalInteger + } + switch r { + case '_': + return lexOctalInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexBinaryInteger consumes a binary integer after seeing the '0b' prefix. +func lexBinaryInteger(lx *lexer) stateFn { + r := lx.next() + if isBinary(r) { + return lexBinaryInteger + } + switch r { + case '_': + return lexBinaryInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes a decimal float or integer. +func lexDecimalNumber(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexDecimalNumber + } + switch r { case '.', 'e', 'E': return lexFloat + case '_': + return lexDecimalNumber + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes the first digit of a number beginning with a sign. +// It assumes the sign has already been consumed. Values which start with a sign +// are only allowed to be decimal integers or floats. +// +// The special "nan" and "inf" values are also recognized. +func lexDecimalNumberStart(lx *lexer) stateFn { + r := lx.next() + + // Special error cases to give users better error messages + switch r { + case 'i': + if !lx.accept('n') || !lx.accept('f') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case 'n': + if !lx.accept('a') || !lx.accept('n') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case '0': + p := lx.peek() + switch p { + case 'b', 'o', 'x': + return lx.errorf("cannot use sign with non-decimal numbers: '%s%c'", lx.current(), p) + } + case '.': + return lx.errorf("floats must start with a digit, not '.'") + } + + if isDigit(r) { + return lexDecimalNumber + } + + return lx.errorf("expected a digit but got %q", r) +} + +// lexBaseNumberOrDate differentiates between the possible values which +// start with '0'. It assumes that before reaching this state, the initial '0' +// has been consumed. +func lexBaseNumberOrDate(lx *lexer) stateFn { + r := lx.next() + // Note: All datetimes start with at least two digits, so we don't + // handle date characters (':', '-', etc.) here. + if isDigit(r) { + return lexNumberOrDate + } + switch r { + case '_': + // Can only be decimal, because there can't be an underscore + // between the '0' and the base designator, and dates can't + // contain underscores. + return lexDecimalNumber + case '.', 'e', 'E': + return lexFloat + case 'b': + r = lx.peek() + if !isBinary(r) { + lx.errorf("not a binary number: '%s%c'", lx.current(), r) + } + return lexBinaryInteger + case 'o': + r = lx.peek() + if !isOctal(r) { + lx.errorf("not an octal number: '%s%c'", lx.current(), r) + } + return lexOctalInteger + case 'x': + r = lx.peek() + if !isHexadecimal(r) { + lx.errorf("not a hexidecimal number: '%s%c'", lx.current(), r) + } + return lexHexInteger } lx.backup() @@ -867,49 +1136,31 @@ func lexCommentStart(lx *lexer) stateFn { // It will consume *up to* the first newline character, and pass control // back to the last state on the stack. func lexComment(lx *lexer) stateFn { - r := lx.peek() - if isNL(r) || r == eof { + switch r := lx.next(); { + case isNL(r) || r == eof: + lx.backup() lx.emit(itemText) return lx.pop() + default: + return lexComment } - lx.next() - return lexComment } // lexSkip ignores all slurped input and moves on to the next state. func lexSkip(lx *lexer, nextState stateFn) stateFn { - return func(lx *lexer) stateFn { - lx.ignore() - return nextState + lx.ignore() + return nextState +} + +func (s stateFn) String() string { + name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name() + if i := strings.LastIndexByte(name, '.'); i > -1 { + name = name[i+1:] } -} - -// isWhitespace returns true if `r` is a whitespace character according -// to the spec. -func isWhitespace(r rune) bool { - return r == '\t' || r == ' ' -} - -func isNL(r rune) bool { - return r == '\n' || r == '\r' -} - -func isDigit(r rune) bool { - return r >= '0' && r <= '9' -} - -func isHexadecimal(r rune) bool { - return (r >= '0' && r <= '9') || - (r >= 'a' && r <= 'f') || - (r >= 'A' && r <= 'F') -} - -func isBareKeyChar(r rune) bool { - return (r >= 'A' && r <= 'Z') || - (r >= 'a' && r <= 'z') || - (r >= '0' && r <= '9') || - r == '_' || - r == '-' + if s == nil { + name = "" + } + return name + "()" } func (itype itemType) String() string { @@ -938,12 +1189,18 @@ func (itype itemType) String() string { return "TableEnd" case itemKeyStart: return "KeyStart" + case itemKeyEnd: + return "KeyEnd" case itemArray: return "Array" case itemArrayEnd: return "ArrayEnd" case itemCommentStart: return "CommentStart" + case itemInlineTableStart: + return "InlineTableStart" + case itemInlineTableEnd: + return "InlineTableEnd" } panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) } @@ -951,3 +1208,26 @@ func (itype itemType) String() string { func (item item) String() string { return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val) } + +func isWhitespace(r rune) bool { return r == '\t' || r == ' ' } +func isNL(r rune) bool { return r == '\n' || r == '\r' } +func isControl(r rune) bool { // Control characters except \t, \r, \n + switch r { + case '\t', '\r', '\n': + return false + default: + return (r >= 0x00 && r <= 0x1f) || r == 0x7f + } +} +func isDigit(r rune) bool { return r >= '0' && r <= '9' } +func isBinary(r rune) bool { return r == '0' || r == '1' } +func isOctal(r rune) bool { return r >= '0' && r <= '7' } +func isHexadecimal(r rune) bool { + return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') +} +func isBareKeyChar(r rune) bool { + return (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || + r == '_' || r == '-' +} diff --git a/vendor/github.com/BurntSushi/toml/decode_meta.go b/vendor/github.com/BurntSushi/toml/meta.go similarity index 62% rename from vendor/github.com/BurntSushi/toml/decode_meta.go rename to vendor/github.com/BurntSushi/toml/meta.go index b9914a67..d284f2a0 100644 --- a/vendor/github.com/BurntSushi/toml/decode_meta.go +++ b/vendor/github.com/BurntSushi/toml/meta.go @@ -1,33 +1,40 @@ package toml -import "strings" +import ( + "strings" +) -// MetaData allows access to meta information about TOML data that may not -// be inferrable via reflection. In particular, whether a key has been defined -// and the TOML type of a key. +// MetaData allows access to meta information about TOML data that's not +// accessible otherwise. +// +// It allows checking if a key is defined in the TOML data, whether any keys +// were undecoded, and the TOML type of a key. type MetaData struct { - mapping map[string]interface{} - types map[string]tomlType - keys []Key - decoded map[string]bool context Key // Used only during decoding. + + keyInfo map[string]keyInfo + mapping map[string]interface{} + keys []Key + decoded map[string]struct{} + data []byte // Input file; for errors. } -// IsDefined returns true if the key given exists in the TOML data. The key -// should be specified hierarchially. e.g., +// IsDefined reports if the key exists in the TOML data. // -// // access the TOML key 'a.b.c' -// IsDefined("a", "b", "c") +// The key should be specified hierarchically, for example to access the TOML +// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive. // -// IsDefined will return false if an empty key given. Keys are case sensitive. +// Returns false for an empty key. func (md *MetaData) IsDefined(key ...string) bool { if len(key) == 0 { return false } - var hash map[string]interface{} - var ok bool - var hashOrVal interface{} = md.mapping + var ( + hash map[string]interface{} + ok bool + hashOrVal interface{} = md.mapping + ) for _, k := range key { if hash, ok = hashOrVal.(map[string]interface{}); !ok { return false @@ -41,58 +48,20 @@ func (md *MetaData) IsDefined(key ...string) bool { // Type returns a string representation of the type of the key specified. // -// Type will return the empty string if given an empty key or a key that -// does not exist. Keys are case sensitive. +// Type will return the empty string if given an empty key or a key that does +// not exist. Keys are case sensitive. func (md *MetaData) Type(key ...string) string { - fullkey := strings.Join(key, ".") - if typ, ok := md.types[fullkey]; ok { - return typ.typeString() + if ki, ok := md.keyInfo[Key(key).String()]; ok { + return ki.tomlType.typeString() } return "" } -// Key is the type of any TOML key, including key groups. Use (MetaData).Keys -// to get values of this type. -type Key []string - -func (k Key) String() string { - return strings.Join(k, ".") -} - -func (k Key) maybeQuotedAll() string { - var ss []string - for i := range k { - ss = append(ss, k.maybeQuoted(i)) - } - return strings.Join(ss, ".") -} - -func (k Key) maybeQuoted(i int) string { - quote := false - for _, c := range k[i] { - if !isBareKeyChar(c) { - quote = true - break - } - } - if quote { - return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" - } - return k[i] -} - -func (k Key) add(piece string) Key { - newKey := make(Key, len(k)+1) - copy(newKey, k) - newKey[len(k)] = piece - return newKey -} - // Keys returns a slice of every key in the TOML data, including key groups. -// Each key is itself a slice, where the first element is the top of the -// hierarchy and the last is the most specific. // -// The list will have the same order as the keys appeared in the TOML data. +// Each key is itself a slice, where the first element is the top of the +// hierarchy and the last is the most specific. The list will have the same +// order as the keys appeared in the TOML data. // // All keys returned are non-empty. func (md *MetaData) Keys() []Key { @@ -113,9 +82,40 @@ func (md *MetaData) Keys() []Key { func (md *MetaData) Undecoded() []Key { undecoded := make([]Key, 0, len(md.keys)) for _, key := range md.keys { - if !md.decoded[key.String()] { + if _, ok := md.decoded[key.String()]; !ok { undecoded = append(undecoded, key) } } return undecoded } + +// Key represents any TOML key, including key groups. Use (MetaData).Keys to get +// values of this type. +type Key []string + +func (k Key) String() string { + ss := make([]string, len(k)) + for i := range k { + ss[i] = k.maybeQuoted(i) + } + return strings.Join(ss, ".") +} + +func (k Key) maybeQuoted(i int) string { + if k[i] == "" { + return `""` + } + for _, c := range k[i] { + if !isBareKeyChar(c) { + return `"` + dblQuotedReplacer.Replace(k[i]) + `"` + } + } + return k[i] +} + +func (k Key) add(piece string) Key { + newKey := make(Key, len(k)+1) + copy(newKey, k) + newKey[len(k)] = piece + return newKey +} diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go index 50869ef9..d2542d6f 100644 --- a/vendor/github.com/BurntSushi/toml/parse.go +++ b/vendor/github.com/BurntSushi/toml/parse.go @@ -5,54 +5,69 @@ import ( "strconv" "strings" "time" - "unicode" "unicode/utf8" + + "github.com/BurntSushi/toml/internal" ) type parser struct { - mapping map[string]interface{} - types map[string]tomlType - lx *lexer + lx *lexer + context Key // Full key for the current hash in scope. + currentKey string // Base key name for everything except hashes. + pos Position // Current position in the TOML file. - // A list of keys in the order that they appear in the TOML data. - ordered []Key + ordered []Key // List of keys in the order that they appear in the TOML data. - // the full key for the current hash in scope - context Key - - // the base key name for everything except hashes - currentKey string - - // rough approximation of line number - approxLine int - - // A map of 'key.group.names' to whether they were created implicitly. - implicits map[string]bool + keyInfo map[string]keyInfo // Map keyname → info about the TOML key. + mapping map[string]interface{} // Map keyname → key value. + implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names"). } -type parseError string - -func (pe parseError) Error() string { - return string(pe) +type keyInfo struct { + pos Position + tomlType tomlType } func parse(data string) (p *parser, err error) { defer func() { if r := recover(); r != nil { - var ok bool - if err, ok = r.(parseError); ok { + if pErr, ok := r.(ParseError); ok { + pErr.input = data + err = pErr return } panic(r) } }() + // Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString() + // which mangles stuff. + if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { + data = data[2:] + } + + // Examine first few bytes for NULL bytes; this probably means it's a UTF-16 + // file (second byte in surrogate pair being NULL). Again, do this here to + // avoid having to deal with UTF-8/16 stuff in the lexer. + ex := 6 + if len(data) < 6 { + ex = len(data) + } + if i := strings.IndexRune(data[:ex], 0); i > -1 { + return nil, ParseError{ + Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8", + Position: Position{Line: 1, Start: i, Len: 1}, + Line: 1, + input: data, + } + } + p = &parser{ + keyInfo: make(map[string]keyInfo), mapping: make(map[string]interface{}), - types: make(map[string]tomlType), lx: lex(data), ordered: make([]Key, 0), - implicits: make(map[string]bool), + implicits: make(map[string]struct{}), } for { item := p.next() @@ -65,20 +80,57 @@ func parse(data string) (p *parser, err error) { return p, nil } +func (p *parser) panicErr(it item, err error) { + panic(ParseError{ + err: err, + Position: it.pos, + Line: it.pos.Len, + LastKey: p.current(), + }) +} + +func (p *parser) panicItemf(it item, format string, v ...interface{}) { + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: it.pos, + Line: it.pos.Len, + LastKey: p.current(), + }) +} + func (p *parser) panicf(format string, v ...interface{}) { - msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", - p.approxLine, p.current(), fmt.Sprintf(format, v...)) - panic(parseError(msg)) + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: p.pos, + Line: p.pos.Line, + LastKey: p.current(), + }) } func (p *parser) next() item { it := p.lx.nextItem() + //fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val) if it.typ == itemError { - p.panicf("%s", it.val) + if it.err != nil { + panic(ParseError{ + Position: it.pos, + Line: it.pos.Line, + LastKey: p.current(), + err: it.err, + }) + } + + p.panicItemf(it, "%s", it.val) } return it } +func (p *parser) nextPos() item { + it := p.next() + p.pos = it.pos + return it +} + func (p *parser) bug(format string, v ...interface{}) { panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) } @@ -97,44 +149,60 @@ func (p *parser) assertEqual(expected, got itemType) { func (p *parser) topLevel(item item) { switch item.typ { - case itemCommentStart: - p.approxLine = item.line + case itemCommentStart: // # .. p.expect(itemText) - case itemTableStart: - kg := p.next() - p.approxLine = kg.line + case itemTableStart: // [ .. ] + name := p.nextPos() var key Key - for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { - key = append(key, p.keyString(kg)) + for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) } - p.assertEqual(itemTableEnd, kg.typ) + p.assertEqual(itemTableEnd, name.typ) - p.establishContext(key, false) - p.setType("", tomlHash) + p.addContext(key, false) + p.setType("", tomlHash, item.pos) p.ordered = append(p.ordered, key) - case itemArrayTableStart: - kg := p.next() - p.approxLine = kg.line + case itemArrayTableStart: // [[ .. ]] + name := p.nextPos() var key Key - for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { - key = append(key, p.keyString(kg)) + for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) } - p.assertEqual(itemArrayTableEnd, kg.typ) + p.assertEqual(itemArrayTableEnd, name.typ) - p.establishContext(key, true) - p.setType("", tomlArrayHash) + p.addContext(key, true) + p.setType("", tomlArrayHash, item.pos) p.ordered = append(p.ordered, key) - case itemKeyStart: - kname := p.next() - p.approxLine = kname.line - p.currentKey = p.keyString(kname) + case itemKeyStart: // key = .. + outerContext := p.context + /// Read all the key parts (e.g. 'a' and 'b' in 'a.b') + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) - val, typ := p.value(p.next()) - p.setValue(p.currentKey, val) - p.setType(p.currentKey, typ) + /// The current key is the last part. + p.currentKey = key[len(key)-1] + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key[:len(key)-1] + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + + /// Set value. + vItem := p.next() + val, typ := p.value(vItem, false) + p.set(p.currentKey, val, typ, vItem.pos) p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + /// Remove the context we added (preserving any context from [tbl] lines). + p.context = outerContext p.currentKey = "" default: p.bug("Unexpected type at top level: %s", item.typ) @@ -148,180 +216,261 @@ func (p *parser) keyString(it item) string { return it.val case itemString, itemMultilineString, itemRawString, itemRawMultilineString: - s, _ := p.value(it) + s, _ := p.value(it, false) return s.(string) default: p.bug("Unexpected key type: %s", it.typ) - panic("unreachable") } + panic("unreachable") } +var datetimeRepl = strings.NewReplacer( + "z", "Z", + "t", "T", + " ", "T") + // value translates an expected value from the lexer into a Go value wrapped // as an empty interface. -func (p *parser) value(it item) (interface{}, tomlType) { +func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) { switch it.typ { case itemString: - return p.replaceEscapes(it.val), p.typeOfPrimitive(it) + return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it) case itemMultilineString: - trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) - return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) + return p.replaceEscapes(it, stripFirstNewline(p.stripEscapedNewlines(it.val))), p.typeOfPrimitive(it) case itemRawString: return it.val, p.typeOfPrimitive(it) case itemRawMultilineString: return stripFirstNewline(it.val), p.typeOfPrimitive(it) + case itemInteger: + return p.valueInteger(it) + case itemFloat: + return p.valueFloat(it) case itemBool: switch it.val { case "true": return true, p.typeOfPrimitive(it) case "false": return false, p.typeOfPrimitive(it) + default: + p.bug("Expected boolean value, but got '%s'.", it.val) } - p.bug("Expected boolean value, but got '%s'.", it.val) - case itemInteger: - if !numUnderscoresOK(it.val) { - p.panicf("Invalid integer %q: underscores must be surrounded by digits", - it.val) - } - val := strings.Replace(it.val, "_", "", -1) - num, err := strconv.ParseInt(val, 10, 64) - if err != nil { - // Distinguish integer values. Normally, it'd be a bug if the lexer - // provides an invalid integer, but it's possible that the number is - // out of range of valid values (which the lexer cannot determine). - // So mark the former as a bug but the latter as a legitimate user - // error. - if e, ok := err.(*strconv.NumError); ok && - e.Err == strconv.ErrRange { - - p.panicf("Integer '%s' is out of the range of 64-bit "+ - "signed integers.", it.val) - } else { - p.bug("Expected integer value, but got '%s'.", it.val) - } - } - return num, p.typeOfPrimitive(it) - case itemFloat: - parts := strings.FieldsFunc(it.val, func(r rune) bool { - switch r { - case '.', 'e', 'E': - return true - } - return false - }) - for _, part := range parts { - if !numUnderscoresOK(part) { - p.panicf("Invalid float %q: underscores must be "+ - "surrounded by digits", it.val) - } - } - if !numPeriodsOK(it.val) { - // As a special case, numbers like '123.' or '1.e2', - // which are valid as far as Go/strconv are concerned, - // must be rejected because TOML says that a fractional - // part consists of '.' followed by 1+ digits. - p.panicf("Invalid float %q: '.' must be followed "+ - "by one or more digits", it.val) - } - val := strings.Replace(it.val, "_", "", -1) - num, err := strconv.ParseFloat(val, 64) - if err != nil { - if e, ok := err.(*strconv.NumError); ok && - e.Err == strconv.ErrRange { - - p.panicf("Float '%s' is out of the range of 64-bit "+ - "IEEE-754 floating-point numbers.", it.val) - } else { - p.panicf("Invalid float value: %q", it.val) - } - } - return num, p.typeOfPrimitive(it) case itemDatetime: - var t time.Time - var ok bool - var err error - for _, format := range []string{ - "2006-01-02T15:04:05Z07:00", - "2006-01-02T15:04:05", - "2006-01-02", - } { - t, err = time.ParseInLocation(format, it.val, time.Local) - if err == nil { - ok = true - break - } - } - if !ok { - p.panicf("Invalid TOML Datetime: %q.", it.val) - } - return t, p.typeOfPrimitive(it) + return p.valueDatetime(it) case itemArray: - array := make([]interface{}, 0) - types := make([]tomlType, 0) - - for it = p.next(); it.typ != itemArrayEnd; it = p.next() { - if it.typ == itemCommentStart { - p.expect(itemText) - continue - } - - val, typ := p.value(it) - array = append(array, val) - types = append(types, typ) - } - return array, p.typeOfArray(types) + return p.valueArray(it) case itemInlineTableStart: - var ( - hash = make(map[string]interface{}) - outerContext = p.context - outerKey = p.currentKey - ) - - p.context = append(p.context, p.currentKey) - p.currentKey = "" - for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { - if it.typ != itemKeyStart { - p.bug("Expected key start but instead found %q, around line %d", - it.val, p.approxLine) - } - if it.typ == itemCommentStart { - p.expect(itemText) - continue - } - - // retrieve key - k := p.next() - p.approxLine = k.line - kname := p.keyString(k) - - // retrieve value - p.currentKey = kname - val, typ := p.value(p.next()) - // make sure we keep metadata up to date - p.setType(kname, typ) - p.ordered = append(p.ordered, p.context.add(p.currentKey)) - hash[kname] = val - } - p.context = outerContext - p.currentKey = outerKey - return hash, tomlHash + return p.valueInlineTable(it, parentIsArray) + default: + p.bug("Unexpected value type: %s", it.typ) } - p.bug("Unexpected value type: %s", it.typ) panic("unreachable") } +func (p *parser) valueInteger(it item) (interface{}, tomlType) { + if !numUnderscoresOK(it.val) { + p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val) + } + if numHasLeadingZero(it.val) { + p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val) + } + + num, err := strconv.ParseInt(it.val, 0, 64) + if err != nil { + // Distinguish integer values. Normally, it'd be a bug if the lexer + // provides an invalid integer, but it's possible that the number is + // out of range of valid values (which the lexer cannot determine). + // So mark the former as a bug but the latter as a legitimate user + // error. + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "int64"}) + } else { + p.bug("Expected integer value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) +} + +func (p *parser) valueFloat(it item) (interface{}, tomlType) { + parts := strings.FieldsFunc(it.val, func(r rune) bool { + switch r { + case '.', 'e', 'E': + return true + } + return false + }) + for _, part := range parts { + if !numUnderscoresOK(part) { + p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val) + } + } + if len(parts) > 0 && numHasLeadingZero(parts[0]) { + p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val) + } + if !numPeriodsOK(it.val) { + // As a special case, numbers like '123.' or '1.e2', + // which are valid as far as Go/strconv are concerned, + // must be rejected because TOML says that a fractional + // part consists of '.' followed by 1+ digits. + p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val) + } + val := strings.Replace(it.val, "_", "", -1) + if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does. + val = "nan" + } + num, err := strconv.ParseFloat(val, 64) + if err != nil { + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "float64"}) + } else { + p.panicItemf(it, "Invalid float value: %q", it.val) + } + } + return num, p.typeOfPrimitive(it) +} + +var dtTypes = []struct { + fmt string + zone *time.Location +}{ + {time.RFC3339Nano, time.Local}, + {"2006-01-02T15:04:05.999999999", internal.LocalDatetime}, + {"2006-01-02", internal.LocalDate}, + {"15:04:05.999999999", internal.LocalTime}, +} + +func (p *parser) valueDatetime(it item) (interface{}, tomlType) { + it.val = datetimeRepl.Replace(it.val) + var ( + t time.Time + ok bool + err error + ) + for _, dt := range dtTypes { + t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) + if err == nil { + ok = true + break + } + } + if !ok { + p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val) + } + return t, p.typeOfPrimitive(it) +} + +func (p *parser) valueArray(it item) (interface{}, tomlType) { + p.setType(p.currentKey, tomlArray, it.pos) + + var ( + types []tomlType + + // Initialize to a non-nil empty slice. This makes it consistent with + // how S = [] decodes into a non-nil slice inside something like struct + // { S []string }. See #338 + array = []interface{}{} + ) + for it = p.next(); it.typ != itemArrayEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + val, typ := p.value(it, true) + array = append(array, val) + types = append(types, typ) + + // XXX: types isn't used here, we need it to record the accurate type + // information. + // + // Not entirely sure how to best store this; could use "key[0]", + // "key[1]" notation, or maybe store it on the Array type? + } + return array, tomlArray +} + +func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) { + var ( + hash = make(map[string]interface{}) + outerContext = p.context + outerKey = p.currentKey + ) + + p.context = append(p.context, p.currentKey) + prevContext := p.context + p.currentKey = "" + + p.addImplicit(p.context) + p.addContext(p.context, parentIsArray) + + /// Loop over all table key/value pairs. + for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + /// Read all key parts. + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) + + /// The current key is the last part. + p.currentKey = key[len(key)-1] + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key[:len(key)-1] + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + + /// Set the value. + val, typ := p.value(p.next(), false) + p.set(p.currentKey, val, typ, it.pos) + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + hash[p.currentKey] = val + + /// Restore context. + p.context = prevContext + } + p.context = outerContext + p.currentKey = outerKey + return hash, tomlHash +} + +// numHasLeadingZero checks if this number has leading zeroes, allowing for '0', +// +/- signs, and base prefixes. +func numHasLeadingZero(s string) bool { + if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x + return true + } + if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' { + return true + } + return false +} + // numUnderscoresOK checks whether each underscore in s is surrounded by // characters that are not underscores. func numUnderscoresOK(s string) bool { + switch s { + case "nan", "+nan", "-nan", "inf", "-inf", "+inf": + return true + } accept := false for _, r := range s { if r == '_' { if !accept { return false } - accept = false - continue } - accept = true + + // isHexadecimal is a superset of all the permissable characters + // surrounding an underscore. + accept = isHexadecimal(r) } return accept } @@ -338,13 +487,12 @@ func numPeriodsOK(s string) bool { return !period } -// establishContext sets the current context of the parser, -// where the context is either a hash or an array of hashes. Which one is -// set depends on the value of the `array` parameter. +// Set the current context of the parser, where the context is either a hash or +// an array of hashes, depending on the value of the `array` parameter. // // Establishing the context also makes sure that the key isn't a duplicate, and // will create implicit hashes automatically. -func (p *parser) establishContext(key Key, array bool) { +func (p *parser) addContext(key Key, array bool) { var ok bool // Always start at the top level and drill down for our context. @@ -383,7 +531,7 @@ func (p *parser) establishContext(key Key, array bool) { // list of tables for it. k := key[len(key)-1] if _, ok := hashContext[k]; !ok { - hashContext[k] = make([]map[string]interface{}, 0, 5) + hashContext[k] = make([]map[string]interface{}, 0, 4) } // Add a new table. But make sure the key hasn't already been used @@ -391,8 +539,7 @@ func (p *parser) establishContext(key Key, array bool) { if hash, ok := hashContext[k].([]map[string]interface{}); ok { hashContext[k] = append(hash, make(map[string]interface{})) } else { - p.panicf("Key '%s' was already created and cannot be used as "+ - "an array.", keyContext) + p.panicf("Key '%s' was already created and cannot be used as an array.", key) } } else { p.setValue(key[len(key)-1], make(map[string]interface{})) @@ -400,15 +547,23 @@ func (p *parser) establishContext(key Key, array bool) { p.context = append(p.context, key[len(key)-1]) } +// set calls setValue and setType. +func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) { + p.setValue(key, val) + p.setType(key, typ, pos) + +} + // setValue sets the given key to the given value in the current context. // It will make sure that the key hasn't already been defined, account for // implicit key groups. func (p *parser) setValue(key string, value interface{}) { - var tmpHash interface{} - var ok bool - - hash := p.mapping - keyContext := make(Key, 0) + var ( + tmpHash interface{} + ok bool + hash = p.mapping + keyContext Key + ) for _, k := range p.context { keyContext = append(keyContext, k) if tmpHash, ok = hash[k]; !ok { @@ -422,24 +577,26 @@ func (p *parser) setValue(key string, value interface{}) { case map[string]interface{}: hash = t default: - p.bug("Expected hash to have type 'map[string]interface{}', but "+ - "it has '%T' instead.", tmpHash) + p.panicf("Key '%s' has already been defined.", keyContext) } } keyContext = append(keyContext, key) if _, ok := hash[key]; ok { - // Typically, if the given key has already been set, then we have - // to raise an error since duplicate keys are disallowed. However, - // it's possible that a key was previously defined implicitly. In this - // case, it is allowed to be redefined concretely. (See the - // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) + // Normally redefining keys isn't allowed, but the key could have been + // defined implicitly and it's allowed to be redefined concretely. (See + // the `valid/implicit-and-explicit-after.toml` in toml-test) // // But we have to make sure to stop marking it as an implicit. (So that // another redefinition provokes an error.) // // Note that since it has already been defined (as a hash), we don't // want to overwrite it. So our business is done. + if p.isArray(keyContext) { + p.removeImplicit(keyContext) + hash[key] = value + return + } if p.isImplicit(keyContext) { p.removeImplicit(keyContext) return @@ -449,40 +606,39 @@ func (p *parser) setValue(key string, value interface{}) { // key, which is *always* wrong. p.panicf("Key '%s' has already been defined.", keyContext) } + hash[key] = value } -// setType sets the type of a particular value at a given key. -// It should be called immediately AFTER setValue. +// setType sets the type of a particular value at a given key. It should be +// called immediately AFTER setValue. // // Note that if `key` is empty, then the type given will be applied to the // current context (which is either a table or an array of tables). -func (p *parser) setType(key string, typ tomlType) { +func (p *parser) setType(key string, typ tomlType, pos Position) { keyContext := make(Key, 0, len(p.context)+1) - for _, k := range p.context { - keyContext = append(keyContext, k) - } + keyContext = append(keyContext, p.context...) if len(key) > 0 { // allow type setting for hashes keyContext = append(keyContext, key) } - p.types[keyContext.String()] = typ + // Special case to make empty keys ("" = 1) work. + // Without it it will set "" rather than `""`. + // TODO: why is this needed? And why is this only needed here? + if len(keyContext) == 0 { + keyContext = Key{""} + } + p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos} } -// addImplicit sets the given Key as having been created implicitly. -func (p *parser) addImplicit(key Key) { - p.implicits[key.String()] = true -} - -// removeImplicit stops tagging the given key as having been implicitly -// created. -func (p *parser) removeImplicit(key Key) { - p.implicits[key.String()] = false -} - -// isImplicit returns true if the key group pointed to by the key was created -// implicitly. -func (p *parser) isImplicit(key Key) bool { - return p.implicits[key.String()] +// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and +// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly). +func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } +func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } +func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } +func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } +func (p *parser) addImplicitContext(key Key) { + p.addImplicit(key) + p.addContext(key, false) } // current returns the full key name of the current context. @@ -497,24 +653,62 @@ func (p *parser) current() string { } func stripFirstNewline(s string) string { - if len(s) == 0 || s[0] != '\n' { + if len(s) > 0 && s[0] == '\n' { + return s[1:] + } + if len(s) > 1 && s[0] == '\r' && s[1] == '\n' { + return s[2:] + } + return s +} + +// Remove newlines inside triple-quoted strings if a line ends with "\". +func (p *parser) stripEscapedNewlines(s string) string { + split := strings.Split(s, "\n") + if len(split) < 1 { return s } - return s[1:] -} -func stripEscapedWhitespace(s string) string { - esc := strings.Split(s, "\\\n") - if len(esc) > 1 { - for i := 1; i < len(esc); i++ { - esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) + escNL := false // Keep track of the last non-blank line was escaped. + for i, line := range split { + line = strings.TrimRight(line, " \t\r") + + if len(line) == 0 || line[len(line)-1] != '\\' { + split[i] = strings.TrimRight(split[i], "\r") + if !escNL && i != len(split)-1 { + split[i] += "\n" + } + continue + } + + escBS := true + for j := len(line) - 1; j >= 0 && line[j] == '\\'; j-- { + escBS = !escBS + } + if escNL { + line = strings.TrimLeft(line, " \t\r") + } + escNL = !escBS + + if escBS { + split[i] += "\n" + continue + } + + if i == len(split)-1 { + p.panicf("invalid escape: '\\ '") + } + + split[i] = line[:len(line)-1] // Remove \ + if len(split)-1 > i { + split[i+1] = strings.TrimLeft(split[i+1], " \t\r") } } - return strings.Join(esc, "") + return strings.Join(split, "") } -func (p *parser) replaceEscapes(str string) string { - var replaced []rune +func (p *parser) replaceEscapes(it item, str string) string { + replaced := make([]rune, 0, len(str)) s := []byte(str) r := 0 for r < len(s) { @@ -532,7 +726,8 @@ func (p *parser) replaceEscapes(str string) string { switch s[r] { default: p.bug("Expected valid escape code after \\, but got %q.", s[r]) - return "" + case ' ', '\t': + p.panicItemf(it, "invalid escape: '\\%c'", s[r]) case 'b': replaced = append(replaced, rune(0x0008)) r += 1 @@ -558,14 +753,14 @@ func (p *parser) replaceEscapes(str string) string { // At this point, we know we have a Unicode escape of the form // `uXXXX` at [r, r+5). (Because the lexer guarantees this // for us.) - escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) + escaped := p.asciiEscapeToUnicode(it, s[r+1:r+5]) replaced = append(replaced, escaped) r += 5 case 'U': // At this point, we know we have a Unicode escape of the form // `uXXXX` at [r, r+9). (Because the lexer guarantees this // for us.) - escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) + escaped := p.asciiEscapeToUnicode(it, s[r+1:r+9]) replaced = append(replaced, escaped) r += 9 } @@ -573,20 +768,14 @@ func (p *parser) replaceEscapes(str string) string { return string(replaced) } -func (p *parser) asciiEscapeToUnicode(bs []byte) rune { +func (p *parser) asciiEscapeToUnicode(it item, bs []byte) rune { s := string(bs) hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) if err != nil { - p.bug("Could not parse '%s' as a hexadecimal number, but the "+ - "lexer claims it's OK: %s", s, err) + p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err) } if !utf8.ValidRune(rune(hex)) { - p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) + p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s) } return rune(hex) } - -func isStringType(ty itemType) bool { - return ty == itemString || ty == itemMultilineString || - ty == itemRawString || ty == itemRawMultilineString -} diff --git a/vendor/github.com/BurntSushi/toml/session.vim b/vendor/github.com/BurntSushi/toml/session.vim deleted file mode 100644 index 562164be..00000000 --- a/vendor/github.com/BurntSushi/toml/session.vim +++ /dev/null @@ -1 +0,0 @@ -au BufWritePost *.go silent!make tags > /dev/null 2>&1 diff --git a/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/BurntSushi/toml/type_fields.go index 608997c2..254ca82e 100644 --- a/vendor/github.com/BurntSushi/toml/type_fields.go +++ b/vendor/github.com/BurntSushi/toml/type_fields.go @@ -70,8 +70,8 @@ func typeFields(t reflect.Type) []field { next := []field{{typ: t}} // Count of queued names for current level and the next. - count := map[reflect.Type]int{} - nextCount := map[reflect.Type]int{} + var count map[reflect.Type]int + var nextCount map[reflect.Type]int // Types already visited at an earlier level. visited := map[reflect.Type]bool{} diff --git a/vendor/github.com/BurntSushi/toml/type_check.go b/vendor/github.com/BurntSushi/toml/type_toml.go similarity index 75% rename from vendor/github.com/BurntSushi/toml/type_check.go rename to vendor/github.com/BurntSushi/toml/type_toml.go index c73f8afc..4e90d773 100644 --- a/vendor/github.com/BurntSushi/toml/type_check.go +++ b/vendor/github.com/BurntSushi/toml/type_toml.go @@ -16,7 +16,7 @@ func typeEqual(t1, t2 tomlType) bool { return t1.typeString() == t2.typeString() } -func typeIsHash(t tomlType) bool { +func typeIsTable(t tomlType) bool { return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) } @@ -68,24 +68,3 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType { p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) panic("unreachable") } - -// typeOfArray returns a tomlType for an array given a list of types of its -// values. -// -// In the current spec, if an array is homogeneous, then its type is always -// "Array". If the array is not homogeneous, an error is generated. -func (p *parser) typeOfArray(types []tomlType) tomlType { - // Empty arrays are cool. - if len(types) == 0 { - return tomlArray - } - - theType := types[0] - for _, t := range types[1:] { - if !typeEqual(theType, t) { - p.panicf("Array contains values of type '%s' and '%s', but "+ - "arrays must be homogeneous.", theType, t) - } - } - return tomlArray -} diff --git a/vendor/github.com/certifi/gocertifi/.travis.yml b/vendor/github.com/certifi/gocertifi/.travis.yml deleted file mode 100644 index c41119d5..00000000 --- a/vendor/github.com/certifi/gocertifi/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -language: go -go: -- tip -- 1.12.x -- 1.11.x -- 1.10.x -- 1.9.x -- 1.8.x -sudo: false - -# Forks will use that path for checkout -go_import_path: github.com/certifi/gocertifi diff --git a/vendor/github.com/certifi/gocertifi/README.md b/vendor/github.com/certifi/gocertifi/README.md index b392d353..9c860301 100644 --- a/vendor/github.com/certifi/gocertifi/README.md +++ b/vendor/github.com/certifi/gocertifi/README.md @@ -23,23 +23,23 @@ You can use the returned `*x509.CertPool` as part of an HTTP transport, for exam ```go import ( - "net/http" - "crypto/tls" + "net/http" + "crypto/tls" ) // Setup an HTTP client with a custom transport transport := &http.Transport{ - Proxy: ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - ForceAttemptHTTP2: true, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + Proxy: ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, } // or, starting with go1.13 simply use: // transport := http.DefaultTransport.(*http.Transport).Clone() @@ -59,16 +59,11 @@ Import as follows: import "github.com/certifi/gocertifi" ``` -### Errors - -```go -var ErrParseFailed = errors.New("gocertifi: error when parsing certificates") -``` - ### Functions ```go func CACerts() (*x509.CertPool, error) ``` CACerts builds an X.509 certificate pool containing the Mozilla CA Certificate -bundle. Returns nil on error along with an appropriate error code. +bundle. This can't actually error and always returns successfully with `nil` +as the error. This will be replaced in `v2` to only return the `CertPool`. diff --git a/vendor/github.com/certifi/gocertifi/certifi.go b/vendor/github.com/certifi/gocertifi/certifi.go index a43244aa..70be286d 100644 --- a/vendor/github.com/certifi/gocertifi/certifi.go +++ b/vendor/github.com/certifi/gocertifi/certifi.go @@ -1,7 +1,11 @@ // Code generated by go generate; DO NOT EDIT. -// 2020-02-11 10:00:50.717122 -0800 PST m=+0.664468880 +// 2021-05-07 14:14:36.874796853 -0700 PDT m=+0.476299993 // https://mkcert.org/generate/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + package gocertifi //go:generate go run gen.go "https://mkcert.org/generate/" @@ -9,14 +13,6 @@ package gocertifi import "crypto/x509" const pemcerts string = ` - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 -----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv @@ -38,14 +34,6 @@ AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp @@ -68,46 +56,6 @@ ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946069240 -# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 -# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 -# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 -----BEGIN CERTIFICATE----- MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp @@ -133,14 +81,6 @@ u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er fF6adulZkMV8gzURZVE= -----END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD @@ -162,47 +102,6 @@ Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- - -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Label: "Entrust Root Certification Authority" -# Serial: 1164660820 -# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 -# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 -# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c -----BEGIN CERTIFICATE----- MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 @@ -230,120 +129,6 @@ AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow @@ -369,14 +154,6 @@ G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 -----BEGIN CERTIFICATE----- MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 @@ -411,14 +188,6 @@ mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK SnQ2+Q== -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv @@ -452,14 +221,6 @@ ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y 4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza 8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 -----BEGIN CERTIFICATE----- MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv @@ -498,14 +259,6 @@ Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- - -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c -----BEGIN CERTIFICATE----- MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t @@ -526,14 +279,6 @@ Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- - -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 -----BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx @@ -553,14 +298,6 @@ FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M -----END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk @@ -586,14 +323,6 @@ IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ O+7ETPTsJ3xCwnR8gooJybQDJbw= -----END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 -----BEGIN CERTIFICATE----- MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE @@ -618,14 +347,6 @@ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf ReYNnyicsbkqWletNw+vHX/bvZ8= -----END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 -----BEGIN CERTIFICATE----- MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp @@ -650,54 +371,6 @@ xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- - -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c -----BEGIN CERTIFICATE----- MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -720,14 +393,6 @@ NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -750,14 +415,6 @@ PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -781,14 +438,6 @@ Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- - -# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Label: "DST Root CA X3" -# Serial: 91299735575339953335919266965803778155 -# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 -# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 -# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 -----BEGIN CERTIFICATE----- MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT @@ -809,14 +458,6 @@ R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ -----END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 -----BEGIN CERTIFICATE----- MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln @@ -850,14 +491,6 @@ Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- - -# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Label: "SwissSign Silver CA - G2" -# Serial: 5700383053117599563 -# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 -# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb -# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 -----BEGIN CERTIFICATE----- MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu @@ -891,112 +524,6 @@ OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 -----BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x @@ -1019,14 +546,6 @@ D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR 3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= -----END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 -----BEGIN CERTIFICATE----- MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x @@ -1049,14 +568,6 @@ I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW -----END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 -----BEGIN CERTIFICATE----- MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G @@ -1082,14 +593,6 @@ zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== -----END CERTIFICATE----- - -# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c -----BEGIN CERTIFICATE----- MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu @@ -1113,14 +616,6 @@ h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey -----END CERTIFICATE----- - -# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Label: "COMODO ECC Certification Authority" -# Serial: 41578283867086692638256921589707938090 -# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 -# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 -# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 -----BEGIN CERTIFICATE----- MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE @@ -1137,46 +632,6 @@ BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d -----BEGIN CERTIFICATE----- MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X @@ -1199,14 +654,6 @@ HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- - -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 -----BEGIN CERTIFICATE----- MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh @@ -1229,14 +676,6 @@ omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW WL1WMRJOEcgh4LMRkWXbtKaIOM5V -----END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 -----BEGIN CERTIFICATE----- MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 @@ -1270,14 +709,6 @@ Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D hNQ+IIX3Sj0rnP0qCglN6oH4EZw= -----END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb -----BEGIN CERTIFICATE----- MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD @@ -1298,193 +729,6 @@ Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD -----END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - -# Issuer: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) -# Subject: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) -# Label: "NetLock Arany (Class Gold) Főtanúsítvány" -# Serial: 80544274841616 -# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 -# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 -# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 @@ -1509,55 +753,6 @@ bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G2" -# Serial: 10000012 -# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a -# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 -# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX -DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 -qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp -uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU -Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE -pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp -5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M -UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN -GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy -5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv -6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK -eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 -B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ -BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov -L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG -SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS -CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen -5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 -IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK -gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL -+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL -vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm -bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk -N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC -Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z -ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Label: "Hongkong Post Root CA 1" -# Serial: 1000 -# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca -# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 -# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 -----BEGIN CERTIFICATE----- MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg @@ -1578,14 +773,6 @@ EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi AmvZWg== -----END CERTIFICATE----- - -# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Label: "SecureSign RootCA11" -# Serial: 1 -# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 -# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 -# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG @@ -1607,14 +794,6 @@ QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN QSdJQO7e5iNEOdyhIta6A/I= -----END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Label: "Microsec e-Szigno Root CA 2009" -# Serial: 14014712776195784473 -# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 -# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e -# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 -----BEGIN CERTIFICATE----- MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 @@ -1639,14 +818,6 @@ tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c 2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW -----END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Label: "GlobalSign Root CA - R3" -# Serial: 4835703278459759426209954 -# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 -# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad -# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b -----BEGIN CERTIFICATE----- MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp @@ -1668,14 +839,6 @@ mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef -----BEGIN CERTIFICATE----- MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h @@ -1711,14 +874,6 @@ StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V -----END CERTIFICATE----- - -# Issuer: CN=Izenpe.com O=IZENPE S.A. -# Subject: CN=Izenpe.com O=IZENPE S.A. -# Label: "Izenpe.com" -# Serial: 917563065490389241595536686991402621 -# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 -# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 -# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f -----BEGIN CERTIFICATE----- MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 @@ -1753,113 +908,6 @@ QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- - -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - -# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Label: "Go Daddy Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 -# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b -# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da -----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT @@ -1883,14 +931,6 @@ gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 -----END CERTIFICATE----- - -# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 -# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e -# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 -----BEGIN CERTIFICATE----- MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT @@ -1914,14 +954,6 @@ sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 -----END CERTIFICATE----- - -# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Services Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 -# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f -# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 -----BEGIN CERTIFICATE----- MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT @@ -1946,14 +978,6 @@ iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn 0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN sSi6 -----END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Commercial O=AffirmTrust -# Subject: CN=AffirmTrust Commercial O=AffirmTrust -# Label: "AffirmTrust Commercial" -# Serial: 8608355977964138876 -# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 -# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 -# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz @@ -1974,14 +998,6 @@ Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= -----END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Networking O=AffirmTrust -# Subject: CN=AffirmTrust Networking O=AffirmTrust -# Label: "AffirmTrust Networking" -# Serial: 8957382827206547757 -# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f -# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f -# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b -----BEGIN CERTIFICATE----- MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz @@ -2002,14 +1018,6 @@ Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= -----END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium O=AffirmTrust -# Subject: CN=AffirmTrust Premium O=AffirmTrust -# Label: "AffirmTrust Premium" -# Serial: 7893706540734352110 -# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 -# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 -# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz @@ -2041,14 +1049,6 @@ GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e KeC2uAloGRwYQw== -----END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust -# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust -# Label: "AffirmTrust Premium ECC" -# Serial: 8401224907861490260 -# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d -# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb -# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 -----BEGIN CERTIFICATE----- MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ @@ -2062,14 +1062,6 @@ A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== -----END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e -----BEGIN CERTIFICATE----- MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D @@ -2092,14 +1084,6 @@ J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI 03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= -----END CERTIFICATE----- - -# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Label: "TWCA Root Certification Authority" -# Serial: 1 -# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 -# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 -# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 -----BEGIN CERTIFICATE----- MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU @@ -2121,14 +1105,6 @@ lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== -----END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Label: "Security Communication RootCA2" -# Serial: 0 -# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 -# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 -# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe @@ -2150,14 +1126,6 @@ t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy 1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- - -# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes -# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes -# Label: "EC-ACC" -# Serial: -23701579247955709139626555126524820479 -# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09 -# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8 -# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99 -----BEGIN CERTIFICATE----- MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB 8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy @@ -2189,14 +1157,6 @@ Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= -----END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2011" -# Serial: 0 -# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 -# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d -# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 -----BEGIN CERTIFICATE----- MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 @@ -2222,14 +1182,6 @@ dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI l7WdmplNsDz4SgCbZN2fOUvRJ9e4 -----END CERTIFICATE----- - -# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Label: "Actalis Authentication Root CA" -# Serial: 6271844772424770508 -# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 -# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac -# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 -----BEGIN CERTIFICATE----- MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w @@ -2263,14 +1215,6 @@ ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- - -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d -----BEGIN CERTIFICATE----- MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL @@ -2292,14 +1236,6 @@ f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN ZetX2fNXlrtIzYE= -----END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 2 Root CA" -# Serial: 2 -# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 -# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 -# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg @@ -2331,14 +1267,6 @@ I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= -----END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 3 Root CA" -# Serial: 2 -# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec -# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 -# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d -----BEGIN CERTIFICATE----- MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg @@ -2370,14 +1298,6 @@ kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq 4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= -----END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 3" -# Serial: 1 -# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef -# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 -# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd @@ -2401,46 +1321,6 @@ WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- - -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 2009" -# Serial: 623603 -# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f -# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 -# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 -----BEGIN CERTIFICATE----- MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD @@ -2466,14 +1346,6 @@ zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y Johw1+qRzT65ysCQblrGXnRl11z+o+I= -----END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 EV 2009" -# Serial: 623604 -# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 -# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 -# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 -----BEGIN CERTIFICATE----- MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD @@ -2499,14 +1371,6 @@ CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 -----END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R2 O=Disig a.s. -# Subject: CN=CA Disig Root R2 O=Disig a.s. -# Label: "CA Disig Root R2" -# Serial: 10572350602393338211 -# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 -# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 -# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 -----BEGIN CERTIFICATE----- MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu @@ -2538,14 +1402,6 @@ G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL -----END CERTIFICATE----- - -# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Label: "ACCVRAIZ1" -# Serial: 6828503384748696800 -# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 -# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 -# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 -----BEGIN CERTIFICATE----- MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw @@ -2590,14 +1446,6 @@ h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 -----END CERTIFICATE----- - -# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA Global Root CA" -# Serial: 3262 -# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 -# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 -# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT @@ -2629,14 +1477,6 @@ wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 -----BEGIN CERTIFICATE----- MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv @@ -2667,14 +1507,6 @@ t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- - -# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC @@ -2711,14 +1543,6 @@ AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== -----END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 -----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd @@ -2742,14 +1566,6 @@ g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP BSeOE6Fuwg== -----END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 -----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG @@ -2771,14 +1587,6 @@ maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 1 G3" -# Serial: 687049649626669250736271037606554624078720034195 -# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab -# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 -# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc @@ -2810,14 +1618,6 @@ ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2 G3" -# Serial: 390156079458959257446133169266079962026824725800 -# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 -# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 -# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc @@ -2849,14 +1649,6 @@ KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M -----END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3 G3" -# Serial: 268090761170461462463995952157327242137089239581 -# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 -# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d -# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc @@ -2888,14 +1680,6 @@ DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G2" -# Serial: 15385348160840213938643033620894905419 -# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d -# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f -# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 -----BEGIN CERTIFICATE----- MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -2918,14 +1702,6 @@ B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo IhNzbM8m9Yop5w== -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G3" -# Serial: 15459312981008553731928384953135426796 -# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb -# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 -# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 -----BEGIN CERTIFICATE----- MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu @@ -2941,14 +1717,6 @@ AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 6pZjamVFkpUBtA== -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G2" -# Serial: 4293743540046975378534879503202253541 -# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 -# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 -# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -2971,14 +1739,6 @@ Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 -----BEGIN CERTIFICATE----- MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu @@ -2994,14 +1754,6 @@ AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 sycX -----END CERTIFICATE----- - -# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Trusted Root G4" -# Serial: 7451500558977370777930084869016614236 -# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 -# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 -# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 -----BEGIN CERTIFICATE----- MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 @@ -3034,14 +1786,6 @@ r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ -----END CERTIFICATE----- - -# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Label: "COMODO RSA Certification Authority" -# Serial: 101909084537582093308941363524873193117 -# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 -# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 -# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 -----BEGIN CERTIFICATE----- MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G @@ -3076,14 +1820,6 @@ QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl 0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB NVOFBkpdn627G190 -----END CERTIFICATE----- - -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 -----BEGIN CERTIFICATE----- MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl @@ -3118,14 +1854,6 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG jjxDah2nGN59PRbxYvnKkKj9 -----END CERTIFICATE----- - -# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Label: "USERTrust ECC Certification Authority" -# Serial: 123013823720199481456569720443997572134 -# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 -# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 -# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a -----BEGIN CERTIFICATE----- MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl @@ -3142,14 +1870,6 @@ VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Label: "GlobalSign ECC Root CA - R4" -# Serial: 14367148294922964480859022125800977897474 -# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e -# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb -# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c -----BEGIN CERTIFICATE----- MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH @@ -3163,14 +1883,6 @@ uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs ewv4n4Q= -----END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Label: "GlobalSign ECC Root CA - R5" -# Serial: 32785792099990507226680698011560947931244 -# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 -# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa -# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 -----BEGIN CERTIFICATE----- MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH @@ -3185,54 +1897,6 @@ KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg 515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO xwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G3" -# Serial: 10003001 -# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 -# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc -# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Label: "Staat der Nederlanden EV Root CA" -# Serial: 10000013 -# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba -# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb -# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a -----BEGIN CERTIFICATE----- MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh @@ -3265,14 +1929,6 @@ eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc 7uzXLg== -----END CERTIFICATE----- - -# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Label: "IdenTrust Commercial Root CA 1" -# Serial: 13298821034946342390520003877796839426 -# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 -# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 -# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae -----BEGIN CERTIFICATE----- MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu @@ -3304,14 +1960,6 @@ l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A 7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H -----END CERTIFICATE----- - -# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Label: "IdenTrust Public Sector Root CA 1" -# Serial: 13298821034946342390521976156843933698 -# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba -# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd -# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f -----BEGIN CERTIFICATE----- MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu @@ -3343,14 +1991,6 @@ tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv 8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c -----END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G2" -# Serial: 1246989352 -# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 -# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 -# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 -----BEGIN CERTIFICATE----- MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 @@ -3376,14 +2016,6 @@ Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== -----END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - EC1" -# Serial: 51543124481930649114116133369 -# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc -# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 -# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 -----BEGIN CERTIFICATE----- MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 @@ -3402,14 +2034,6 @@ BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G -----END CERTIFICATE----- - -# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority -# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority -# Label: "CFCA EV ROOT" -# Serial: 407555286 -# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 -# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 -# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd -----BEGIN CERTIFICATE----- MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y @@ -3442,14 +2066,6 @@ Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GB CA" -# Serial: 157768595616588414422159278966750757568 -# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d -# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed -# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg @@ -3472,14 +2088,6 @@ ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= -----END CERTIFICATE----- - -# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Label: "SZAFIR ROOT CA2" -# Serial: 357043034767186914217277344587386743377558296292 -# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 -# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de -# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe -----BEGIN CERTIFICATE----- MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 @@ -3501,14 +2109,6 @@ oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== -----END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA 2" -# Serial: 44979900017204383099463764357512596969 -# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 -# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 -# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 -----BEGIN CERTIFICATE----- MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu @@ -3543,14 +2143,6 @@ XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P 5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi DrW5viSP -----END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce -# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 -# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 -----BEGIN CERTIFICATE----- MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k @@ -3586,14 +2178,6 @@ pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 vm9qp/UsQu0yrbYhnr68 -----END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef -# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 -# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 -----BEGIN CERTIFICATE----- MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl @@ -3611,14 +2195,6 @@ C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR -----END CERTIFICATE----- - -# Issuer: CN=ISRG Root X1 O=Internet Security Research Group -# Subject: CN=ISRG Root X1 O=Internet Security Research Group -# Label: "ISRG Root X1" -# Serial: 172886928669790476064670243504169061120 -# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e -# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 -# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh @@ -3650,14 +2226,6 @@ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= -----END CERTIFICATE----- - -# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Label: "AC RAIZ FNMT-RCM" -# Serial: 485876308206448804701554682760554759 -# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d -# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 -# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ @@ -3690,14 +2258,6 @@ fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= -----END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 1 O=Amazon -# Subject: CN=Amazon Root CA 1 O=Amazon -# Label: "Amazon Root CA 1" -# Serial: 143266978916655856878034712317230054538369994 -# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 -# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 -# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 @@ -3718,14 +2278,6 @@ o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy rqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 2 O=Amazon -# Subject: CN=Amazon Root CA 2 O=Amazon -# Label: "Amazon Root CA 2" -# Serial: 143266982885963551818349160658925006970653239 -# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 -# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a -# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 -----BEGIN CERTIFICATE----- MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 @@ -3757,14 +2309,6 @@ n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE 9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT 4PsJYGw= -----END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 3 O=Amazon -# Subject: CN=Amazon Root CA 3 O=Amazon -# Label: "Amazon Root CA 3" -# Serial: 143266986699090766294700635381230934788665930 -# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 -# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e -# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 -----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g @@ -3777,14 +2321,6 @@ ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM YyRIHN8wfdVoOw== -----END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 4 O=Amazon -# Subject: CN=Amazon Root CA 4 O=Amazon -# Label: "Amazon Root CA 4" -# Serial: 143266989758080763974105200630763877849284878 -# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd -# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be -# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 -----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g @@ -3798,55 +2334,6 @@ MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- - -# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Label: "LuxTrust Global Root 2" -# Serial: 59914338225734147123941058376788110305822489521 -# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c -# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f -# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 ------BEGIN CERTIFICATE----- -MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL -BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV -BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw -MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B -LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F -ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem -hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 -EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn -Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 -zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ -96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m -j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g -DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ -8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j -X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH -hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB -KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 -Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT -+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL -BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 -BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO -jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 -loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c -qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ -2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ -JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre -zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf -LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ -x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 -oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr ------END CERTIFICATE----- - -# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" -# Serial: 1 -# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 -# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca -# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 -----BEGIN CERTIFICATE----- MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp @@ -3873,14 +2360,6 @@ lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c 8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= -----END CERTIFICATE----- - -# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Label: "GDCA TrustAUTH R5 ROOT" -# Serial: 9009899650740120186 -# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 -# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 -# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 -----BEGIN CERTIFICATE----- MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ @@ -3913,14 +2392,6 @@ XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- - -# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-1" -# Serial: 15752444095811006489 -# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 -# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a -# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c -----BEGIN CERTIFICATE----- MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk @@ -3946,14 +2417,6 @@ yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN zl/HHk484IkzlQsPpTLWPFp5LBk= -----END CERTIFICATE----- - -# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-2" -# Serial: 2711694510199101698 -# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 -# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 -# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 -----BEGIN CERTIFICATE----- MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw @@ -3990,14 +2453,6 @@ As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp 5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu 1uwJ -----END CERTIFICATE----- - -# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor ECA-1" -# Serial: 9548242946988625984 -# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c -# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd -# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c -----BEGIN CERTIFICATE----- MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk @@ -4023,14 +2478,6 @@ soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi tJ/X5g== -----END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Label: "SSL.com Root Certification Authority RSA" -# Serial: 8875640296558310041 -# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 -# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb -# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 -----BEGIN CERTIFICATE----- MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK @@ -4065,14 +2512,6 @@ nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY Ic2wBlX7Jz9TkHCpBB5XJ7k= -----END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com Root Certification Authority ECC" -# Serial: 8495723813297216424 -# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e -# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a -# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 -----BEGIN CERTIFICATE----- MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T @@ -4089,14 +2528,6 @@ VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl -----END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority RSA R2" -# Serial: 6248227494352943350 -# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 -# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a -# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c -----BEGIN CERTIFICATE----- MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE @@ -4131,14 +2562,6 @@ w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== -----END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority ECC" -# Serial: 3182246526754555285 -# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 -# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d -# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 -----BEGIN CERTIFICATE----- MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T @@ -4155,14 +2578,6 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== -----END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Label: "GlobalSign Root CA - R6" -# Serial: 1417766617973444989252670301619537 -# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae -# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 -# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 -----BEGIN CERTIFICATE----- MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh @@ -4195,14 +2610,6 @@ JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R 8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= -----END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GC CA" -# Serial: 44084345621038548146064804565436152554 -# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 -# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 -# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d -----BEGIN CERTIFICATE----- MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 @@ -4218,14 +2625,6 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV 57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 -----END CERTIFICATE----- - -# Issuer: CN=GTS Root R1 O=Google Trust Services LLC -# Subject: CN=GTS Root R1 O=Google Trust Services LLC -# Label: "GTS Root R1" -# Serial: 146587175971765017618439757810265552097 -# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85 -# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8 -# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72 -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM @@ -4257,14 +2656,6 @@ F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl -----END CERTIFICATE----- - -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 146587176055767053814479386953112547951 -# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b -# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d -# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60 -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM @@ -4296,14 +2687,6 @@ RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC -----END CERTIFICATE----- - -# Issuer: CN=GTS Root R3 O=Google Trust Services LLC -# Subject: CN=GTS Root R3 O=Google Trust Services LLC -# Label: "GTS Root R3" -# Serial: 146587176140553309517047991083707763997 -# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25 -# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5 -# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5 -----BEGIN CERTIFICATE----- MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU @@ -4317,14 +2700,6 @@ DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd -----END CERTIFICATE----- - -# Issuer: CN=GTS Root R4 O=Google Trust Services LLC -# Subject: CN=GTS Root R4 O=Google Trust Services LLC -# Label: "GTS Root R4" -# Serial: 146587176229350439916519468929765261721 -# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26 -# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb -# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd -----BEGIN CERTIFICATE----- MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU @@ -4338,14 +2713,6 @@ DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== -----END CERTIFICATE----- - -# Issuer: CN=UCA Global G2 Root O=UniTrust -# Subject: CN=UCA Global G2 Root O=UniTrust -# Label: "UCA Global G2 Root" -# Serial: 124779693093741543919145257850076631279 -# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 -# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a -# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c -----BEGIN CERTIFICATE----- MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH @@ -4377,14 +2744,6 @@ DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX UB+K+wb1whnw0A== -----END CERTIFICATE----- - -# Issuer: CN=UCA Extended Validation Root O=UniTrust -# Subject: CN=UCA Extended Validation Root O=UniTrust -# Label: "UCA Extended Validation Root" -# Serial: 106100277556486529736699587978573607008 -# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 -# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a -# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 -----BEGIN CERTIFICATE----- MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF @@ -4416,14 +2775,6 @@ xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax -----END CERTIFICATE----- - -# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Label: "Certigna Root CA" -# Serial: 269714418870597844693661054334862075617 -# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 -# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 -# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 -----BEGIN CERTIFICATE----- MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw @@ -4460,14 +2811,6 @@ Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw 3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= -----END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign Root CA - G1" -# Serial: 235931866688319308814040 -# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac -# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c -# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 -----BEGIN CERTIFICATE----- MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU @@ -4490,14 +2833,6 @@ GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign ECC Root CA - G3" -# Serial: 287880440101571086945156 -# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 -# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 -# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b -----BEGIN CERTIFICATE----- MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo @@ -4513,14 +2848,6 @@ hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD +JbNR6iC8hZVdyR+EhCVBCyj -----END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Label: "emSign Root CA - C1" -# Serial: 825510296613316004955058 -# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 -# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 -# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f -----BEGIN CERTIFICATE----- MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg @@ -4542,14 +2869,6 @@ YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT +xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= -----END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Label: "emSign ECC Root CA - C3" -# Serial: 582948710642506000014504 -# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 -# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 -# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 -----BEGIN CERTIFICATE----- MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx @@ -4564,14 +2883,6 @@ Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c 3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J 0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== -----END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Label: "Hongkong Post Root CA 3" -# Serial: 46170865288971385588281144162979347873371282084 -# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 -# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 -# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 -----BEGIN CERTIFICATE----- MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ @@ -4606,14 +2917,6 @@ L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG mpv0 -----END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G4" -# Serial: 289383649854506086828220374796556676440 -# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 -# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 -# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 -----BEGIN CERTIFICATE----- MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL @@ -4650,12 +2953,265 @@ IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk 5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== -----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- ` // CACerts builds an X.509 certificate pool containing the -// certificate bundle from https://mkcert.org/generate/ fetch on 2020-02-11 10:00:50.717122 -0800 PST m=+0.664468880. -// Returns nil on error along with an appropriate error code. +// certificate bundle from https://mkcert.org/generate/ fetch on 2021-05-07 14:14:36.874796853 -0700 PDT m=+0.476299993. +// Will never actually return an error. func CACerts() (*x509.CertPool, error) { pool := x509.NewCertPool() pool.AppendCertsFromPEM([]byte(pemcerts)) diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/address.go b/vendor/github.com/coredns/coredns/core/dnsserver/address.go index b8563c87..872e44cb 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/address.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/address.go @@ -49,12 +49,23 @@ func newOverlapZone() *zoneOverlap { // registerAndCheck adds a new zoneAddr for validation, it returns information about existing or overlapping with already registered // we consider that an unbound address is overlapping all bound addresses for same zone, same port func (zo *zoneOverlap) registerAndCheck(z zoneAddr) (existingZone *zoneAddr, overlappingZone *zoneAddr) { + existingZone, overlappingZone = zo.check(z) + if existingZone != nil || overlappingZone != nil { + return existingZone, overlappingZone + } + // there is no overlap, keep the current zoneAddr for future checks + zo.registeredAddr[z] = z + zo.unboundOverlap[z.unbound()] = z + return nil, nil +} +// check validates a zoneAddr for overlap without registering it +func (zo *zoneOverlap) check(z zoneAddr) (existingZone *zoneAddr, overlappingZone *zoneAddr) { if exist, ok := zo.registeredAddr[z]; ok { // exact same zone already registered return &exist, nil } - uz := zoneAddr{Zone: z.Zone, Address: "", Port: z.Port, Transport: z.Transport} + uz := z.unbound() if already, ok := zo.unboundOverlap[uz]; ok { if z.Address == "" { // current is not bound to an address, but there is already another zone with a bind address registered @@ -65,8 +76,11 @@ func (zo *zoneOverlap) registerAndCheck(z zoneAddr) (existingZone *zoneAddr, ove return nil, &uz } } - // there is no overlap, keep the current zoneAddr for future checks - zo.registeredAddr[z] = z - zo.unboundOverlap[uz] = z + // there is no overlap return nil, nil } + +// unbound returns an unbound version of the zoneAddr +func (z zoneAddr) unbound() zoneAddr { + return zoneAddr{Zone: z.Zone, Address: "", Port: z.Port, Transport: z.Transport} +} diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/config.go b/vendor/github.com/coredns/coredns/core/dnsserver/config.go index e5200d67..3da86271 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/config.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/config.go @@ -1,12 +1,14 @@ package dnsserver import ( + "context" "crypto/tls" "fmt" "net/http" "github.com/coredns/caddy" "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/request" ) // Config configuration for a single server. @@ -28,6 +30,9 @@ type Config struct { // Debug controls the panic/recover mechanism that is enabled by default. Debug bool + // Stacktrace controls including stacktrace as part of log from recover mechanism, it is disabled by default. + Stacktrace bool + // The transport we implement, normally just "dns" over TCP/UDP, but could be // DNS-over-TLS or DNS-over-gRPC. Transport string @@ -37,9 +42,20 @@ type Config struct { // may depend on it. HTTPRequestValidateFunc func(*http.Request) bool + // FilterFuncs is used to further filter access + // to this handler. E.g. to limit access to a reverse zone + // on a non-octet boundary, i.e. /17 + FilterFuncs []FilterFunc + + // ViewName is the name of the Viewer PLugin defined in the Config + ViewName string + // TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS). TLSConfig *tls.Config + // TSIG secrets, [name]key. + TsigSecret map[string]string + // Plugin stack. Plugin []plugin.Plugin @@ -54,8 +70,14 @@ type Config struct { // firstConfigInBlock is used to reference the first config in a server block, for the // purpose of sharing single instance of each plugin among all zones in a server block. firstConfigInBlock *Config + + // metaCollector references the first MetadataCollector plugin, if one exists + metaCollector MetadataCollector } +// FilterFunc is a function that filters requests from the Config +type FilterFunc func(context.Context, *request.Request) bool + // keyForConfig builds a key for identifying the configs during setup time func keyForConfig(blocIndex int, blocKeyIndex int) string { return fmt.Sprintf("%d:%d", blocIndex, blocKeyIndex) diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/onstartup.go b/vendor/github.com/coredns/coredns/core/dnsserver/onstartup.go index 113bf15e..572b3589 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/onstartup.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/onstartup.go @@ -2,17 +2,31 @@ package dnsserver import ( "fmt" + "regexp" "sort" + + "github.com/coredns/coredns/plugin/pkg/dnsutil" ) +// checkZoneSyntax() checks whether the given string match 1035 Preferred Syntax or not. +// The root zone, and all reverse zones always return true even though they technically don't meet 1035 Preferred Syntax +func checkZoneSyntax(zone string) bool { + if zone == "." || dnsutil.IsReverse(zone) != 0 { + return true + } + regex1035PreferredSyntax, _ := regexp.MatchString(`^(([A-Za-z]([A-Za-z0-9-]*[A-Za-z0-9])?)\.)+$`, zone) + return regex1035PreferredSyntax +} + // startUpZones creates the text that we show when starting up: // grpc://example.com.:1055 // example.com.:1053 on 127.0.0.1 -func startUpZones(protocol, addr string, zones map[string]*Config) string { +func startUpZones(protocol, addr string, zones map[string][]*Config) string { s := "" keys := make([]string, len(zones)) i := 0 + for k := range zones { keys[i] = k i++ @@ -20,6 +34,9 @@ func startUpZones(protocol, addr string, zones map[string]*Config) string { sort.Strings(keys) for _, zone := range keys { + if !checkZoneSyntax(zone) { + s += fmt.Sprintf("Warning: Domain %q does not follow RFC1035 preferred syntax\n", zone) + } // split addr into protocol, IP and Port _, ip, port, err := SplitProtocolHostPort(addr) diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/register.go b/vendor/github.com/coredns/coredns/core/dnsserver/register.go index ec254085..e94accc2 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/register.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/register.go @@ -138,14 +138,6 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy // MakeServers uses the newly-created siteConfigs to create and return a list of server instances. func (h *dnsContext) MakeServers() ([]caddy.Server, error) { - - // Now that all Keys and Directives are parsed and initialized - // lets verify that there is no overlap on the zones and addresses to listen for - errValid := h.validateZonesAndListeningAddresses() - if errValid != nil { - return nil, errValid - } - // Copy the Plugin, ListenHosts and Debug from first config in the block // to all other config in the same block . Doing this results in zones // sharing the same plugin instances and settings as other zones in @@ -154,7 +146,9 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) { c.Plugin = c.firstConfigInBlock.Plugin c.ListenHosts = c.firstConfigInBlock.ListenHosts c.Debug = c.firstConfigInBlock.Debug + c.Stacktrace = c.firstConfigInBlock.Stacktrace c.TLSConfig = c.firstConfigInBlock.TLSConfig + c.TsigSecret = c.firstConfigInBlock.TsigSecret } // we must map (group) each config to a bind address @@ -195,7 +189,27 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) { } servers = append(servers, s) } + } + // For each server config, check for View Filter plugins + for _, c := range h.configs { + // Add filters in the plugin.cfg order for consistent filter func evaluation order. + for _, d := range Directives { + if vf, ok := c.registry[d].(Viewer); ok { + if c.ViewName != "" { + return nil, fmt.Errorf("multiple views defined in server block") + } + c.ViewName = vf.ViewName() + c.FilterFuncs = append(c.FilterFuncs, vf.Filter) + } + } + } + + // Verify that there is no overlap on the zones and listen addresses + // for unfiltered server configs + errValid := h.validateZonesAndListeningAddresses() + if errValid != nil { + return nil, errValid } return servers, nil @@ -253,18 +267,24 @@ func (h *dnsContext) validateZonesAndListeningAddresses() error { for _, h := range conf.ListenHosts { // Validate the overlapping of ZoneAddr akey := zoneAddr{Transport: conf.Transport, Zone: conf.Zone, Address: h, Port: conf.Port} - existZone, overlapZone := checker.registerAndCheck(akey) + var existZone, overlapZone *zoneAddr + if len(conf.FilterFuncs) > 0 { + // This config has filters. Check for overlap with other (unfiltered) configs. + existZone, overlapZone = checker.check(akey) + } else { + // This config has no filters. Check for overlap with other (unfiltered) configs, + // and register the zone to prevent subsequent zones from overlapping with it. + existZone, overlapZone = checker.registerAndCheck(akey) + } if existZone != nil { return fmt.Errorf("cannot serve %s - it is already defined", akey.String()) } if overlapZone != nil { return fmt.Errorf("cannot serve %s - zone overlap listener capacity with %v", akey.String(), overlapZone.String()) } - } } return nil - } // groupSiteConfigsByListenAddr groups site configs by their listen diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/server.go b/vendor/github.com/coredns/coredns/core/dnsserver/server.go index fd498f19..478287bf 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/server.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/server.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "runtime" + "runtime/debug" "strings" "sync" "time" @@ -36,22 +37,30 @@ type Server struct { server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case. m sync.Mutex // protects the servers - zones map[string]*Config // zones keyed by their address - dnsWg sync.WaitGroup // used to wait on outstanding connections - graceTimeout time.Duration // the maximum duration of a graceful shutdown - trace trace.Trace // the trace plugin for the server - debug bool // disable recover() - classChaos bool // allow non-INET class queries + zones map[string][]*Config // zones keyed by their address + dnsWg sync.WaitGroup // used to wait on outstanding connections + graceTimeout time.Duration // the maximum duration of a graceful shutdown + trace trace.Trace // the trace plugin for the server + debug bool // disable recover() + stacktrace bool // enable stacktrace in recover error log + classChaos bool // allow non-INET class queries + + tsigSecret map[string]string +} + +// MetadataCollector is a plugin that can retrieve metadata functions from all metadata providing plugins +type MetadataCollector interface { + Collect(context.Context, request.Request) context.Context } // NewServer returns a new CoreDNS server and compiles all plugins in to it. By default CH class // queries are blocked unless queries from enableChaos are loaded. func NewServer(addr string, group []*Config) (*Server, error) { - s := &Server{ Addr: addr, - zones: make(map[string]*Config), + zones: make(map[string][]*Config), graceTimeout: 5 * time.Second, + tsigSecret: make(map[string]string), } // We have to bound our wg with one increment @@ -67,8 +76,15 @@ func NewServer(addr string, group []*Config) (*Server, error) { s.debug = true log.D.Set() } - // set the config per zone - s.zones[site.Zone] = site + s.stacktrace = site.Stacktrace + + // append the config to the zone's configs + s.zones[site.Zone] = append(s.zones[site.Zone], site) + + // copy tsig secrets + for key, secret := range site.TsigSecret { + s.tsigSecret[key] = secret + } // compile custom plugin for everything var stack plugin.Handler @@ -78,6 +94,12 @@ func NewServer(addr string, group []*Config) (*Server, error) { // register the *handler* also site.registerHandler(stack) + // If the current plugin is a MetadataCollector, bookmark it for later use. This loop traverses the plugin + // list backwards, so the first MetadataCollector plugin wins. + if mdc, ok := stack.(MetadataCollector); ok { + site.metaCollector = mdc + } + if s.trace == nil && stack.Name() == "trace" { // we have to stash away the plugin, not the // Tracer object, because the Tracer won't be initialized yet @@ -112,7 +134,7 @@ func (s *Server) Serve(l net.Listener) error { ctx := context.WithValue(context.Background(), Key{}, s) ctx = context.WithValue(ctx, LoopKey{}, 0) s.ServeDNS(ctx, w, r) - })} + }), TsigSecret: s.tsigSecret} s.m.Unlock() return s.server[tcp].ActivateAndServe() @@ -126,7 +148,7 @@ func (s *Server) ServePacket(p net.PacketConn) error { ctx := context.WithValue(context.Background(), Key{}, s) ctx = context.WithValue(ctx, LoopKey{}, 0) s.ServeDNS(ctx, w, r) - })} + }), TsigSecret: s.tsigSecret} s.m.Unlock() return s.server[udp].ActivateAndServe() @@ -163,7 +185,6 @@ func (s *Server) ListenPacket() (net.PacketConn, error) { // immediately. // This implements Caddy.Stopper interface. func (s *Server) Stop() (err error) { - if runtime.GOOS != "windows" { // force connections to close after timeout done := make(chan struct{}) @@ -213,7 +234,11 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) // In case the user doesn't enable error plugin, we still // need to make sure that we stay alive up here if rec := recover(); rec != nil { - log.Errorf("Recovered from panic in server: %q %v", s.Addr, rec) + if s.stacktrace { + log.Errorf("Recovered from panic in server: %q %v\n%s", s.Addr, rec, string(debug.Stack())) + } else { + log.Errorf("Recovered from panic in server: %q %v", s.Addr, rec) + } vars.Panic.Inc() errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure) } @@ -241,24 +266,39 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ) for { - if h, ok := s.zones[q[off:]]; ok { - if h.pluginChain == nil { // zone defined, but has not got any plugins - errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused) - return - } - if r.Question[0].Qtype != dns.TypeDS { - rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) - if !plugin.ClientWrite(rcode) { - errorFunc(s.Addr, w, r, rcode) + if z, ok := s.zones[q[off:]]; ok { + for _, h := range z { + if h.pluginChain == nil { // zone defined, but has not got any plugins + errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused) + return + } + + if h.metaCollector != nil { + // Collect metadata now, so it can be used before we send a request down the plugin chain. + ctx = h.metaCollector.Collect(ctx, request.Request{Req: r, W: w}) + } + + // If all filter funcs pass, use this config. + if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) { + if h.ViewName != "" { + // if there was a view defined for this Config, set the view name in the context + ctx = context.WithValue(ctx, ViewKey{}, h.ViewName) + } + if r.Question[0].Qtype != dns.TypeDS { + rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) + if !plugin.ClientWrite(rcode) { + errorFunc(s.Addr, w, r, rcode) + } + return + } + // The type is DS, keep the handler, but keep on searching as maybe we are serving + // the parent as well and the DS should be routed to it - this will probably *misroute* DS + // queries to a possibly grand parent, but there is no way for us to know at this point + // if there is an actual delegation from grandparent -> parent -> zone. + // In all fairness: direct DS queries should not be needed. + dshandler = h } - return } - // The type is DS, keep the handler, but keep on searching as maybe we are serving - // the parent as well and the DS should be routed to it - this will probably *misroute* DS - // queries to a possibly grand parent, but there is no way for us to know at this point - // if there is an actual delegation from grandparent -> parent -> zone. - // In all fairness: direct DS queries should not be needed. - dshandler = h } off, end = dns.NextLabel(q, off) if end { @@ -276,18 +316,46 @@ func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) } // Wildcard match, if we have found nothing try the root zone as a last resort. - if h, ok := s.zones["."]; ok && h.pluginChain != nil { - rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) - if !plugin.ClientWrite(rcode) { - errorFunc(s.Addr, w, r, rcode) + if z, ok := s.zones["."]; ok { + for _, h := range z { + if h.pluginChain == nil { + continue + } + + if h.metaCollector != nil { + // Collect metadata now, so it can be used before we send a request down the plugin chain. + ctx = h.metaCollector.Collect(ctx, request.Request{Req: r, W: w}) + } + + // If all filter funcs pass, use this config. + if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) { + if h.ViewName != "" { + // if there was a view defined for this Config, set the view name in the context + ctx = context.WithValue(ctx, ViewKey{}, h.ViewName) + } + rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) + if !plugin.ClientWrite(rcode) { + errorFunc(s.Addr, w, r, rcode) + } + return + } } - return } // Still here? Error out with REFUSED. errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused) } +// passAllFilterFuncs returns true if all filter funcs evaluate to true for the given request +func passAllFilterFuncs(ctx context.Context, filterFuncs []FilterFunc, req *request.Request) bool { + for _, ff := range filterFuncs { + if !ff(ctx, req) { + return false + } + } + return true +} + // OnStartupComplete lists the sites served by this server // and any relevant information, assuming Quiet is false. func (s *Server) OnStartupComplete() { @@ -328,7 +396,7 @@ func errorAndMetricsFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int answer.SetRcode(r, rc) state.SizeAndDo(answer) - vars.Report(server, state, vars.Dropped, rcode.ToString(rc), "" /* plugin */, answer.Len(), time.Now()) + vars.Report(server, state, vars.Dropped, "", rcode.ToString(rc), "" /* plugin */, answer.Len(), time.Now()) w.WriteMsg(answer) } @@ -344,6 +412,9 @@ type ( // LoopKey is the context key to detect server wide loops. LoopKey struct{} + + // ViewKey is the context key for the current view, if defined + ViewKey struct{} ) // EnableChaos is a map with plugin names for which we should open CH class queries as we block these by default. diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/server_grpc.go b/vendor/github.com/coredns/coredns/core/dnsserver/server_grpc.go index d5726e8a..9d7a95ac 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/server_grpc.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/server_grpc.go @@ -22,6 +22,7 @@ import ( // ServergRPC represents an instance of a DNS-over-gRPC server. type ServergRPC struct { *Server + *pb.UnimplementedDnsServiceServer grpcServer *grpc.Server listenAddr net.Addr tlsConfig *tls.Config @@ -36,9 +37,11 @@ func NewServergRPC(addr string, group []*Config) (*ServergRPC, error) { // The *tls* plugin must make sure that multiple conflicting // TLS configuration returns an error: it can only be specified once. var tlsConfig *tls.Config - for _, conf := range s.zones { - // Should we error if some configs *don't* have TLS? - tlsConfig = conf.TLSConfig + for _, z := range s.zones { + for _, conf := range z { + // Should we error if some configs *don't* have TLS? + tlsConfig = conf.TLSConfig + } } // http/2 is required when using gRPC. We need to specify it in next protos // or the upgrade won't happen. @@ -81,7 +84,6 @@ func (s *ServergRPC) ServePacket(p net.PacketConn) error { return nil } // Listen implements caddy.TCPServer interface. func (s *ServergRPC) Listen() (net.Listener, error) { - l, err := reuseport.Listen("tcp", s.Addr[len(transport.GRPC+"://"):]) if err != nil { return nil, err diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/server_https.go b/vendor/github.com/coredns/coredns/core/dnsserver/server_https.go index b8bdbc66..eda39c14 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/server_https.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/server_https.go @@ -4,14 +4,17 @@ import ( "context" "crypto/tls" "fmt" + stdlog "log" "net" "net/http" "strconv" "time" "github.com/coredns/caddy" + "github.com/coredns/coredns/plugin/metrics/vars" "github.com/coredns/coredns/plugin/pkg/dnsutil" "github.com/coredns/coredns/plugin/pkg/doh" + clog "github.com/coredns/coredns/plugin/pkg/log" "github.com/coredns/coredns/plugin/pkg/response" "github.com/coredns/coredns/plugin/pkg/reuseport" "github.com/coredns/coredns/plugin/pkg/transport" @@ -26,6 +29,18 @@ type ServerHTTPS struct { validRequest func(*http.Request) bool } +// loggerAdapter is a simple adapter around CoreDNS logger made to implement io.Writer in order to log errors from HTTP server +type loggerAdapter struct { +} + +func (l *loggerAdapter) Write(p []byte) (n int, err error) { + clog.Debug(string(p)) + return len(p), nil +} + +// HTTPRequestKey is the context key for the current processed HTTP request (if current processed request was done over DOH) +type HTTPRequestKey struct{} + // NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it. func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) { s, err := NewServer(addr, group) @@ -35,9 +50,11 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) { // The *tls* plugin must make sure that multiple conflicting // TLS configuration returns an error: it can only be specified once. var tlsConfig *tls.Config - for _, conf := range s.zones { - // Should we error if some configs *don't* have TLS? - tlsConfig = conf.TLSConfig + for _, z := range s.zones { + for _, conf := range z { + // Should we error if some configs *don't* have TLS? + tlsConfig = conf.TLSConfig + } } // http/2 is recommended when using DoH. We need to specify it in next protos @@ -48,8 +65,10 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) { // Use a custom request validation func or use the standard DoH path check. var validator func(*http.Request) bool - for _, conf := range s.zones { - validator = conf.HTTPRequestValidateFunc + for _, z := range s.zones { + for _, conf := range z { + validator = conf.HTTPRequestValidateFunc + } } if validator == nil { validator = func(r *http.Request) bool { return r.URL.Path == doh.Path } @@ -59,6 +78,7 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) { ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, + ErrorLog: stdlog.New(&loggerAdapter{}, "", 0), } sh := &ServerHTTPS{ Server: s, tlsConfig: tlsConfig, httpsServer: srv, validRequest: validator, @@ -88,7 +108,6 @@ func (s *ServerHTTPS) ServePacket(p net.PacketConn) error { return nil } // Listen implements caddy.TCPServer interface. func (s *ServerHTTPS) Listen() (net.Listener, error) { - l, err := reuseport.Listen("tcp", s.Addr[len(transport.HTTPS+"://"):]) if err != nil { return nil, err @@ -125,15 +144,16 @@ func (s *ServerHTTPS) Stop() error { // ServeHTTP is the handler that gets the HTTP request and converts to the dns format, calls the plugin // chain, converts it back and write it to the client. func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if !s.validRequest(r) { http.Error(w, "", http.StatusNotFound) + s.countResponse(http.StatusNotFound) return } msg, err := doh.RequestToMsg(r) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) + s.countResponse(http.StatusBadRequest) return } @@ -150,6 +170,7 @@ func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) { // We should expect a packet to be returned that we can send to the client. ctx := context.WithValue(context.Background(), Key{}, s.Server) ctx = context.WithValue(ctx, LoopKey{}, 0) + ctx = context.WithValue(ctx, HTTPRequestKey{}, r) s.ServeDNS(ctx, dw, msg) // See section 4.2.1 of RFC 8484. @@ -157,6 +178,7 @@ func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) { // handler has not provided any response message. if dw.Msg == nil { http.Error(w, "No response", http.StatusInternalServerError) + s.countResponse(http.StatusInternalServerError) return } @@ -169,10 +191,15 @@ func (s *ServerHTTPS) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%f", age.Seconds())) w.Header().Set("Content-Length", strconv.Itoa(len(buf))) w.WriteHeader(http.StatusOK) + s.countResponse(http.StatusOK) w.Write(buf) } +func (s *ServerHTTPS) countResponse(status int) { + vars.HTTPSResponsesCount.WithLabelValues(s.Addr, strconv.Itoa(status)).Inc() +} + // Shutdown stops the server (non gracefully). func (s *ServerHTTPS) Shutdown() error { if s.httpsServer != nil { diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/server_tls.go b/vendor/github.com/coredns/coredns/core/dnsserver/server_tls.go index 1c53c4e3..6fff61d5 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/server_tls.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/server_tls.go @@ -28,9 +28,11 @@ func NewServerTLS(addr string, group []*Config) (*ServerTLS, error) { // The *tls* plugin must make sure that multiple conflicting // TLS configuration returns an error: it can only be specified once. var tlsConfig *tls.Config - for _, conf := range s.zones { - // Should we error if some configs *don't* have TLS? - tlsConfig = conf.TLSConfig + for _, z := range s.zones { + for _, conf := range z { + // Should we error if some configs *don't* have TLS? + tlsConfig = conf.TLSConfig + } } return &ServerTLS{Server: s, tlsConfig: tlsConfig}, nil diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/view.go b/vendor/github.com/coredns/coredns/core/dnsserver/view.go new file mode 100644 index 00000000..ac797839 --- /dev/null +++ b/vendor/github.com/coredns/coredns/core/dnsserver/view.go @@ -0,0 +1,20 @@ +package dnsserver + +import ( + "context" + + "github.com/coredns/coredns/request" +) + +// Viewer - If Viewer is implemented by a plugin in a server block, its Filter() +// is added to the server block's filter functions when starting the server. When a running server +// serves a DNS request, it will route the request to the first Config (server block) that passes +// all its filter functions. +type Viewer interface { + // Filter returns true if the server should use the server block in which the implementing plugin resides, and the + // name of the view for metrics logging. + Filter(ctx context.Context, req *request.Request) bool + + // ViewName returns the name of the view + ViewName() string +} diff --git a/vendor/github.com/coredns/coredns/core/dnsserver/zdirectives.go b/vendor/github.com/coredns/coredns/core/dnsserver/zdirectives.go index bca21718..38425fb0 100644 --- a/vendor/github.com/coredns/coredns/core/dnsserver/zdirectives.go +++ b/vendor/github.com/coredns/coredns/core/dnsserver/zdirectives.go @@ -34,6 +34,7 @@ var Directives = []string{ "any", "chaos", "loadbalance", + "tsig", "cache", "rewrite", "header", @@ -59,4 +60,5 @@ var Directives = []string{ "whoami", "on", "sign", + "view", } diff --git a/vendor/github.com/coredns/coredns/coremain/run.go b/vendor/github.com/coredns/coredns/coremain/run.go index ff5c5ed5..fa765788 100644 --- a/vendor/github.com/coredns/coredns/coremain/run.go +++ b/vendor/github.com/coredns/coredns/coremain/run.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "os" + "path/filepath" "runtime" "strings" @@ -95,7 +96,7 @@ func confLoader(serverType string) (caddy.Input, error) { return caddy.CaddyfileFromPipe(os.Stdin, serverType) } - contents, err := os.ReadFile(conf) + contents, err := os.ReadFile(filepath.Clean(conf)) if err != nil { return nil, err } diff --git a/vendor/github.com/coredns/coredns/coremain/version.go b/vendor/github.com/coredns/coredns/coremain/version.go index b029b4ab..7578079c 100644 --- a/vendor/github.com/coredns/coredns/coremain/version.go +++ b/vendor/github.com/coredns/coredns/coremain/version.go @@ -2,7 +2,7 @@ package coremain // Various CoreDNS constants. const ( - CoreVersion = "1.8.7" + CoreVersion = "1.10.0" coreName = "CoreDNS" serverType = "dns" ) diff --git a/vendor/github.com/coredns/coredns/pb/Makefile b/vendor/github.com/coredns/coredns/pb/Makefile index a666d6c1..7e8cdaf2 100644 --- a/vendor/github.com/coredns/coredns/pb/Makefile +++ b/vendor/github.com/coredns/coredns/pb/Makefile @@ -2,11 +2,18 @@ # from: https://github.com/golang/protobuf to make this work. # The generate dns.pb.go is checked into git, so for normal builds we don't need # to run this generation step. +# Note: The following has been used when regenerate pb: +# curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip +# go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1 +# go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 +# export PATH="$PATH:$(go env GOPATH)/bin" +# rm pb/dns.pb.go pb/dns_grpc.pb.go +# make pb all: dns.pb.go dns.pb.go: dns.proto - protoc --go_out=plugins=grpc:. dns.proto + protoc --go_out=. --go-grpc_out=. dns.proto .PHONY: clean clean: diff --git a/vendor/github.com/coredns/coredns/pb/dns.pb.go b/vendor/github.com/coredns/coredns/pb/dns.pb.go index 919f0e7d..e2a311cc 100644 --- a/vendor/github.com/coredns/coredns/pb/dns.pb.go +++ b/vendor/github.com/coredns/coredns/pb/dns.pb.go @@ -1,156 +1,147 @@ // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.4 // source: dns.proto package pb import ( - context "context" - fmt "fmt" - math "math" - - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -/* Miek: disabled this manually, because I don't know what the heck */ -/* -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package -*/ +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) type DnsPacket struct { - Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` } -func (m *DnsPacket) Reset() { *m = DnsPacket{} } -func (m *DnsPacket) String() string { return proto.CompactTextString(m) } -func (*DnsPacket) ProtoMessage() {} +func (x *DnsPacket) Reset() { + *x = DnsPacket{} + if protoimpl.UnsafeEnabled { + mi := &file_dns_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DnsPacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsPacket) ProtoMessage() {} + +func (x *DnsPacket) ProtoReflect() protoreflect.Message { + mi := &file_dns_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DnsPacket.ProtoReflect.Descriptor instead. func (*DnsPacket) Descriptor() ([]byte, []int) { - return fileDescriptor_638ff8d8aaf3d8ae, []int{0} + return file_dns_proto_rawDescGZIP(), []int{0} } -func (m *DnsPacket) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DnsPacket.Unmarshal(m, b) -} -func (m *DnsPacket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DnsPacket.Marshal(b, m, deterministic) -} -func (m *DnsPacket) XXX_Merge(src proto.Message) { - xxx_messageInfo_DnsPacket.Merge(m, src) -} -func (m *DnsPacket) XXX_Size() int { - return xxx_messageInfo_DnsPacket.Size(m) -} -func (m *DnsPacket) XXX_DiscardUnknown() { - xxx_messageInfo_DnsPacket.DiscardUnknown(m) -} - -var xxx_messageInfo_DnsPacket proto.InternalMessageInfo - -func (m *DnsPacket) GetMsg() []byte { - if m != nil { - return m.Msg +func (x *DnsPacket) GetMsg() []byte { + if x != nil { + return x.Msg } return nil } -func init() { - proto.RegisterType((*DnsPacket)(nil), "coredns.dns.DnsPacket") +var File_dns_proto protoreflect.FileDescriptor + +var file_dns_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x6f, 0x72, + 0x65, 0x64, 0x6e, 0x73, 0x2e, 0x64, 0x6e, 0x73, 0x22, 0x1d, 0x0a, 0x09, 0x44, 0x6e, 0x73, 0x50, + 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x45, 0x0a, 0x0a, 0x44, 0x6e, 0x73, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, + 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x6e, 0x73, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6e, 0x73, + 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x64, 0x6e, 0x73, + 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6e, 0x73, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x06, + 0x5a, 0x04, 0x2e, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } -func init() { proto.RegisterFile("dns.proto", fileDescriptor_638ff8d8aaf3d8ae) } +var ( + file_dns_proto_rawDescOnce sync.Once + file_dns_proto_rawDescData = file_dns_proto_rawDesc +) -var fileDescriptor_638ff8d8aaf3d8ae = []byte{ - // 120 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0xc9, 0x2b, 0xd6, - 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4e, 0xce, 0x2f, 0x4a, 0x05, 0x71, 0x53, 0xf2, 0x8a, - 0x95, 0x64, 0xb9, 0x38, 0x5d, 0xf2, 0x8a, 0x03, 0x12, 0x93, 0xb3, 0x53, 0x4b, 0x84, 0x04, 0xb8, - 0x98, 0x73, 0x8b, 0xd3, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x40, 0x4c, 0x23, 0x57, 0x2e, - 0x2e, 0x97, 0xbc, 0xe2, 0xe0, 0xd4, 0xa2, 0xb2, 0xcc, 0xe4, 0x54, 0x21, 0x73, 0x2e, 0xd6, 0xc0, - 0xd2, 0xd4, 0xa2, 0x4a, 0x21, 0x31, 0x3d, 0x24, 0x33, 0xf4, 0xe0, 0x06, 0x48, 0xe1, 0x10, 0x77, - 0x62, 0x89, 0x62, 0x2a, 0x48, 0x4a, 0x62, 0x03, 0xdb, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, - 0xf5, 0xd1, 0x3f, 0x26, 0x8c, 0x00, 0x00, 0x00, +func file_dns_proto_rawDescGZIP() []byte { + file_dns_proto_rawDescOnce.Do(func() { + file_dns_proto_rawDescData = protoimpl.X.CompressGZIP(file_dns_proto_rawDescData) + }) + return file_dns_proto_rawDescData } -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// DnsServiceClient is the client API for DnsService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type DnsServiceClient interface { - Query(ctx context.Context, in *DnsPacket, opts ...grpc.CallOption) (*DnsPacket, error) +var file_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_dns_proto_goTypes = []interface{}{ + (*DnsPacket)(nil), // 0: coredns.dns.DnsPacket +} +var file_dns_proto_depIdxs = []int32{ + 0, // 0: coredns.dns.DnsService.Query:input_type -> coredns.dns.DnsPacket + 0, // 1: coredns.dns.DnsService.Query:output_type -> coredns.dns.DnsPacket + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } -type dnsServiceClient struct { - cc *grpc.ClientConn -} - -func NewDnsServiceClient(cc *grpc.ClientConn) DnsServiceClient { - return &dnsServiceClient{cc} -} - -func (c *dnsServiceClient) Query(ctx context.Context, in *DnsPacket, opts ...grpc.CallOption) (*DnsPacket, error) { - out := new(DnsPacket) - err := c.cc.Invoke(ctx, "/coredns.dns.DnsService/Query", in, out, opts...) - if err != nil { - return nil, err +func init() { file_dns_proto_init() } +func file_dns_proto_init() { + if File_dns_proto != nil { + return } - return out, nil -} - -// DnsServiceServer is the server API for DnsService service. -type DnsServiceServer interface { - Query(context.Context, *DnsPacket) (*DnsPacket, error) -} - -func RegisterDnsServiceServer(s *grpc.Server, srv DnsServiceServer) { - s.RegisterService(&_DnsService_serviceDesc, srv) -} - -func _DnsService_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DnsPacket) - if err := dec(in); err != nil { - return nil, err + if !protoimpl.UnsafeEnabled { + file_dns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DnsPacket); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } - if interceptor == nil { - return srv.(DnsServiceServer).Query(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/coredns.dns.DnsService/Query", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DnsServiceServer).Query(ctx, req.(*DnsPacket)) - } - return interceptor(ctx, in, info, handler) -} - -var _DnsService_serviceDesc = grpc.ServiceDesc{ - ServiceName: "coredns.dns.DnsService", - HandlerType: (*DnsServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Query", - Handler: _DnsService_Query_Handler, + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_dns_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "dns.proto", + GoTypes: file_dns_proto_goTypes, + DependencyIndexes: file_dns_proto_depIdxs, + MessageInfos: file_dns_proto_msgTypes, + }.Build() + File_dns_proto = out.File + file_dns_proto_rawDesc = nil + file_dns_proto_goTypes = nil + file_dns_proto_depIdxs = nil } diff --git a/vendor/github.com/coredns/coredns/pb/dns.proto b/vendor/github.com/coredns/coredns/pb/dns.proto index 8461f01e..ee24cb0b 100644 --- a/vendor/github.com/coredns/coredns/pb/dns.proto +++ b/vendor/github.com/coredns/coredns/pb/dns.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package coredns.dns; -option go_package = "pb"; +option go_package = ".;pb"; message DnsPacket { bytes msg = 1; diff --git a/vendor/github.com/coredns/coredns/pb/dns_grpc.pb.go b/vendor/github.com/coredns/coredns/pb/dns_grpc.pb.go new file mode 100644 index 00000000..6ff3faf1 --- /dev/null +++ b/vendor/github.com/coredns/coredns/pb/dns_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.19.4 +// source: dns.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// DnsServiceClient is the client API for DnsService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DnsServiceClient interface { + Query(ctx context.Context, in *DnsPacket, opts ...grpc.CallOption) (*DnsPacket, error) +} + +type dnsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDnsServiceClient(cc grpc.ClientConnInterface) DnsServiceClient { + return &dnsServiceClient{cc} +} + +func (c *dnsServiceClient) Query(ctx context.Context, in *DnsPacket, opts ...grpc.CallOption) (*DnsPacket, error) { + out := new(DnsPacket) + err := c.cc.Invoke(ctx, "/coredns.dns.DnsService/Query", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DnsServiceServer is the server API for DnsService service. +// All implementations must embed UnimplementedDnsServiceServer +// for forward compatibility +type DnsServiceServer interface { + Query(context.Context, *DnsPacket) (*DnsPacket, error) + mustEmbedUnimplementedDnsServiceServer() +} + +// UnimplementedDnsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDnsServiceServer struct { +} + +func (UnimplementedDnsServiceServer) Query(context.Context, *DnsPacket) (*DnsPacket, error) { + return nil, status.Errorf(codes.Unimplemented, "method Query not implemented") +} +func (UnimplementedDnsServiceServer) mustEmbedUnimplementedDnsServiceServer() {} + +// UnsafeDnsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DnsServiceServer will +// result in compilation errors. +type UnsafeDnsServiceServer interface { + mustEmbedUnimplementedDnsServiceServer() +} + +func RegisterDnsServiceServer(s grpc.ServiceRegistrar, srv DnsServiceServer) { + s.RegisterService(&DnsService_ServiceDesc, srv) +} + +func _DnsService_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DnsPacket) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DnsServiceServer).Query(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/coredns.dns.DnsService/Query", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DnsServiceServer).Query(ctx, req.(*DnsPacket)) + } + return interceptor(ctx, in, info, handler) +} + +// DnsService_ServiceDesc is the grpc.ServiceDesc for DnsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DnsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "coredns.dns.DnsService", + HandlerType: (*DnsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Query", + Handler: _DnsService_Query_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dns.proto", +} diff --git a/vendor/github.com/coredns/coredns/plugin/backend_lookup.go b/vendor/github.com/coredns/coredns/plugin/backend_lookup.go index b2bfa7c6..0887bb4d 100644 --- a/vendor/github.com/coredns/coredns/plugin/backend_lookup.go +++ b/vendor/github.com/coredns/coredns/plugin/backend_lookup.go @@ -14,22 +14,22 @@ import ( ) // A returns A records from Backend or an error. -func A(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { +func A(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, truncated bool, err error) { services, err := checkForApex(ctx, b, zone, state, opt) if err != nil { - return nil, err + return nil, false, err } dup := make(map[string]struct{}) for _, serv := range services { - what, ip := serv.HostType() switch what { case dns.TypeCNAME: if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those + // in etcd/skydns w.x CNAME x is also direct loop due to the "recursive" nature of search results continue } @@ -44,7 +44,7 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) { state1 := state.NewWithQuestion(serv.Host, state.QType()) state1.Zone = zone - nextRecords, err := A(ctx, b, zone, state1, append(previousRecords, newRecord), opt) + nextRecords, tc, err := A(ctx, b, zone, state1, append(previousRecords, newRecord), opt) if err == nil { // Not only have we found something we should add the CNAME and the IP addresses. @@ -53,6 +53,9 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request records = append(records, nextRecords...) } } + if tc { + truncated = true + } continue } // This means we can not complete the CNAME, try to look else where. @@ -62,6 +65,9 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request if e1 != nil { continue } + if m1.Truncated { + truncated = true + } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) @@ -77,20 +83,19 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request // nada } } - return records, nil + return records, truncated, nil } // AAAA returns AAAA records from Backend or an error. -func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { +func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, truncated bool, err error) { services, err := checkForApex(ctx, b, zone, state, opt) if err != nil { - return nil, err + return nil, false, err } dup := make(map[string]struct{}) for _, serv := range services { - what, ip := serv.HostType() switch what { @@ -98,6 +103,7 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ // Try to resolve as CNAME if it's not an IP, but only if we don't create loops. if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those + // in etcd/skydns w.x CNAME x is also direct loop due to the "recursive" nature of search results continue } @@ -112,7 +118,7 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) { state1 := state.NewWithQuestion(serv.Host, state.QType()) state1.Zone = zone - nextRecords, err := AAAA(ctx, b, zone, state1, append(previousRecords, newRecord), opt) + nextRecords, tc, err := AAAA(ctx, b, zone, state1, append(previousRecords, newRecord), opt) if err == nil { // Not only have we found something we should add the CNAME and the IP addresses. @@ -121,6 +127,9 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ records = append(records, nextRecords...) } } + if tc { + truncated = true + } continue } // This means we can not complete the CNAME, try to look else where. @@ -129,6 +138,9 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ if e1 != nil { continue } + if m1.Truncated { + truncated = true + } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) @@ -145,7 +157,7 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ } } } - return records, nil + return records, truncated, nil } // SRV returns SRV records from the Backend. @@ -223,7 +235,7 @@ func SRV(ctx context.Context, b ServiceBackend, zone string, state request.Reque // Internal name, we should have some info on them, either v4 or v6 // Clients expect a complete answer, because we are a recursor in their view. state1 := state.NewWithQuestion(srv.Target, dns.TypeA) - addr, e1 := A(ctx, b, zone, state1, nil, opt) + addr, _, e1 := A(ctx, b, zone, state1, nil, opt) if e1 == nil { extra = append(extra, addr...) } @@ -289,7 +301,7 @@ func MX(ctx context.Context, b ServiceBackend, zone string, state request.Reques } // Internal name state1 := state.NewWithQuestion(mx.Mx, dns.TypeA) - addr, e1 := A(ctx, b, zone, state1, nil, opt) + addr, _, e1 := A(ctx, b, zone, state1, nil, opt) if e1 == nil { extra = append(extra, addr...) } @@ -329,23 +341,22 @@ func CNAME(ctx context.Context, b ServiceBackend, zone string, state request.Req } // TXT returns TXT records from Backend or an error. -func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { - - services, err := b.Services(ctx, state, true, opt) +func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, truncated bool, err error) { + services, err := b.Services(ctx, state, false, opt) if err != nil { - return nil, err + return nil, false, err } dup := make(map[string]struct{}) for _, serv := range services { - what, _ := serv.HostType() switch what { case dns.TypeCNAME: if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those + // in etcd/skydns w.x CNAME x is also direct loop due to the "recursive" nature of search results continue } @@ -360,8 +371,10 @@ func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Reque if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) { state1 := state.NewWithQuestion(serv.Host, state.QType()) state1.Zone = zone - nextRecords, err := TXT(ctx, b, zone, state1, append(previousRecords, newRecord), opt) - + nextRecords, tc, err := TXT(ctx, b, zone, state1, append(previousRecords, newRecord), opt) + if tc { + truncated = true + } if err == nil { // Not only have we found something we should add the CNAME and the IP addresses. if len(nextRecords) > 0 { @@ -384,15 +397,14 @@ func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Reque continue case dns.TypeTXT: - if _, ok := dup[serv.Host]; !ok { - dup[serv.Host] = struct{}{} - return append(records, serv.NewTXT(state.QName())), nil + if _, ok := dup[serv.Text]; !ok { + dup[serv.Text] = struct{}{} + records = append(records, serv.NewTXT(state.QName())) } - } } - return records, nil + return records, truncated, nil } // PTR returns the PTR records from the backend, only services that have a domain name as host are included. @@ -490,7 +502,6 @@ func BackendError(ctx context.Context, b ServiceBackend, zone string, rcode int, } func newAddress(s msg.Service, name string, ip net.IP, what uint16) dns.RR { - hdr := dns.RR_Header{Name: name, Rrtype: what, Class: dns.ClassINET, Ttl: s.TTL} if what == dns.TypeA { diff --git a/vendor/github.com/coredns/coredns/plugin/cache/README.md b/vendor/github.com/coredns/coredns/plugin/cache/README.md index f37597ee..562f5bd9 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/README.md +++ b/vendor/github.com/coredns/coredns/plugin/cache/README.md @@ -37,7 +37,9 @@ cache [TTL] [ZONES...] { success CAPACITY [TTL] [MINTTL] denial CAPACITY [TTL] [MINTTL] prefetch AMOUNT [[DURATION] [PERCENTAGE%]] - serve_stale [DURATION] + serve_stale [DURATION] [REFRESH_MODE] + servfail DURATION + disable success|denial [ZONES...] } ~~~ @@ -54,10 +56,20 @@ cache [TTL] [ZONES...] { **DURATION** defaults to 1m. Prefetching will happen when the TTL drops below **PERCENTAGE**, which defaults to `10%`, or latest 1 second before TTL expiration. Values should be in the range `[10%, 90%]`. Note the percent sign is mandatory. **PERCENTAGE** is treated as an `int`. -* `serve_stale`, when serve\_stale is set, cache always will serve an expired entry to a client if there is one - available. When this happens, cache will attempt to refresh the cache entry after sending the expired cache - entry to the client. The responses have a TTL of 0. **DURATION** is how far back to consider - stale responses as fresh. The default duration is 1h. +* `serve_stale`, when serve\_stale is set, cache will always serve an expired entry to a client if there is one + available as long as it has not been expired for longer than **DURATION** (default 1 hour). By default, the _cache_ plugin will + attempt to refresh the cache entry after sending the expired cache entry to the client. The + responses have a TTL of 0. **REFRESH_MODE** controls the timing of the expired cache entry refresh. + `verify` will first verify that an entry is still unavailable from the source before sending the expired entry to the client. + `immediate` will immediately send the expired entry to the client before + checking to see if the entry is available from the source. **REFRESH_MODE** defaults to `immediate`. Setting this + value to `verify` can lead to increased latency when serving stale responses, but will prevent stale entries + from ever being served if an updated response can be retrieved from the source. +* `servfail` cache SERVFAIL responses for **DURATION**. Setting **DURATION** to 0 will disable caching of SERVFAIL + responses. If this option is not set, SERVFAIL responses will be cached for 5 seconds. **DURATION** may not be + greater than 5 minutes. +* `disable` disable the success or denial cache for the listed **ZONES**. If no **ZONES** are given, the specified + cache will be disabled for all zones. ## Capacity and Eviction @@ -73,14 +85,14 @@ Entries with 0 TTL will remain in the cache until randomly evicted when the shar If monitoring is enabled (via the *prometheus* plugin) then the following metrics are exported: -* `coredns_cache_entries{server, type}` - Total elements in the cache by cache type. -* `coredns_cache_hits_total{server, type}` - Counter of cache hits by cache type. -* `coredns_cache_misses_total{server}` - Counter of cache misses. - Deprecated, derive misses from cache hits/requests counters. -* `coredns_cache_requests_total{server}` - Counter of cache requests. -* `coredns_cache_prefetch_total{server}` - Counter of times the cache has prefetched a cached item. -* `coredns_cache_drops_total{server}` - Counter of responses excluded from the cache due to request/response question name mismatch. -* `coredns_cache_served_stale_total{server}` - Counter of requests served from stale cache entries. -* `coredns_cache_evictions_total{server, type}` - Counter of cache evictions. +* `coredns_cache_entries{server, type, zones, view}` - Total elements in the cache by cache type. +* `coredns_cache_hits_total{server, type, zones, view}` - Counter of cache hits by cache type. +* `coredns_cache_misses_total{server, zones, view}` - Counter of cache misses. - Deprecated, derive misses from cache hits/requests counters. +* `coredns_cache_requests_total{server, zones, view}` - Counter of cache requests. +* `coredns_cache_prefetch_total{server, zones, view}` - Counter of times the cache has prefetched a cached item. +* `coredns_cache_drops_total{server, zones, view}` - Counter of responses excluded from the cache due to request/response question name mismatch. +* `coredns_cache_served_stale_total{server, zones, view}` - Counter of requests served from stale cache entries. +* `coredns_cache_evictions_total{server, type, zones, view}` - Counter of cache evictions. Cache types are either "denial" or "success". `Server` is the server handling the request, see the prometheus plugin for documentation. @@ -115,3 +127,13 @@ example.org { } } ~~~ + +Enable caching for `example.org`, but do not cache denials in `sub.example.org`: + +~~~ corefile +example.org { + cache { + disable denial sub.example.org + } +} +~~~ \ No newline at end of file diff --git a/vendor/github.com/coredns/coredns/plugin/cache/cache.go b/vendor/github.com/coredns/coredns/plugin/cache/cache.go index 54e5e4db..b4767937 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/cache.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/cache.go @@ -21,6 +21,9 @@ type Cache struct { Next plugin.Handler Zones []string + zonesMetricLabel string + viewMetricLabel string + ncache *cache.Cache ncap int nttl time.Duration @@ -30,13 +33,20 @@ type Cache struct { pcap int pttl time.Duration minpttl time.Duration + failttl time.Duration // TTL for caching SERVFAIL responses // Prefetch. prefetch int duration time.Duration percentage int - staleUpTo time.Duration + // Stale serve + staleUpTo time.Duration + verifyStale bool + + // Positive/negative zone exceptions + pexcept []string + nexcept []string // Testing. now func() time.Time @@ -55,6 +65,7 @@ func New() *Cache { ncache: cache.New(defaultCap), nttl: maxNTTL, minnttl: minNTTL, + failttl: minNTTL, prefetch: 0, duration: 1 * time.Minute, percentage: 10, @@ -105,8 +116,14 @@ type ResponseWriter struct { server string // Server handling the request. do bool // When true the original request had the DO bit set. + ad bool // When true the original request had the AD bit set. prefetch bool // When true write nothing back to the client. remoteAddr net.Addr + + wildcardFunc func() string // function to retrieve wildcard name that synthesized the result. + + pexcept []string // positive zone exceptions + nexcept []string // negative zone exceptions } // newPrefetchResponseWriter returns a Cache ResponseWriter to be used in @@ -153,8 +170,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error { if mt == response.NameError || mt == response.NoData { duration = computeTTL(msgTTL, w.minnttl, w.nttl) } else if mt == response.ServerError { - // use default ttl which is 5s - duration = minTTL + duration = w.failttl } else { duration = computeTTL(msgTTL, w.minpttl, w.pttl) } @@ -162,11 +178,11 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error { if hasKey && duration > 0 { if w.state.Match(res) { w.set(res, key, mt, duration) - cacheSize.WithLabelValues(w.server, Success).Set(float64(w.pcache.Len())) - cacheSize.WithLabelValues(w.server, Denial).Set(float64(w.ncache.Len())) + cacheSize.WithLabelValues(w.server, Success, w.zonesMetricLabel, w.viewMetricLabel).Set(float64(w.pcache.Len())) + cacheSize.WithLabelValues(w.server, Denial, w.zonesMetricLabel, w.viewMetricLabel).Set(float64(w.ncache.Len())) } else { // Don't log it, but increment counter - cacheDrops.WithLabelValues(w.server).Inc() + cacheDrops.WithLabelValues(w.server, w.zonesMetricLabel, w.viewMetricLabel).Inc() } } @@ -181,8 +197,10 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error { res.Ns = filterRRSlice(res.Ns, ttl, w.do, false) res.Extra = filterRRSlice(res.Extra, ttl, w.do, false) - if !w.do { - res.AuthenticatedData = false // unset AD bit if client is not OK with DNSSEC + if !w.do && !w.ad { + // unset AD bit if requester is not OK with DNSSEC + // But retain AD bit if requester set the AD bit in the request, per RFC6840 5.7-5.8 + res.AuthenticatedData = false } return w.ResponseWriter.WriteMsg(res) @@ -193,9 +211,16 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration // and key is valid switch mt { case response.NoError, response.Delegation: + if plugin.Zones(w.pexcept).Matches(m.Question[0].Name) != "" { + // zone is in exception list, do not cache + return + } i := newItem(m, w.now(), duration) + if w.wildcardFunc != nil { + i.wildcard = w.wildcardFunc() + } if w.pcache.Add(key, i) { - evictions.WithLabelValues(w.server, Success).Inc() + evictions.WithLabelValues(w.server, Success, w.zonesMetricLabel, w.viewMetricLabel).Inc() } // when pre-fetching, remove the negative cache entry if it exists if w.prefetch { @@ -203,9 +228,16 @@ func (w *ResponseWriter) set(m *dns.Msg, key uint64, mt response.Type, duration } case response.NameError, response.NoData, response.ServerError: + if plugin.Zones(w.nexcept).Matches(m.Question[0].Name) != "" { + // zone is in exception list, do not cache + return + } i := newItem(m, w.now(), duration) + if w.wildcardFunc != nil { + i.wildcard = w.wildcardFunc() + } if w.ncache.Add(key, i) { - evictions.WithLabelValues(w.server, Denial).Inc() + evictions.WithLabelValues(w.server, Denial, w.zonesMetricLabel, w.viewMetricLabel).Inc() } case response.OtherError: @@ -225,6 +257,33 @@ func (w *ResponseWriter) Write(buf []byte) (int, error) { return n, err } +// verifyStaleResponseWriter is a response writer that only writes messages if they should replace a +// stale cache entry, and otherwise discards them. +type verifyStaleResponseWriter struct { + *ResponseWriter + refreshed bool // set to true if the last WriteMsg wrote to ResponseWriter, false otherwise. +} + +// newVerifyStaleResponseWriter returns a ResponseWriter to be used when verifying stale cache +// entries. It only forward writes if an entry was successfully refreshed according to RFC8767, +// section 4 (response is NoError or NXDomain), and ignores any other response. +func newVerifyStaleResponseWriter(w *ResponseWriter) *verifyStaleResponseWriter { + return &verifyStaleResponseWriter{ + w, + false, + } +} + +// WriteMsg implements the dns.ResponseWriter interface. +func (w *verifyStaleResponseWriter) WriteMsg(res *dns.Msg) error { + w.refreshed = false + if res.Rcode == dns.RcodeSuccess || res.Rcode == dns.RcodeNameError { + w.refreshed = true + return w.ResponseWriter.WriteMsg(res) // stores to the cache and send to client + } + return nil // else discard +} + const ( maxTTL = dnsutil.MaximumDefaulTTL minTTL = dnsutil.MinimalDefaultTTL diff --git a/vendor/github.com/coredns/coredns/plugin/cache/fuzz.go b/vendor/github.com/coredns/coredns/plugin/cache/fuzz.go index 18e98fa9..43f4d260 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/fuzz.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/fuzz.go @@ -1,4 +1,4 @@ -// +build gofuzz +//go:build gofuzz package cache diff --git a/vendor/github.com/coredns/coredns/plugin/cache/handler.go b/vendor/github.com/coredns/coredns/plugin/cache/handler.go index 418e4086..ec2135e8 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/handler.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/handler.go @@ -6,6 +6,7 @@ import ( "time" "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/metadata" "github.com/coredns/coredns/plugin/metrics" "github.com/coredns/coredns/request" @@ -17,6 +18,7 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) rc := r.Copy() // We potentially modify r, to prevent other plugins from seeing this (r is a pointer), copy r into rc. state := request.Request{W: w, Req: rc} do := state.Do() + ad := r.AuthenticatedData zone := plugin.Zones(c.Zones).Matches(state.Name()) if zone == "" { @@ -35,31 +37,59 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ttl := 0 i := c.getIgnoreTTL(now, state, server) - if i != nil { - ttl = i.ttl(now) - } if i == nil { - crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do} + crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, ad: ad, + nexcept: c.nexcept, pexcept: c.pexcept, wildcardFunc: wildcardFunc(ctx)} return c.doRefresh(ctx, state, crr) } + ttl = i.ttl(now) if ttl < 0 { - servedStale.WithLabelValues(server).Inc() + // serve stale behavior + if c.verifyStale { + crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do} + cw := newVerifyStaleResponseWriter(crr) + ret, err := c.doRefresh(ctx, state, cw) + if cw.refreshed { + return ret, err + } + } + // Adjust the time to get a 0 TTL in the reply built from a stale item. now = now.Add(time.Duration(ttl) * time.Second) - cw := newPrefetchResponseWriter(server, state, c) - go c.doPrefetch(ctx, state, cw, i, now) + if !c.verifyStale { + cw := newPrefetchResponseWriter(server, state, c) + go c.doPrefetch(ctx, state, cw, i, now) + } + servedStale.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc() } else if c.shouldPrefetch(i, now) { cw := newPrefetchResponseWriter(server, state, c) go c.doPrefetch(ctx, state, cw, i, now) } - resp := i.toMsg(r, now, do) - w.WriteMsg(resp) + if i.wildcard != "" { + // Set wildcard source record name to metadata + metadata.SetValueFunc(ctx, "zone/wildcard", func() string { + return i.wildcard + }) + } + + resp := i.toMsg(r, now, do, ad) + w.WriteMsg(resp) return dns.RcodeSuccess, nil } +func wildcardFunc(ctx context.Context) func() string { + return func() string { + // Get wildcard source record name from metadata + if f := metadata.ValueFunc(ctx, "zone/wildcard"); f != nil { + return f() + } + return "" + } +} + func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *ResponseWriter, i *item, now time.Time) { - cachePrefetches.WithLabelValues(cw.server).Inc() + cachePrefetches.WithLabelValues(cw.server, c.zonesMetricLabel, c.viewMetricLabel).Inc() c.doRefresh(ctx, state, cw) // When prefetching we loose the item i, and with it the frequency @@ -70,7 +100,7 @@ func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *Respo } } -func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw *ResponseWriter) (int, error) { +func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw dns.ResponseWriter) (int, error) { if !state.Do() { setDo(state.Req) } @@ -89,43 +119,28 @@ func (c *Cache) shouldPrefetch(i *item, now time.Time) bool { // Name implements the Handler interface. func (c *Cache) Name() string { return "cache" } -func (c *Cache) get(now time.Time, state request.Request, server string) (*item, bool) { - k := hash(state.Name(), state.QType()) - cacheRequests.WithLabelValues(server).Inc() - - if i, ok := c.ncache.Get(k); ok && i.(*item).ttl(now) > 0 { - cacheHits.WithLabelValues(server, Denial).Inc() - return i.(*item), true - } - - if i, ok := c.pcache.Get(k); ok && i.(*item).ttl(now) > 0 { - cacheHits.WithLabelValues(server, Success).Inc() - return i.(*item), true - } - cacheMisses.WithLabelValues(server).Inc() - return nil, false -} - // getIgnoreTTL unconditionally returns an item if it exists in the cache. func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item { k := hash(state.Name(), state.QType()) - cacheRequests.WithLabelValues(server).Inc() + cacheRequests.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc() if i, ok := c.ncache.Get(k); ok { - ttl := i.(*item).ttl(now) - if ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds())) { - cacheHits.WithLabelValues(server, Denial).Inc() + itm := i.(*item) + ttl := itm.ttl(now) + if itm.matches(state) && (ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds()))) { + cacheHits.WithLabelValues(server, Denial, c.zonesMetricLabel, c.viewMetricLabel).Inc() return i.(*item) } } if i, ok := c.pcache.Get(k); ok { - ttl := i.(*item).ttl(now) - if ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds())) { - cacheHits.WithLabelValues(server, Success).Inc() + itm := i.(*item) + ttl := itm.ttl(now) + if itm.matches(state) && (ttl > 0 || (c.staleUpTo > 0 && -ttl < int(c.staleUpTo.Seconds()))) { + cacheHits.WithLabelValues(server, Success, c.zonesMetricLabel, c.viewMetricLabel).Inc() return i.(*item) } } - cacheMisses.WithLabelValues(server).Inc() + cacheMisses.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc() return nil } diff --git a/vendor/github.com/coredns/coredns/plugin/cache/item.go b/vendor/github.com/coredns/coredns/plugin/cache/item.go index 3b47a3b6..6b51a5ba 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/item.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/item.go @@ -1,20 +1,25 @@ package cache import ( + "strings" "time" "github.com/coredns/coredns/plugin/cache/freq" + "github.com/coredns/coredns/request" "github.com/miekg/dns" ) type item struct { + Name string + QType uint16 Rcode int AuthenticatedData bool RecursionAvailable bool Answer []dns.RR Ns []dns.RR Extra []dns.RR + wildcard string origTTL uint32 stored time.Time @@ -24,6 +29,10 @@ type item struct { func newItem(m *dns.Msg, now time.Time, d time.Duration) *item { i := new(item) + if len(m.Question) != 0 { + i.Name = m.Question[0].Name + i.QType = m.Question[0].Qtype + } i.Rcode = m.Rcode i.AuthenticatedData = m.AuthenticatedData i.RecursionAvailable = m.RecursionAvailable @@ -56,7 +65,7 @@ func newItem(m *dns.Msg, now time.Time, d time.Duration) *item { // So we're forced to always set this to 1; regardless if the answer came from the cache or not. // On newer systems(e.g. ubuntu 16.04 with glib version 2.23), this issue is resolved. // So we may set this bit back to 0 in the future ? -func (i *item) toMsg(m *dns.Msg, now time.Time, do bool) *dns.Msg { +func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg { m1 := new(dns.Msg) m1.SetReply(m) @@ -65,8 +74,10 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool) *dns.Msg { // just set it to true. m1.Authoritative = true m1.AuthenticatedData = i.AuthenticatedData - if !do { - m1.AuthenticatedData = false // when DNSSEC was not wanted, it can't be authenticated data. + if !do && !ad { + // When DNSSEC was not wanted, it can't be authenticated data. + // However, retain the AD bit if the requester set the AD bit, per RFC6840 5.7-5.8 + m1.AuthenticatedData = false } m1.RecursionAvailable = i.RecursionAvailable m1.Rcode = i.Rcode @@ -87,3 +98,10 @@ func (i *item) ttl(now time.Time) int { ttl := int(i.origTTL) - int(now.UTC().Sub(i.stored).Seconds()) return ttl } + +func (i *item) matches(state request.Request) bool { + if state.QType() == i.QType && strings.EqualFold(state.QName(), i.Name) { + return true + } + return false +} diff --git a/vendor/github.com/coredns/coredns/plugin/cache/metrics.go b/vendor/github.com/coredns/coredns/plugin/cache/metrics.go index bb7ce258..77edb028 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/metrics.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/metrics.go @@ -14,54 +14,54 @@ var ( Subsystem: "cache", Name: "entries", Help: "The number of elements in the cache.", - }, []string{"server", "type"}) + }, []string{"server", "type", "zones", "view"}) // cacheRequests is a counter of all requests through the cache. cacheRequests = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "requests_total", Help: "The count of cache requests.", - }, []string{"server"}) + }, []string{"server", "zones", "view"}) // cacheHits is counter of cache hits by cache type. cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "hits_total", Help: "The count of cache hits.", - }, []string{"server", "type"}) + }, []string{"server", "type", "zones", "view"}) // cacheMisses is the counter of cache misses. - Deprecated cacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "misses_total", Help: "The count of cache misses. Deprecated, derive misses from cache hits/requests counters.", - }, []string{"server"}) + }, []string{"server", "zones", "view"}) // cachePrefetches is the number of time the cache has prefetched a cached item. cachePrefetches = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "prefetch_total", Help: "The number of times the cache has prefetched a cached item.", - }, []string{"server"}) + }, []string{"server", "zones", "view"}) // cacheDrops is the number responses that are not cached, because the reply is malformed. cacheDrops = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "drops_total", Help: "The number responses that are not cached, because the reply is malformed.", - }, []string{"server"}) + }, []string{"server", "zones", "view"}) // servedStale is the number of requests served from stale cache entries. servedStale = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "served_stale_total", Help: "The number of requests served from stale cache entries.", - }, []string{"server"}) + }, []string{"server", "zones", "view"}) // evictions is the counter of cache evictions. evictions = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: "cache", Name: "evictions_total", Help: "The count of cache evictions.", - }, []string{"server", "type"}) + }, []string{"server", "type", "zones", "view"}) ) diff --git a/vendor/github.com/coredns/coredns/plugin/cache/setup.go b/vendor/github.com/coredns/coredns/plugin/cache/setup.go index bae4c3d9..6a537d98 100644 --- a/vendor/github.com/coredns/coredns/plugin/cache/setup.go +++ b/vendor/github.com/coredns/coredns/plugin/cache/setup.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "time" "github.com/coredns/caddy" @@ -22,6 +23,12 @@ func setup(c *caddy.Controller) error { if err != nil { return plugin.Error("cache", err) } + + c.OnStartup(func() error { + ca.viewMetricLabel = dnsserver.GetConfig(c).ViewName + return nil + }) + dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { ca.Next = next return ca @@ -165,11 +172,11 @@ func cacheParse(c *caddy.Controller) (*Cache, error) { case "serve_stale": args := c.RemainingArgs() - if len(args) > 1 { + if len(args) > 2 { return nil, c.ArgErr() } ca.staleUpTo = 1 * time.Hour - if len(args) == 1 { + if len(args) > 0 { d, err := time.ParseDuration(args[0]) if err != nil { return nil, err @@ -179,12 +186,67 @@ func cacheParse(c *caddy.Controller) (*Cache, error) { } ca.staleUpTo = d } + ca.verifyStale = false + if len(args) > 1 { + mode := strings.ToLower(args[1]) + if mode != "immediate" && mode != "verify" { + return nil, fmt.Errorf("invalid value for serve_stale refresh mode: %s", mode) + } + ca.verifyStale = mode == "verify" + } + case "servfail": + args := c.RemainingArgs() + if len(args) != 1 { + return nil, c.ArgErr() + } + d, err := time.ParseDuration(args[0]) + if err != nil { + return nil, err + } + if d < 0 { + return nil, errors.New("invalid negative ttl for servfail") + } + if d > 5*time.Minute { + // RFC 2308 prohibits caching SERVFAIL longer than 5 minutes + return nil, errors.New("caching SERVFAIL responses over 5 minutes is not permitted") + } + ca.failttl = d + case "disable": + // disable [success|denial] [zones]... + args := c.RemainingArgs() + if len(args) < 1 { + return nil, c.ArgErr() + } + + var zones []string + if len(args) > 1 { + for _, z := range args[1:] { // args[1:] define the list of zones to disable + nz := plugin.Name(z).Normalize() + if nz == "" { + return nil, fmt.Errorf("invalid disabled zone: %s", z) + } + zones = append(zones, nz) + } + } else { + // if no zones specified, default to root + zones = []string{"."} + } + + switch args[0] { // args[0] defines which cache to disable + case Denial: + ca.nexcept = zones + case Success: + ca.pexcept = zones + default: + return nil, fmt.Errorf("cache type for disable must be %q or %q", Success, Denial) + } default: return nil, c.ArgErr() } } ca.Zones = origins + ca.zonesMetricLabel = strings.Join(origins, ",") ca.pcache = cache.New(ca.pcap) ca.ncache = cache.New(ca.ncap) } diff --git a/vendor/github.com/coredns/coredns/plugin/etcd/msg/path.go b/vendor/github.com/coredns/coredns/plugin/etcd/msg/path.go index bfa45886..2c6cbff0 100644 --- a/vendor/github.com/coredns/coredns/plugin/etcd/msg/path.go +++ b/vendor/github.com/coredns/coredns/plugin/etcd/msg/path.go @@ -22,6 +22,9 @@ func Path(s, prefix string) string { // Domain is the opposite of Path. func Domain(s string) string { l := strings.Split(s, "/") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } // start with 1, to strip /skydns for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 { l[i], l[j] = l[j], l[i] diff --git a/vendor/github.com/coredns/coredns/plugin/etcd/msg/service.go b/vendor/github.com/coredns/coredns/plugin/etcd/msg/service.go index 4e049e10..759a8621 100644 --- a/vendor/github.com/coredns/coredns/plugin/etcd/msg/service.go +++ b/vendor/github.com/coredns/coredns/plugin/etcd/msg/service.go @@ -154,7 +154,6 @@ func split255(s string) []string { } else { sx = append(sx, s[p:]) break - } p, i = p+255, i+255 } diff --git a/vendor/github.com/coredns/coredns/plugin/etcd/msg/type.go b/vendor/github.com/coredns/coredns/plugin/etcd/msg/type.go index ad09e74f..a300eac7 100644 --- a/vendor/github.com/coredns/coredns/plugin/etcd/msg/type.go +++ b/vendor/github.com/coredns/coredns/plugin/etcd/msg/type.go @@ -15,11 +15,9 @@ import ( // // Note that a service can double/triple as a TXT record or MX record. func (s *Service) HostType() (what uint16, normalized net.IP) { - ip := net.ParseIP(s.Host) switch { - case ip == nil: if len(s.Text) == 0 { return dns.TypeCNAME, nil diff --git a/vendor/github.com/coredns/coredns/plugin/metadata/README.md b/vendor/github.com/coredns/coredns/plugin/metadata/README.md new file mode 100644 index 00000000..6eb2c396 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/metadata/README.md @@ -0,0 +1,49 @@ +# metadata + +## Name + +*metadata* - enables a metadata collector. + +## Description + +By enabling *metadata* any plugin that implements [metadata.Provider +interface](https://godoc.org/github.com/coredns/coredns/plugin/metadata#Provider) will be called for +each DNS query, at the beginning of the process for that query, in order to add its own metadata to +context. + +The metadata collected will be available for all plugins, via the Context parameter provided in the +ServeDNS function. The package (code) documentation has examples on how to inspect and retrieve +metadata a plugin might be interested in. + +The metadata is added by setting a label with a value in the context. These labels should be named +`plugin/NAME`, where **NAME** is something descriptive. The only hard requirement the *metadata* +plugin enforces is that the labels contain a slash. See the documentation for +`metadata.SetValueFunc`. + +The value stored is a string. The empty string signals "no metadata". See the documentation for +`metadata.ValueFunc` on how to retrieve this. + +## Syntax + +~~~ +metadata [ZONES... ] +~~~ + +* **ZONES** zones metadata should be invoked for. + +## Plugins + +`metadata.Provider` interface needs to be implemented by each plugin willing to provide metadata +information for other plugins. It will be called by metadata and gather the information from all +plugins in context. + +Note: this method should work quickly, because it is called for every request. + +## Examples + +The *rewrite* plugin uses meta data to rewrite requests. + +## See Also + +The [Provider interface](https://godoc.org/github.com/coredns/coredns/plugin/metadata#Provider) and +the [package level](https://godoc.org/github.com/coredns/coredns/plugin/metadata) documentation. diff --git a/vendor/github.com/coredns/coredns/plugin/metadata/metadata.go b/vendor/github.com/coredns/coredns/plugin/metadata/metadata.go new file mode 100644 index 00000000..58e5ce2e --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/metadata/metadata.go @@ -0,0 +1,44 @@ +package metadata + +import ( + "context" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +// Metadata implements collecting metadata information from all plugins that +// implement the Provider interface. +type Metadata struct { + Zones []string + Providers []Provider + Next plugin.Handler +} + +// Name implements the Handler interface. +func (m *Metadata) Name() string { return "metadata" } + +// ContextWithMetadata is exported for use by provider tests +func ContextWithMetadata(ctx context.Context) context.Context { + return context.WithValue(ctx, key{}, md{}) +} + +// ServeDNS implements the plugin.Handler interface. +func (m *Metadata) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + rcode, err := plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r) + return rcode, err +} + +// Collect will retrieve metadata functions from each metadata provider and update the context +func (m *Metadata) Collect(ctx context.Context, state request.Request) context.Context { + ctx = ContextWithMetadata(ctx) + if plugin.Zones(m.Zones).Matches(state.Name()) != "" { + // Go through all Providers and collect metadata. + for _, p := range m.Providers { + ctx = p.Metadata(ctx, state) + } + } + return ctx +} diff --git a/vendor/github.com/coredns/coredns/plugin/metadata/provider.go b/vendor/github.com/coredns/coredns/plugin/metadata/provider.go new file mode 100644 index 00000000..e1bd7059 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/metadata/provider.go @@ -0,0 +1,127 @@ +// Package metadata provides an API that allows plugins to add metadata to the context. +// Each metadata is stored under a label that has the form /. Each metadata +// is returned as a Func. When Func is called the metadata is returned. If Func is expensive to +// execute it is its responsibility to provide some form of caching. During the handling of a +// query it is expected the metadata stays constant. +// +// Basic example: +// +// Implement the Provider interface for a plugin p: +// +// func (p P) Metadata(ctx context.Context, state request.Request) context.Context { +// metadata.SetValueFunc(ctx, "test/something", func() string { return "myvalue" }) +// return ctx +// } +// +// Basic example with caching: +// +// func (p P) Metadata(ctx context.Context, state request.Request) context.Context { +// cached := "" +// f := func() string { +// if cached != "" { +// return cached +// } +// cached = expensiveFunc() +// return cached +// } +// metadata.SetValueFunc(ctx, "test/something", f) +// return ctx +// } +// +// If you need access to this metadata from another plugin: +// +// // ... +// valueFunc := metadata.ValueFunc(ctx, "test/something") +// value := valueFunc() +// // use 'value' +// +package metadata + +import ( + "context" + "strings" + + "github.com/coredns/coredns/request" +) + +// Provider interface needs to be implemented by each plugin willing to provide +// metadata information for other plugins. +type Provider interface { + // Metadata adds metadata to the context and returns a (potentially) new context. + // Note: this method should work quickly, because it is called for every request + // from the metadata plugin. + Metadata(ctx context.Context, state request.Request) context.Context +} + +// Func is the type of function in the metadata, when called they return the value of the label. +type Func func() string + +// IsLabel checks that the provided name is a valid label name, i.e. two or more words separated by a slash. +func IsLabel(label string) bool { + p := strings.Index(label, "/") + if p <= 0 || p >= len(label)-1 { + // cannot accept namespace empty nor label empty + return false + } + return true +} + +// Labels returns all metadata keys stored in the context. These label names should be named +// as: plugin/NAME, where NAME is something descriptive. +func Labels(ctx context.Context) []string { + if metadata := ctx.Value(key{}); metadata != nil { + if m, ok := metadata.(md); ok { + return keys(m) + } + } + return nil +} + +// ValueFuncs returns the map[string]Func from the context, or nil if it does not exist. +func ValueFuncs(ctx context.Context) map[string]Func { + if metadata := ctx.Value(key{}); metadata != nil { + if m, ok := metadata.(md); ok { + return m + } + } + return nil +} + +// ValueFunc returns the value function of label. If none can be found nil is returned. Calling the +// function returns the value of the label. +func ValueFunc(ctx context.Context, label string) Func { + if metadata := ctx.Value(key{}); metadata != nil { + if m, ok := metadata.(md); ok { + return m[label] + } + } + return nil +} + +// SetValueFunc set the metadata label to the value function. If no metadata can be found this is a noop and +// false is returned. Any existing value is overwritten. +func SetValueFunc(ctx context.Context, label string, f Func) bool { + if metadata := ctx.Value(key{}); metadata != nil { + if m, ok := metadata.(md); ok { + m[label] = f + return true + } + } + return false +} + +// md is metadata information storage. +type md map[string]Func + +// key defines the type of key that is used to save metadata into the context. +type key struct{} + +func keys(m map[string]Func) []string { + s := make([]string, len(m)) + i := 0 + for k := range m { + s[i] = k + i++ + } + return s +} diff --git a/vendor/github.com/coredns/coredns/plugin/metadata/setup.go b/vendor/github.com/coredns/coredns/plugin/metadata/setup.go new file mode 100644 index 00000000..90b1cf99 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/metadata/setup.go @@ -0,0 +1,44 @@ +package metadata + +import ( + "github.com/coredns/caddy" + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/plugin" +) + +func init() { plugin.Register("metadata", setup) } + +func setup(c *caddy.Controller) error { + m, err := metadataParse(c) + if err != nil { + return err + } + dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { + m.Next = next + return m + }) + + c.OnStartup(func() error { + plugins := dnsserver.GetConfig(c).Handlers() + for _, p := range plugins { + if met, ok := p.(Provider); ok { + m.Providers = append(m.Providers, met) + } + } + return nil + }) + + return nil +} + +func metadataParse(c *caddy.Controller) (*Metadata, error) { + m := &Metadata{} + c.Next() + + m.Zones = plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), c.ServerBlockKeys) + + if c.NextBlock() || c.Next() { + return nil, plugin.Error("metadata", c.ArgErr()) + } + return m, nil +} diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/README.md b/vendor/github.com/coredns/coredns/plugin/metrics/README.md index 75c20bde..ec5da10d 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/README.md +++ b/vendor/github.com/coredns/coredns/plugin/metrics/README.md @@ -8,19 +8,22 @@ With *prometheus* you export metrics from CoreDNS and any plugin that has them. The default location for the metrics is `localhost:9153`. The metrics path is fixed to `/metrics`. -The following metrics are exported: + +In addition to the default Go metrics exported by the [Prometheus Go client](https://prometheus.io/docs/guides/go-application/), +the following metrics are exported: * `coredns_build_info{version, revision, goversion}` - info about CoreDNS itself. * `coredns_panics_total{}` - total number of panics. -* `coredns_dns_requests_total{server, zone, proto, family, type}` - total query count. -* `coredns_dns_request_duration_seconds{server, zone, type}` - duration to process each query. -* `coredns_dns_request_size_bytes{server, zone, proto}` - size of the request in bytes. -* `coredns_dns_do_requests_total{server, zone}` - queries that have the DO bit set -* `coredns_dns_response_size_bytes{server, zone, proto}` - response size in bytes. -* `coredns_dns_responses_total{server, zone, rcode, plugin}` - response per zone, rcode and plugin. -* `coredns_plugin_enabled{server, zone, name}` - indicates whether a plugin is enabled on per server and zone basis. +* `coredns_dns_requests_total{server, zone, view, proto, family, type}` - total query count. +* `coredns_dns_request_duration_seconds{server, zone, view, type}` - duration to process each query. +* `coredns_dns_request_size_bytes{server, zone, view, proto}` - size of the request in bytes. +* `coredns_dns_do_requests_total{server, view, zone}` - queries that have the DO bit set +* `coredns_dns_response_size_bytes{server, zone, view, proto}` - response size in bytes. +* `coredns_dns_responses_total{server, zone, view, rcode, plugin}` - response per zone, rcode and plugin. +* `coredns_dns_https_responses_total{server, status}` - responses per server and http status code. +* `coredns_plugin_enabled{server, zone, view, name}` - indicates whether a plugin is enabled on per server, zone and view basis. -Each counter has a label `zone` which is the zonename used for the request/response. +Almost each counter has a label `zone` which is the zonename used for the request/response. Extra labels used are: @@ -32,12 +35,20 @@ Extra labels used are: * `type` which holds the query type. It holds most common types (A, AAAA, MX, SOA, CNAME, PTR, TXT, NS, SRV, DS, DNSKEY, RRSIG, NSEC, NSEC3, HTTPS, IXFR, AXFR and ANY) and "other" which lumps together all other types. +* `status` which holds the https status code. Possible values are: + * 200 - request is processed, + * 404 - request has been rejected on validation, + * 400 - request to dns message conversion failed, + * 500 - processing ended up with no response. * the `plugin` label holds the name of the plugin that made the write to the client. If the server did the write (on error for instance), the value is empty. If monitoring is enabled, queries that do not enter the plugin chain are exported under the fake name "dropped" (without a closing dot - this is never a valid domain name). +Other plugins may export additional stats when the _prometheus_ plugin is enabled. Those stats are documented in each +plugin's README. + This plugin can only be used once per Server Block. ## Syntax diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/context.go b/vendor/github.com/coredns/coredns/plugin/metrics/context.go index da6bdb12..ae2856dd 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/context.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/context.go @@ -22,3 +22,16 @@ func WithServer(ctx context.Context) string { } return srv.(*dnsserver.Server).Addr } + +// WithView returns the name of the view currently handling the request, if a view is defined. +// +// Basic usage with a metric: +// +// .WithLabelValues(metrics.WithView(ctx), labels..).Add(1) +func WithView(ctx context.Context) string { + v := ctx.Value(dnsserver.ViewKey{}) + if v == nil { + return "" + } + return v.(string) +} diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/handler.go b/vendor/github.com/coredns/coredns/plugin/metrics/handler.go index 90db7618..41da6901 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/handler.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/handler.go @@ -34,7 +34,7 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg rc = status } plugin := m.authoritativePlugin(rw.Caller) - vars.Report(WithServer(ctx), state, zone, rcode.ToString(rc), plugin, rw.Len, rw.Start) + vars.Report(WithServer(ctx), state, zone, WithView(ctx), rcode.ToString(rc), plugin, rw.Len, rw.Start) return status, err } diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/recorder.go b/vendor/github.com/coredns/coredns/plugin/metrics/recorder.go index a37f420d..d4d42ba5 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/recorder.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/recorder.go @@ -24,7 +24,5 @@ func (r *Recorder) WriteMsg(res *dns.Msg) error { _, r.Caller[0], _, _ = runtime.Caller(1) _, r.Caller[1], _, _ = runtime.Caller(2) _, r.Caller[2], _, _ = runtime.Caller(3) - r.Len += res.Len() - r.Msg = res - return r.ResponseWriter.WriteMsg(res) + return r.Recorder.WriteMsg(res) } diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/setup.go b/vendor/github.com/coredns/coredns/plugin/metrics/setup.go index 5ac7d8f7..bee7d1f4 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/setup.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/setup.go @@ -39,7 +39,7 @@ func setup(c *caddy.Controller) error { for _, h := range conf.ListenHosts { addrstr := conf.Transport + "://" + net.JoinHostPort(h, conf.Port) for _, p := range conf.Handlers() { - vars.PluginEnabled.WithLabelValues(addrstr, conf.Zone, p.Name()).Set(1) + vars.PluginEnabled.WithLabelValues(addrstr, conf.Zone, conf.ViewName, p.Name()).Set(1) } } return nil @@ -49,7 +49,7 @@ func setup(c *caddy.Controller) error { for _, h := range conf.ListenHosts { addrstr := conf.Transport + "://" + net.JoinHostPort(h, conf.Port) for _, p := range conf.Handlers() { - vars.PluginEnabled.WithLabelValues(addrstr, conf.Zone, p.Name()).Set(1) + vars.PluginEnabled.WithLabelValues(addrstr, conf.Zone, conf.ViewName, p.Name()).Set(1) } } return nil diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/vars/report.go b/vendor/github.com/coredns/coredns/plugin/metrics/vars/report.go index 9761f626..92f6bc16 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/vars/report.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/vars/report.go @@ -9,7 +9,7 @@ import ( // Report reports the metrics data associated with request. This function is exported because it is also // called from core/dnsserver to report requests hitting the server that should not be handled and are thus // not sent down the plugin chain. -func Report(server string, req request.Request, zone, rcode, plugin string, size int, start time.Time) { +func Report(server string, req request.Request, zone, view, rcode, plugin string, size int, start time.Time) { // Proto and Family. net := req.Proto() fam := "1" @@ -18,16 +18,16 @@ func Report(server string, req request.Request, zone, rcode, plugin string, size } if req.Do() { - RequestDo.WithLabelValues(server, zone).Inc() + RequestDo.WithLabelValues(server, zone, view).Inc() } qType := qTypeString(req.QType()) - RequestCount.WithLabelValues(server, zone, net, fam, qType).Inc() + RequestCount.WithLabelValues(server, zone, view, net, fam, qType).Inc() - RequestDuration.WithLabelValues(server, zone).Observe(time.Since(start).Seconds()) + RequestDuration.WithLabelValues(server, zone, view).Observe(time.Since(start).Seconds()) - ResponseSize.WithLabelValues(server, zone, net).Observe(float64(size)) - RequestSize.WithLabelValues(server, zone, net).Observe(float64(req.Len())) + ResponseSize.WithLabelValues(server, zone, view, net).Observe(float64(size)) + RequestSize.WithLabelValues(server, zone, view, net).Observe(float64(req.Len())) - ResponseRcode.WithLabelValues(server, zone, rcode, plugin).Inc() + ResponseRcode.WithLabelValues(server, zone, view, rcode, plugin).Inc() } diff --git a/vendor/github.com/coredns/coredns/plugin/metrics/vars/vars.go b/vendor/github.com/coredns/coredns/plugin/metrics/vars/vars.go index 1412bf16..f0cf829c 100644 --- a/vendor/github.com/coredns/coredns/plugin/metrics/vars/vars.go +++ b/vendor/github.com/coredns/coredns/plugin/metrics/vars/vars.go @@ -14,7 +14,7 @@ var ( Subsystem: subsystem, Name: "requests_total", Help: "Counter of DNS requests made per zone, protocol and family.", - }, []string{"server", "zone", "proto", "family", "type"}) + }, []string{"server", "zone", "view", "proto", "family", "type"}) RequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: plugin.Namespace, @@ -22,7 +22,7 @@ var ( Name: "request_duration_seconds", Buckets: plugin.TimeBuckets, Help: "Histogram of the time (in seconds) each request took per zone.", - }, []string{"server", "zone"}) + }, []string{"server", "zone", "view"}) RequestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: plugin.Namespace, @@ -30,14 +30,14 @@ var ( Name: "request_size_bytes", Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP) per zone and protocol.", Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3}, - }, []string{"server", "zone", "proto"}) + }, []string{"server", "zone", "view", "proto"}) RequestDo = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: subsystem, Name: "do_requests_total", Help: "Counter of DNS requests with DO bit set per zone.", - }, []string{"server", "zone"}) + }, []string{"server", "zone", "view"}) ResponseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: plugin.Namespace, @@ -45,14 +45,14 @@ var ( Name: "response_size_bytes", Help: "Size of the returned response in bytes.", Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3}, - }, []string{"server", "zone", "proto"}) + }, []string{"server", "zone", "view", "proto"}) ResponseRcode = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: plugin.Namespace, Subsystem: subsystem, Name: "responses_total", Help: "Counter of response status codes.", - }, []string{"server", "zone", "rcode", "plugin"}) + }, []string{"server", "zone", "view", "rcode", "plugin"}) Panic = promauto.NewCounter(prometheus.CounterOpts{ Namespace: plugin.Namespace, @@ -64,7 +64,14 @@ var ( Namespace: plugin.Namespace, Name: "plugin_enabled", Help: "A metric that indicates whether a plugin is enabled on per server and zone basis.", - }, []string{"server", "zone", "name"}) + }, []string{"server", "zone", "view", "name"}) + + HTTPSResponsesCount = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: plugin.Namespace, + Subsystem: subsystem, + Name: "https_responses_total", + Help: "Counter of DoH responses per server and http status code.", + }, []string{"server", "status"}) ) const ( diff --git a/vendor/github.com/coredns/coredns/plugin/normalize.go b/vendor/github.com/coredns/coredns/plugin/normalize.go index 7543e42a..4b92bb43 100644 --- a/vendor/github.com/coredns/coredns/plugin/normalize.go +++ b/vendor/github.com/coredns/coredns/plugin/normalize.go @@ -125,7 +125,6 @@ func (h Host) NormalizeExact() []string { } for i := range hosts { hosts[i] = Name(hosts[i]).Normalize() - } return hosts } diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/cache/cache.go b/vendor/github.com/coredns/coredns/plugin/pkg/cache/cache.go index 5105c4f7..6c4105e7 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/cache/cache.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/cache/cache.go @@ -66,7 +66,7 @@ func (c *Cache) Remove(key uint64) { // Len returns the number of elements in the cache. func (c *Cache) Len() int { l := 0 - for _, s := range c.shards { + for _, s := range &c.shards { l += s.Len() } return l @@ -74,7 +74,7 @@ func (c *Cache) Len() int { // Walk walks each shard in the cache. func (c *Cache) Walk(f func(map[uint64]interface{}, uint64) bool) { - for _, s := range c.shards { + for _, s := range &c.shards { s.Walk(f) } } diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/cidr/cidr.go b/vendor/github.com/coredns/coredns/plugin/pkg/cidr/cidr.go index 15eac6d4..91aead91 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/cidr/cidr.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/cidr/cidr.go @@ -38,7 +38,7 @@ func Split(n *net.IPNet) []string { func nets(network *net.IPNet, newPrefixLen int) []*net.IPNet { prefixLen, _ := network.Mask.Size() maxSubnets := int(math.Exp2(float64(newPrefixLen)) / math.Exp2(float64(prefixLen))) - nets := []*net.IPNet{{network.IP, net.CIDRMask(newPrefixLen, 8*len(network.IP))}} + nets := []*net.IPNet{{IP: network.IP, Mask: net.CIDRMask(newPrefixLen, 8*len(network.IP))}} for i := 1; i < maxSubnets; i++ { next, exceeds := cidr.NextSubnet(nets[len(nets)-1], newPrefixLen) diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/doh/doh.go b/vendor/github.com/coredns/coredns/plugin/pkg/doh/doh.go index 1a338537..9d5305b3 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/doh/doh.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/doh/doh.go @@ -92,7 +92,7 @@ func requestToMsgGet(req *http.Request) (*dns.Msg, error) { } func toMsg(r io.ReadCloser) (*dns.Msg, error) { - buf, err := io.ReadAll(r) + buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536)) if err != nil { return nil, err } diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/log/listener.go b/vendor/github.com/coredns/coredns/plugin/pkg/log/listener.go new file mode 100644 index 00000000..2dfe8155 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/pkg/log/listener.go @@ -0,0 +1,141 @@ +package log + +import ( + "sync" +) + +// Listener listens for all log prints of plugin loggers aka loggers with plugin name. +// When a plugin logger gets called, it should first call the same method in the Listener object. +// A usage example is, the external plugin k8s_event will replicate log prints to Kubernetes events. +type Listener interface { + Name() string + Debug(plugin string, v ...interface{}) + Debugf(plugin string, format string, v ...interface{}) + Info(plugin string, v ...interface{}) + Infof(plugin string, format string, v ...interface{}) + Warning(plugin string, v ...interface{}) + Warningf(plugin string, format string, v ...interface{}) + Error(plugin string, v ...interface{}) + Errorf(plugin string, format string, v ...interface{}) + Fatal(plugin string, v ...interface{}) + Fatalf(plugin string, format string, v ...interface{}) +} + +type listeners struct { + listeners []Listener + sync.RWMutex +} + +var ls *listeners + +func init() { + ls = &listeners{} + ls.listeners = make([]Listener, 0) +} + +// RegisterListener register a listener object. +func RegisterListener(new Listener) error { + ls.Lock() + defer ls.Unlock() + for k, l := range ls.listeners { + if l.Name() == new.Name() { + ls.listeners[k] = new + return nil + } + } + ls.listeners = append(ls.listeners, new) + return nil +} + +// DeregisterListener deregister a listener object. +func DeregisterListener(old Listener) error { + ls.Lock() + defer ls.Unlock() + for k, l := range ls.listeners { + if l.Name() == old.Name() { + ls.listeners = append(ls.listeners[:k], ls.listeners[k+1:]...) + return nil + } + } + return nil +} + +func (ls *listeners) debug(plugin string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Debug(plugin, v...) + } + ls.RUnlock() +} + +func (ls *listeners) debugf(plugin string, format string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Debugf(plugin, format, v...) + } + ls.RUnlock() +} + +func (ls *listeners) info(plugin string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Info(plugin, v...) + } + ls.RUnlock() +} + +func (ls *listeners) infof(plugin string, format string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Infof(plugin, format, v...) + } + ls.RUnlock() +} + +func (ls *listeners) warning(plugin string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Warning(plugin, v...) + } + ls.RUnlock() +} + +func (ls *listeners) warningf(plugin string, format string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Warningf(plugin, format, v...) + } + ls.RUnlock() +} + +func (ls *listeners) error(plugin string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Error(plugin, v...) + } + ls.RUnlock() +} + +func (ls *listeners) errorf(plugin string, format string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Errorf(plugin, format, v...) + } + ls.RUnlock() +} + +func (ls *listeners) fatal(plugin string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Fatal(plugin, v...) + } + ls.RUnlock() +} + +func (ls *listeners) fatalf(plugin string, format string, v ...interface{}) { + ls.RLock() + for _, l := range ls.listeners { + l.Fatalf(plugin, format, v...) + } + ls.RUnlock() +} diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/log/plugin.go b/vendor/github.com/coredns/coredns/plugin/pkg/log/plugin.go index 8dbffa07..1be79f11 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/log/plugin.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/log/plugin.go @@ -27,6 +27,7 @@ func (p P) Debug(v ...interface{}) { if !D.Value() { return } + ls.debug(p.plugin, v...) p.log(debug, v...) } @@ -35,29 +36,56 @@ func (p P) Debugf(format string, v ...interface{}) { if !D.Value() { return } + ls.debugf(p.plugin, format, v...) p.logf(debug, format, v...) } // Info logs as log.Info. -func (p P) Info(v ...interface{}) { p.log(info, v...) } +func (p P) Info(v ...interface{}) { + ls.info(p.plugin, v...) + p.log(info, v...) +} // Infof logs as log.Infof. -func (p P) Infof(format string, v ...interface{}) { p.logf(info, format, v...) } +func (p P) Infof(format string, v ...interface{}) { + ls.infof(p.plugin, format, v...) + p.logf(info, format, v...) +} // Warning logs as log.Warning. -func (p P) Warning(v ...interface{}) { p.log(warning, v...) } +func (p P) Warning(v ...interface{}) { + ls.warning(p.plugin, v...) + p.log(warning, v...) +} // Warningf logs as log.Warningf. -func (p P) Warningf(format string, v ...interface{}) { p.logf(warning, format, v...) } +func (p P) Warningf(format string, v ...interface{}) { + ls.warningf(p.plugin, format, v...) + p.logf(warning, format, v...) +} // Error logs as log.Error. -func (p P) Error(v ...interface{}) { p.log(err, v...) } +func (p P) Error(v ...interface{}) { + ls.error(p.plugin, v...) + p.log(err, v...) +} // Errorf logs as log.Errorf. -func (p P) Errorf(format string, v ...interface{}) { p.logf(err, format, v...) } +func (p P) Errorf(format string, v ...interface{}) { + ls.errorf(p.plugin, format, v...) + p.logf(err, format, v...) +} // Fatal logs as log.Fatal and calls os.Exit(1). -func (p P) Fatal(v ...interface{}) { p.log(fatal, v...); os.Exit(1) } +func (p P) Fatal(v ...interface{}) { + ls.fatal(p.plugin, v...) + p.log(fatal, v...) + os.Exit(1) +} // Fatalf logs as log.Fatalf and calls os.Exit(1). -func (p P) Fatalf(format string, v ...interface{}) { p.logf(fatal, format, v...); os.Exit(1) } +func (p P) Fatalf(format string, v ...interface{}) { + ls.fatalf(p.plugin, format, v...) + p.logf(fatal, format, v...) + os.Exit(1) +} diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/parse/host.go b/vendor/github.com/coredns/coredns/plugin/pkg/parse/host.go index 3bee4f96..9206a033 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/parse/host.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/parse/host.go @@ -32,7 +32,6 @@ func stripZone(host string) string { func HostPortOrFile(s ...string) ([]string, error) { var servers []string for _, h := range s { - trans, host := Transport(h) addr, _, err := net.SplitHostPort(host) diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_no_reuseport.go b/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_no_reuseport.go index e3bdfb90..1018a9b1 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_no_reuseport.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_no_reuseport.go @@ -1,4 +1,4 @@ -// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd +//go:build !go1.11 || (!aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd) package reuseport diff --git a/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_reuseport.go b/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_reuseport.go index fa6f365d..71fac3e7 100644 --- a/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_reuseport.go +++ b/vendor/github.com/coredns/coredns/plugin/pkg/reuseport/listen_reuseport.go @@ -1,5 +1,4 @@ -// +build go1.11 -// +build aix darwin dragonfly freebsd linux netbsd openbsd +//go:build go1.11 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd) package reuseport diff --git a/vendor/github.com/coredns/coredns/plugin/test/helpers.go b/vendor/github.com/coredns/coredns/plugin/test/helpers.go index cb7b0994..8145b605 100644 --- a/vendor/github.com/coredns/coredns/plugin/test/helpers.go +++ b/vendor/github.com/coredns/coredns/plugin/test/helpers.go @@ -82,6 +82,9 @@ func PTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } // TXT returns a TXT record from rr. It panics on errors. func TXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } +// CAA returns a CAA record from rr. It panics on errors. +func CAA(rr string) *dns.CAA { r, _ := dns.NewRR(rr); return r.(*dns.CAA) } + // HINFO returns a HINFO record from rr. It panics on errors. func HINFO(rr string) *dns.HINFO { r, _ := dns.NewRR(rr); return r.(*dns.HINFO) } @@ -282,7 +285,6 @@ func SortAndCheck(resp *dns.Msg, tc Case) error { } if err := Section(tc, Ns, resp.Ns); err != nil { return err - } return Section(tc, Extra, resp.Extra) } diff --git a/vendor/github.com/coredns/coredns/plugin/test/responsewriter.go b/vendor/github.com/coredns/coredns/plugin/test/responsewriter.go index c66b0c96..32167008 100644 --- a/vendor/github.com/coredns/coredns/plugin/test/responsewriter.go +++ b/vendor/github.com/coredns/coredns/plugin/test/responsewriter.go @@ -12,6 +12,7 @@ import ( type ResponseWriter struct { TCP bool // if TCP is true we return an TCP connection instead of an UDP one. RemoteIP string + Zone string } // LocalAddr returns the local address, 127.0.0.1:53 (UDP, TCP if t.TCP is true). @@ -33,9 +34,9 @@ func (t *ResponseWriter) RemoteAddr() net.Addr { ip := net.ParseIP(remoteIP) port := 40212 if t.TCP { - return &net.TCPAddr{IP: ip, Port: port, Zone: ""} + return &net.TCPAddr{IP: ip, Port: port, Zone: t.Zone} } - return &net.UDPAddr{IP: ip, Port: port, Zone: ""} + return &net.UDPAddr{IP: ip, Port: port, Zone: t.Zone} } // WriteMsg implements dns.ResponseWriter interface. diff --git a/vendor/github.com/coredns/coredns/plugin/test/scrape.go b/vendor/github.com/coredns/coredns/plugin/test/scrape.go index 3fb6b26d..7847e39d 100644 --- a/vendor/github.com/coredns/coredns/plugin/test/scrape.go +++ b/vendor/github.com/coredns/coredns/plugin/test/scrape.go @@ -80,7 +80,6 @@ func Scrape(url string) []*MetricFamily { // ScrapeMetricAsInt provides a sum of all metrics collected for the name and label provided. // if the metric is not a numeric value, it will be counted a 0. func ScrapeMetricAsInt(addr string, name string, label string, nometricvalue int) int { - valueToInt := func(m metric) int { v := m.Value r, err := strconv.Atoi(v) @@ -141,7 +140,6 @@ func MetricValueLabel(name, label string, mfs []*MetricFamily) (string, map[stri return m.(metric).Value, m.(metric).Labels } } - } } } diff --git a/vendor/github.com/coredns/coredns/request/request.go b/vendor/github.com/coredns/coredns/request/request.go index 7731ff38..9188888f 100644 --- a/vendor/github.com/coredns/coredns/request/request.go +++ b/vendor/github.com/coredns/coredns/request/request.go @@ -313,7 +313,6 @@ func (r *Request) Class() string { } return dns.Class(r.Req.Question[0].Qclass).String() - } // QClass returns the class of the question in the request. @@ -327,7 +326,6 @@ func (r *Request) QClass() uint16 { } return r.Req.Question[0].Qclass - } // Clear clears all caching from Request s. diff --git a/vendor/github.com/getsentry/raven-go/.gitignore b/vendor/github.com/getsentry/raven-go/.gitignore index 797da784..0f66ce75 100644 --- a/vendor/github.com/getsentry/raven-go/.gitignore +++ b/vendor/github.com/getsentry/raven-go/.gitignore @@ -1,5 +1,5 @@ *.test +*.out example/example - -docs/_build -docs/doctrees +/xunit.xml +/coverage.xml diff --git a/vendor/github.com/getsentry/raven-go/.gitmodules b/vendor/github.com/getsentry/raven-go/.gitmodules index 1e6464ab..e69de29b 100644 --- a/vendor/github.com/getsentry/raven-go/.gitmodules +++ b/vendor/github.com/getsentry/raven-go/.gitmodules @@ -1,3 +0,0 @@ -[submodule "docs/_sentryext"] - path = docs/_sentryext - url = https://github.com/getsentry/sentry-doc-support diff --git a/vendor/github.com/getsentry/raven-go/.travis.yml b/vendor/github.com/getsentry/raven-go/.travis.yml index 9f0d5488..8ec4eca8 100644 --- a/vendor/github.com/getsentry/raven-go/.travis.yml +++ b/vendor/github.com/getsentry/raven-go/.travis.yml @@ -1,26 +1,41 @@ sudo: false language: go go: - - "1.2" - - "1.3" - - 1.4.x - - 1.5.x - - 1.6.x - 1.7.x - 1.8.x - 1.9.x - 1.10.x + - 1.11.x - tip before_install: - go install -race std - go get golang.org/x/tools/cmd/cover + - go get github.com/tebeka/go2xunit + - go get github.com/t-yuki/gocover-cobertura - go get -v ./... script: - - go test -v -race ./... - - go test -v -cover ./... + - go test -v -race ./... | tee gotest.out + - $GOPATH/bin/go2xunit -fail -input gotest.out -output xunit.xml + - go test -v -coverprofile=coverage.txt -covermode count . + - $GOPATH/bin/gocover-cobertura < coverage.txt > coverage.xml + +after_script: + - npm install -g @zeus-ci/cli + - zeus upload -t "application/x-cobertura+xml" coverage.xml + - zeus upload -t "application/x-xunit+xml" xunit.xml matrix: allow_failures: - go: tip + +notifications: + webhooks: + urls: + - https://zeus.ci/hooks/cd949996-d30a-11e8-ba53-0a580a28042d/public/provider/travis/webhook + on_success: always + on_failure: always + on_start: always + on_cancel: always + on_error: always diff --git a/vendor/github.com/getsentry/raven-go/README.md b/vendor/github.com/getsentry/raven-go/README.md index 5ea29a0a..16c9483e 100644 --- a/vendor/github.com/getsentry/raven-go/README.md +++ b/vendor/github.com/getsentry/raven-go/README.md @@ -1,6 +1,10 @@ -# raven [![Build Status](https://travis-ci.org/getsentry/raven-go.png?branch=master)](https://travis-ci.org/getsentry/raven-go) +# raven -raven is a Go client for the [Sentry](https://github.com/getsentry/sentry) +[![Build Status](https://api.travis-ci.org/getsentry/raven-go.svg?branch=master)](https://travis-ci.org/getsentry/raven-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/raven-go)](https://goreportcard.com/report/github.com/getsentry/raven-go) +[![GoDoc](https://godoc.org/github.com/getsentry/raven-go?status.svg)](https://godoc.org/github.com/getsentry/raven-go) + +raven is the official Go SDK for the [Sentry](https://github.com/getsentry/sentry) event/error logging system. - [**API Documentation**](https://godoc.org/github.com/getsentry/raven-go) @@ -11,3 +15,5 @@ event/error logging system. ```text go get github.com/getsentry/raven-go ``` + +Note: Go 1.7 and newer are supported. diff --git a/vendor/github.com/getsentry/raven-go/client.go b/vendor/github.com/getsentry/raven-go/client.go index e224aa1a..a2c9a6c3 100644 --- a/vendor/github.com/getsentry/raven-go/client.go +++ b/vendor/github.com/getsentry/raven-go/client.go @@ -945,7 +945,7 @@ func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error { io.Copy(ioutil.Discard, res.Body) res.Body.Close() if res.StatusCode != 200 { - return fmt.Errorf("raven: got http status %d", res.StatusCode) + return fmt.Errorf("raven: got http status %d - x-sentry-error: %s", res.StatusCode, res.Header.Get("X-Sentry-Error")) } return nil } diff --git a/vendor/github.com/getsentry/raven-go/http.go b/vendor/github.com/getsentry/raven-go/http.go index d5cb05a6..ae8f4723 100644 --- a/vendor/github.com/getsentry/raven-go/http.go +++ b/vendor/github.com/getsentry/raven-go/http.go @@ -69,17 +69,31 @@ func (h *Http) Class() string { return "request" } // ... // })) func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { + return Recoverer(http.HandlerFunc(handler)).ServeHTTP +} + +// Recovery handler to wrap the stdlib net/http Mux. +// Example: +// mux := http.NewServeMux +// ... +// http.Handle("/", raven.Recoverer(mux)) +func Recoverer(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if rval := recover(); rval != nil { debug.PrintStack() rvalStr := fmt.Sprint(rval) - packet := NewPacket(rvalStr, NewException(errors.New(rvalStr), GetOrNewStacktrace(rval.(error), 2, 3, nil)), NewHttp(r)) + var packet *Packet + if err, ok := rval.(error); ok { + packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), GetOrNewStacktrace(err, 2, 3, nil)), NewHttp(r)) + } else { + packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r)) + } Capture(packet, nil) w.WriteHeader(http.StatusInternalServerError) } }() - handler(w, r) - } + handler.ServeHTTP(w, r) + }) } diff --git a/vendor/github.com/getsentry/raven-go/stacktrace.go b/vendor/github.com/getsentry/raven-go/stacktrace.go index fe46bf66..bc302ba1 100644 --- a/vendor/github.com/getsentry/raven-go/stacktrace.go +++ b/vendor/github.com/getsentry/raven-go/stacktrace.go @@ -61,14 +61,17 @@ func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []s for _, f := range stacktracer.StackTrace() { pc := uintptr(f) - 1 fn := runtime.FuncForPC(pc) + var fName string var file string var line int if fn != nil { file, line = fn.FileLine(pc) + fName = fn.Name() } else { file = "unknown" + fName = "unknown" } - frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes) + frame := NewStacktraceFrame(pc, fName, file, line, context, appPackagePrefixes) if frame != nil { frames = append([]*StacktraceFrame{frame}, frames...) } @@ -89,14 +92,27 @@ func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []s // be considered "in app". func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace { var frames []*StacktraceFrame - for i := 1 + skip; ; i++ { - pc, file, line, ok := runtime.Caller(i) - if !ok { - break + + callerPcs := make([]uintptr, 100) + numCallers := runtime.Callers(skip+2, callerPcs) + + // If there are no callers, the entire stacktrace is nil + if numCallers == 0 { + return nil + } + + callersFrames := runtime.CallersFrames(callerPcs) + + for { + fr, more := callersFrames.Next() + if fr.Func != nil { + frame := NewStacktraceFrame(fr.PC, fr.Function, fr.File, fr.Line, context, appPackagePrefixes) + if frame != nil { + frames = append(frames, frame) + } } - frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes) - if frame != nil { - frames = append(frames, frame) + if !more { + break } } // If there are no frames, the entire stacktrace is nil @@ -122,9 +138,9 @@ func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktra // // appPackagePrefixes is a list of prefixes used to check whether a package should // be considered "in app". -func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame { +func NewStacktraceFrame(pc uintptr, fName, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame { frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false} - frame.Module, frame.Function = functionName(pc) + frame.Module, frame.Function = functionName(fName) // `runtime.goexit` is effectively a placeholder that comes from // runtime/asm_amd64.s and is meaningless. @@ -143,7 +159,7 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr } if context > 0 { - contextLines, lineIdx := fileContext(file, line, context) + contextLines, lineIdx := sourceCodeLoader.Load(file, line, context) if len(contextLines) > 0 { for i, line := range contextLines { switch { @@ -157,7 +173,7 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr } } } else if context == -1 { - contextLine, _ := fileContext(file, line, 0) + contextLine, _ := sourceCodeLoader.Load(file, line, 0) if len(contextLine) > 0 { frame.ContextLine = string(contextLine[0]) } @@ -166,12 +182,8 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr } // Retrieve the name of the package and function containing the PC. -func functionName(pc uintptr) (pack string, name string) { - fn := runtime.FuncForPC(pc) - if fn == nil { - return - } - name = fn.Name() +func functionName(fName string) (pack string, name string) { + name = fName // We get this: // runtime/debug.*T·ptrmethod // and want this: @@ -185,24 +197,36 @@ func functionName(pc uintptr) (pack string, name string) { return } -var fileCacheLock sync.Mutex -var fileCache = make(map[string][][]byte) +type SourceCodeLoader interface { + Load(filename string, line, context int) ([][]byte, int) +} -func fileContext(filename string, line, context int) ([][]byte, int) { - fileCacheLock.Lock() - defer fileCacheLock.Unlock() - lines, ok := fileCache[filename] +var sourceCodeLoader SourceCodeLoader = &fsLoader{cache: make(map[string][][]byte)} + +func SetSourceCodeLoader(loader SourceCodeLoader) { + sourceCodeLoader = loader +} + +type fsLoader struct { + mu sync.Mutex + cache map[string][][]byte +} + +func (fs *fsLoader) Load(filename string, line, context int) ([][]byte, int) { + fs.mu.Lock() + defer fs.mu.Unlock() + lines, ok := fs.cache[filename] if !ok { data, err := ioutil.ReadFile(filename) if err != nil { // cache errors as nil slice: code below handles it correctly // otherwise when missing the source or running as a different user, we try // reading the file on each error which is unnecessary - fileCache[filename] = nil + fs.cache[filename] = nil return nil, 0 } lines = bytes.Split(data, []byte{'\n'}) - fileCache[filename] = lines + fs.cache[filename] = lines } if lines == nil { diff --git a/vendor/github.com/getsentry/sentry-go/.craft.yml b/vendor/github.com/getsentry/sentry-go/.craft.yml new file mode 100644 index 00000000..4af175ad --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/.craft.yml @@ -0,0 +1,11 @@ +minVersion: 0.23.1 +changelogPolicy: simple +artifactProvider: + name: none +targets: + - name: github + includeNames: /none/ + tagPrefix: v + - name: registry + sdks: + github:getsentry/sentry-go: diff --git a/vendor/github.com/getsentry/sentry-go/.gitattributes b/vendor/github.com/getsentry/sentry-go/.gitattributes new file mode 100644 index 00000000..bccfeeab --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/.gitattributes @@ -0,0 +1,5 @@ +# Tell Git to use LF for line endings on all platforms. +# Required to have correct test data on Windows. +# https://github.com/mvdan/github-actions-golang#caveats +# https://github.com/actions/checkout/issues/135#issuecomment-613361104 +* text eol=lf diff --git a/vendor/github.com/getsentry/sentry-go/.gitignore b/vendor/github.com/getsentry/sentry-go/.gitignore new file mode 100644 index 00000000..5d0e90c5 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/.gitignore @@ -0,0 +1,10 @@ +coverage.txt + +# Just my personal way of tracking stuff — Kamil +FIXME.md +TODO.md +!NOTES.md + +# IDE system files +.idea +.vscode diff --git a/vendor/github.com/getsentry/sentry-go/.golangci.yml b/vendor/github.com/getsentry/sentry-go/.golangci.yml new file mode 100644 index 00000000..0deb9c0e --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/.golangci.yml @@ -0,0 +1,49 @@ +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exportloopref + - gochecknoinits + - goconst + - gocritic + - gocyclo + - godot + - gofmt + - goimports + - gosec + - gosimple + - govet + - ineffassign + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - structcheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace +issues: + exclude-rules: + - path: _test\.go + linters: + - prealloc + - path: _test\.go + text: "G306:" + linters: + - gosec + - path: errors_test\.go + linters: + - unused + - path: http/example_test\.go + linters: + - errcheck + - bodyclose diff --git a/vendor/github.com/getsentry/sentry-go/CHANGELOG.md b/vendor/github.com/getsentry/sentry-go/CHANGELOG.md new file mode 100644 index 00000000..ae9a6c29 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/CHANGELOG.md @@ -0,0 +1,400 @@ +# Changelog + +## 0.16.0 + +The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.16.0. +Due to ongoing work towards a stable API for `v1.0.0`, we sadly had to include **two breaking changes** in this release. + +### Breaking Changes + +- Add `EnableTracing`, a boolean option flag to enable performance monitoring (`false` by default). + - If you're using `TracesSampleRate` or `TracesSampler`, this option is **required** to enable performance monitoring. + + ```go + sentry.Init(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + }) + ``` +- Unify TracesSampler [#498](https://github.com/getsentry/sentry-go/pull/498) + - `TracesSampler` was changed to a callback that must return a `float64` between `0.0` and `1.0`. + + For example, you can apply a sample rate of `1.0` (100%) to all `/api` transactions, and a sample rate of `0.5` (50%) to all other transactions. + You can read more about this in our [SDK docs](https://docs.sentry.io/platforms/go/configuration/filtering/#using-sampling-to-filter-transaction-events). + + ```go + sentry.Init(sentry.ClientOptions{ + TracesSampler: sentry.TracesSampler(func(ctx sentry.SamplingContext) float64 { + hub := sentry.GetHubFromContext(ctx.Span.Context()) + name := hub.Scope().Transaction() + + if strings.HasPrefix(name, "GET /api") { + return 1.0 + } + + return 0.5 + }), + } + ``` + +### Features + +- Send errors logged with [Logrus](https://github.com/sirupsen/logrus) to Sentry. + - Have a look at our [logrus examples](https://github.com/getsentry/sentry-go/blob/master/example/logrus/main.go) on how to use the integration. +- Add support for Dynamic Sampling [#491](https://github.com/getsentry/sentry-go/pull/491) + - You can read more about Dynamic Sampling in our [product docs](https://docs.sentry.io/product/data-management-settings/dynamic-sampling/). +- Add detailed logging about the reason transactions are being dropped. + - You can enable SDK logging via `sentry.ClientOptions.Debug: true`. + +### Bug Fixes + +- Do not clone the hub when calling `StartTransaction` [#505](https://github.com/getsentry/sentry-go/pull/505) + - Fixes [#502](https://github.com/getsentry/sentry-go/issues/502) + +## 0.15.0 + +- fix: Scope values should not override Event values (#446) +- feat: Make maximum amount of spans configurable (#460) +- feat: Add a method to start a transaction (#482) +- feat: Extend User interface by adding Data, Name and Segment (#483) +- feat: Add ClientOptions.SendDefaultPII (#485) + +## 0.14.0 + +- feat: Add function to continue from trace string (#434) +- feat: Add `max-depth` options (#428) +- *[breaking]* ref: Use a `Context` type mapping to a `map[string]interface{}` for all event contexts (#444) +- *[breaking]* ref: Replace deprecated `ioutil` pkg with `os` & `io` (#454) +- ref: Optimize `stacktrace.go` from size and speed (#467) +- ci: Test against `go1.19` and `go1.18`, drop `go1.16` and `go1.15` support (#432, #477) +- deps: Dependency update to fix CVEs (#462, #464, #477) + +_NOTE:_ This version drops support for Go 1.16 and Go 1.15. The currently supported Go versions are the last 3 stable releases: 1.19, 1.18 and 1.17. + +## v0.13.0 + +- ref: Change DSN ProjectID to be a string (#420) +- fix: When extracting PCs from stack frames, try the `PC` field (#393) +- build: Bump gin-gonic/gin from v1.4.0 to v1.7.7 (#412) +- build: Bump Go version in go.mod (#410) +- ci: Bump golangci-lint version in GH workflow (#419) +- ci: Update GraphQL config with appropriate permissions (#417) +- ci: ci: Add craft release automation (#422) + +## v0.12.0 + +- feat: Automatic Release detection (#363, #369, #386, #400) +- fix: Do not change Hub.lastEventID for transactions (#379) +- fix: Do not clear LastEventID when events are dropped (#382) +- Updates to documentation (#366, #385) + +_NOTE:_ +This version drops support for Go 1.14, however no changes have been made that would make the SDK not work with Go 1.14. The currently supported Go versions are the last 3 stable releases: 1.15, 1.16 and 1.17. +There are two behavior changes related to `LastEventID`, both of which were intended to align the behavior of the Sentry Go SDK with other Sentry SDKs. +The new [automatic release detection feature](https://github.com/getsentry/sentry-go/issues/335) makes it easier to use Sentry and separate events per release without requiring extra work from users. We intend to improve this functionality in a future release by utilizing information that will be available in runtime starting with Go 1.18. The tracking issue is [#401](https://github.com/getsentry/sentry-go/issues/401). + +## v0.11.0 + +- feat(transports): Category-based Rate Limiting ([#354](https://github.com/getsentry/sentry-go/pull/354)) +- feat(transports): Report User-Agent identifying SDK ([#357](https://github.com/getsentry/sentry-go/pull/357)) +- fix(scope): Include event processors in clone ([#349](https://github.com/getsentry/sentry-go/pull/349)) +- Improvements to `go doc` documentation ([#344](https://github.com/getsentry/sentry-go/pull/344), [#350](https://github.com/getsentry/sentry-go/pull/350), [#351](https://github.com/getsentry/sentry-go/pull/351)) +- Miscellaneous changes to our testing infrastructure with GitHub Actions + ([57123a40](https://github.com/getsentry/sentry-go/commit/57123a409be55f61b1d5a6da93c176c55a399ad0), [#128](https://github.com/getsentry/sentry-go/pull/128), [#338](https://github.com/getsentry/sentry-go/pull/338), [#345](https://github.com/getsentry/sentry-go/pull/345), [#346](https://github.com/getsentry/sentry-go/pull/346), [#352](https://github.com/getsentry/sentry-go/pull/352), [#353](https://github.com/getsentry/sentry-go/pull/353), [#355](https://github.com/getsentry/sentry-go/pull/355)) + +_NOTE:_ +This version drops support for Go 1.13. The currently supported Go versions are the last 3 stable releases: 1.14, 1.15 and 1.16. +Users of the tracing functionality (`StartSpan`, etc) should upgrade to this version to benefit from separate rate limits for errors and transactions. +There are no breaking changes and upgrading should be a smooth experience for all users. + +## v0.10.0 + +- feat: Debug connection reuse (#323) +- fix: Send root span data as `Event.Extra` (#329) +- fix: Do not double sample transactions (#328) +- fix: Do not override trace context of transactions (#327) +- fix: Drain and close API response bodies (#322) +- ci: Run tests against Go tip (#319) +- ci: Move away from Travis in favor of GitHub Actions (#314) (#321) + +## v0.9.0 + +- feat: Initial tracing and performance monitoring support (#285) +- doc: Revamp sentryhttp documentation (#304) +- fix: Hub.PopScope never empties the scope stack (#300) +- ref: Report Event.Timestamp in local time (#299) +- ref: Report Breadcrumb.Timestamp in local time (#299) + +_NOTE:_ +This version introduces support for [Sentry's Performance Monitoring](https://docs.sentry.io/platforms/go/performance/). +The new tracing capabilities are beta, and we plan to expand them on future versions. Feedback is welcome, please open new issues on GitHub. +The `sentryhttp` package got better API docs, an [updated usage example](https://github.com/getsentry/sentry-go/tree/master/example/http) and support for creating automatic transactions as part of Performance Monitoring. + +## v0.8.0 + +- build: Bump required version of Iris (#296) +- fix: avoid unnecessary allocation in Client.processEvent (#293) +- doc: Remove deprecation of sentryhttp.HandleFunc (#284) +- ref: Update sentryhttp example (#283) +- doc: Improve documentation of sentryhttp package (#282) +- doc: Clarify SampleRate documentation (#279) +- fix: Remove RawStacktrace (#278) +- docs: Add example of custom HTTP transport +- ci: Test against go1.15, drop go1.12 support (#271) + +_NOTE:_ +This version comes with a few updates. Some examples and documentation have been +improved. We've bumped the supported version of the Iris framework to avoid +LGPL-licensed modules in the module dependency graph. +The `Exception.RawStacktrace` and `Thread.RawStacktrace` fields have been +removed to conform to Sentry's ingestion protocol, only `Exception.Stacktrace` +and `Thread.Stacktrace` should appear in user code. + +## v0.7.0 + +- feat: Include original error when event cannot be encoded as JSON (#258) +- feat: Use Hub from request context when available (#217, #259) +- feat: Extract stack frames from golang.org/x/xerrors (#262) +- feat: Make Environment Integration preserve existing context data (#261) +- feat: Recover and RecoverWithContext with arbitrary types (#268) +- feat: Report bad usage of CaptureMessage and CaptureEvent (#269) +- feat: Send debug logging to stderr by default (#266) +- feat: Several improvements to documentation (#223, #245, #250, #265) +- feat: Example of Recover followed by panic (#241, #247) +- feat: Add Transactions and Spans (to support OpenTelemetry Sentry Exporter) (#235, #243, #254) +- fix: Set either Frame.Filename or Frame.AbsPath (#233) +- fix: Clone requestBody to new Scope (#244) +- fix: Synchronize access and mutation of Hub.lastEventID (#264) +- fix: Avoid repeated syscalls in prepareEvent (#256) +- fix: Do not allocate new RNG for every event (#256) +- fix: Remove stale replace directive in go.mod (#255) +- fix(http): Deprecate HandleFunc, remove duplication (#260) + +_NOTE:_ +This version comes packed with several fixes and improvements and no breaking +changes. +Notably, there is a change in how the SDK reports file names in stack traces +that should resolve any ambiguity when looking at stack traces and using the +Suspect Commits feature. +We recommend all users to upgrade. + +## v0.6.1 + +- fix: Use NewEvent to init Event struct (#220) + +_NOTE:_ +A change introduced in v0.6.0 with the intent of avoiding allocations made a +pattern used in official examples break in certain circumstances (attempting +to write to a nil map). +This release reverts the change such that maps in the Event struct are always +allocated. + +## v0.6.0 + +- feat: Read module dependencies from runtime/debug (#199) +- feat: Support chained errors using Unwrap (#206) +- feat: Report chain of errors when available (#185) +- **[breaking]** fix: Accept http.RoundTripper to customize transport (#205) + Before the SDK accepted a concrete value of type `*http.Transport` in + `ClientOptions`, now it accepts any value implementing the `http.RoundTripper` + interface. Note that `*http.Transport` implements `http.RoundTripper`, so most + code bases will continue to work unchanged. + Users of custom transport gain the ability to pass in other implementations of + `http.RoundTripper` and may be able to simplify their code bases. +- fix: Do not panic when scope event processor drops event (#192) +- **[breaking]** fix: Use time.Time for timestamps (#191) + Users of sentry-go typically do not need to manipulate timestamps manually. + For those who do, the field type changed from `int64` to `time.Time`, which + should be more convenient to use. The recommended way to get the current time + is `time.Now().UTC()`. +- fix: Report usage error including stack trace (#189) +- feat: Add Exception.ThreadID field (#183) +- ci: Test against Go 1.14, drop 1.11 (#170) +- feat: Limit reading bytes from request bodies (#168) +- **[breaking]** fix: Rename fasthttp integration package sentryhttp => sentryfasthttp + The current recommendation is to use a named import, in which case existing + code should not require any change: + ```go + package main + + import ( + "fmt" + + "github.com/getsentry/sentry-go" + sentryfasthttp "github.com/getsentry/sentry-go/fasthttp" + "github.com/valyala/fasthttp" + ) + ``` + +_NOTE:_ +This version includes some new features and a few breaking changes, none of +which should pose troubles with upgrading. Most code bases should be able to +upgrade without any changes. + +## v0.5.1 + +- fix: Ignore err.Cause() when it is nil (#160) + +## v0.5.0 + +- fix: Synchronize access to HTTPTransport.disabledUntil (#158) +- docs: Update Flush documentation (#153) +- fix: HTTPTransport.Flush panic and data race (#140) + +_NOTE:_ +This version changes the implementation of the default transport, modifying the +behavior of `sentry.Flush`. The previous behavior was to wait until there were +no buffered events; new concurrent events kept `Flush` from returning. The new +behavior is to wait until the last event prior to the call to `Flush` has been +sent or the timeout; new concurrent events have no effect. The new behavior is +inline with the [Unified API +Guidelines](https://docs.sentry.io/development/sdk-dev/unified-api/). + +We have updated the documentation and examples to clarify that `Flush` is meant +to be called typically only once before program termination, to wait for +in-flight events to be sent to Sentry. Calling `Flush` after every event is not +recommended, as it introduces unnecessary latency to the surrounding function. +Please verify the usage of `sentry.Flush` in your code base. + +## v0.4.0 + +- fix(stacktrace): Correctly report package names (#127) +- fix(stacktrace): Do not rely on AbsPath of files (#123) +- build: Require github.com/ugorji/go@v1.1.7 (#110) +- fix: Correctly store last event id (#99) +- fix: Include request body in event payload (#94) +- build: Reset go.mod version to 1.11 (#109) +- fix: Eliminate data race in modules integration (#105) +- feat: Add support for path prefixes in the DSN (#102) +- feat: Add HTTPClient option (#86) +- feat: Extract correct type and value from top-most error (#85) +- feat: Check for broken pipe errors in Gin integration (#82) +- fix: Client.CaptureMessage accept nil EventModifier (#72) + +## v0.3.1 + +- feat: Send extra information exposed by the Go runtime (#76) +- fix: Handle new lines in module integration (#65) +- fix: Make sure that cache is locked when updating for contextifyFramesIntegration +- ref: Update Iris integration and example to version 12 +- misc: Remove indirect dependencies in order to move them to separate go.mod files + +## v0.3.0 + +- feat: Retry event marshaling without contextual data if the first pass fails +- fix: Include `url.Parse` error in `DsnParseError` +- fix: Make more `Scope` methods safe for concurrency +- fix: Synchronize concurrent access to `Hub.client` +- ref: Remove mutex from `Scope` exported API +- ref: Remove mutex from `Hub` exported API +- ref: Compile regexps for `filterFrames` only once +- ref: Change `SampleRate` type to `float64` +- doc: `Scope.Clear` not safe for concurrent use +- ci: Test sentry-go with `go1.13`, drop `go1.10` + +_NOTE:_ +This version removes some of the internal APIs that landed publicly (namely `Hub/Scope` mutex structs) and may require (but shouldn't) some changes to your code. +It's not done through major version update, as we are still in `0.x` stage. + +## v0.2.1 + +- fix: Run `Contextify` integration on `Threads` as well + +## v0.2.0 + +- feat: Add `SetTransaction()` method on the `Scope` +- feat: `fasthttp` framework support with `sentryfasthttp` package +- fix: Add `RWMutex` locks to internal `Hub` and `Scope` changes + +## v0.1.3 + +- feat: Move frames context reading into `contextifyFramesIntegration` (#28) + +_NOTE:_ +In case of any performance issues due to source contexts IO, you can let us know and turn off the integration in the meantime with: + +```go +sentry.Init(sentry.ClientOptions{ + Integrations: func(integrations []sentry.Integration) []sentry.Integration { + var filteredIntegrations []sentry.Integration + for _, integration := range integrations { + if integration.Name() == "ContextifyFrames" { + continue + } + filteredIntegrations = append(filteredIntegrations, integration) + } + return filteredIntegrations + }, +}) +``` + +## v0.1.2 + +- feat: Better source code location resolution and more useful inapp frames (#26) +- feat: Use `noopTransport` when no `Dsn` provided (#27) +- ref: Allow empty `Dsn` instead of returning an error (#22) +- fix: Use `NewScope` instead of literal struct inside a `scope.Clear` call (#24) +- fix: Add to `WaitGroup` before the request is put inside a buffer (#25) + +## v0.1.1 + +- fix: Check for initialized `Client` in `AddBreadcrumbs` (#20) +- build: Bump version when releasing with Craft (#19) + +## v0.1.0 + +- First stable release! \o/ + +## v0.0.1-beta.5 + +- feat: **[breaking]** Add `NewHTTPTransport` and `NewHTTPSyncTransport` which accepts all transport options +- feat: New `HTTPSyncTransport` that blocks after each call +- feat: New `Echo` integration +- ref: **[breaking]** Remove `BufferSize` option from `ClientOptions` and move it to `HTTPTransport` instead +- ref: Export default `HTTPTransport` +- ref: Export `net/http` integration handler +- ref: Set `Request` instantly in the package handlers, not in `recoverWithSentry` so it can be accessed later on +- ci: Add craft config + +## v0.0.1-beta.4 + +- feat: `IgnoreErrors` client option and corresponding integration +- ref: Reworked `net/http` integration, wrote better example and complete readme +- ref: Reworked `Gin` integration, wrote better example and complete readme +- ref: Reworked `Iris` integration, wrote better example and complete readme +- ref: Reworked `Negroni` integration, wrote better example and complete readme +- ref: Reworked `Martini` integration, wrote better example and complete readme +- ref: Remove `Handle()` from frameworks handlers and return it directly from New + +## v0.0.1-beta.3 + +- feat: `Iris` framework support with `sentryiris` package +- feat: `Gin` framework support with `sentrygin` package +- feat: `Martini` framework support with `sentrymartini` package +- feat: `Negroni` framework support with `sentrynegroni` package +- feat: Add `Hub.Clone()` for easier frameworks integration +- feat: Return `EventID` from `Recovery` methods +- feat: Add `NewScope` and `NewEvent` functions and use them in the whole codebase +- feat: Add `AddEventProcessor` to the `Client` +- fix: Operate on requests body copy instead of the original +- ref: Try to read source files from the root directory, based on the filename as well, to make it work on AWS Lambda +- ref: Remove `gocertifi` dependence and document how to provide your own certificates +- ref: **[breaking]** Remove `Decorate` and `DecorateFunc` methods in favor of `sentryhttp` package +- ref: **[breaking]** Allow for integrations to live on the client, by passing client instance in `SetupOnce` method +- ref: **[breaking]** Remove `GetIntegration` from the `Hub` +- ref: **[breaking]** Remove `GlobalEventProcessors` getter from the public API + +## v0.0.1-beta.2 + +- feat: Add `AttachStacktrace` client option to include stacktrace for messages +- feat: Add `BufferSize` client option to configure transport buffer size +- feat: Add `SetRequest` method on a `Scope` to control `Request` context data +- feat: Add `FromHTTPRequest` for `Request` type for easier extraction +- ref: Extract `Request` information more accurately +- fix: Attach `ServerName`, `Release`, `Dist`, `Environment` options to the event +- fix: Don't log events dropped due to full transport buffer as sent +- fix: Don't panic and create an appropriate event when called `CaptureException` or `Recover` with `nil` value + +## v0.0.1-beta + +- Initial release diff --git a/vendor/github.com/getsentry/sentry-go/CONTRIBUTING.md b/vendor/github.com/getsentry/sentry-go/CONTRIBUTING.md new file mode 100644 index 00000000..094f5211 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/CONTRIBUTING.md @@ -0,0 +1,96 @@ +# Contributing to sentry-go + +Hey, thank you if you're reading this, we welcome your contribution! + +## Sending a Pull Request + +Please help us save time when reviewing your PR by following this simple +process: + +1. Is your PR a simple typo fix? Read no further, **click that green "Create + pull request" button**! + +2. For more complex PRs that involve behavior changes or new APIs, please + consider [opening an **issue**][new-issue] describing the problem you're + trying to solve if there's not one already. + + A PR is often one specific solution to a problem and sometimes talking about + the problem unfolds new possible solutions. Remember we will be responsible + for maintaining the changes later. + +3. Fixing a bug and changing a behavior? Please add automated tests to prevent + future regression. + +4. Practice writing good commit messages. We have [commit + guidelines][commit-guide]. + +5. We have [guidelines for PR submitters][pr-guide]. A short summary: + + - Good PR descriptions are very helpful and most of the time they include + **why** something is done and why done in this particular way. Also list + other possible solutions that were considered and discarded. + - Be your own first reviewer. Make sure your code compiles and passes the + existing tests. + +[new-issue]: https://github.com/getsentry/sentry-go/issues/new/choose +[commit-guide]: https://develop.sentry.dev/code-review/#commit-guidelines +[pr-guide]: https://develop.sentry.dev/code-review/#guidelines-for-submitters + +Please also read through our [SDK Development docs](https://develop.sentry.dev/sdk/). +It contains information about SDK features, expected payloads and best practices for +contributing to Sentry SDKs. + +## Community + +The public-facing channels for support and development of Sentry SDKs can be found on [Discord](https://discord.gg/Ww9hbqr). + +## Testing + +```console +$ go test +``` + +### Watch mode + +Use: https://github.com/cespare/reflex + +```console +$ reflex -g '*.go' -d "none" -- sh -c 'printf "\n"; go test' +``` + +### With data race detection + +```console +$ go test -race +``` + +### Coverage + +```console +$ go test -race -coverprofile=coverage.txt -covermode=atomic && go tool cover -html coverage.txt +``` + +## Linting + +```console +$ golangci-lint run +``` + +## Release + +1. Update `CHANGELOG.md` with new version in `vX.X.X` format title and list of changes. + + The command below can be used to get a list of changes since the last tag, with the format used in `CHANGELOG.md`: + + ```console + $ git log --no-merges --format=%s $(git describe --abbrev=0).. | sed 's/^/- /' + ``` + +2. Commit with `misc: vX.X.X changelog` commit message and push to `master`. + +3. Let [`craft`](https://github.com/getsentry/craft) do the rest: + + ```console + $ craft prepare X.X.X + $ craft publish X.X.X + ``` diff --git a/vendor/github.com/getsentry/sentry-go/LICENSE b/vendor/github.com/getsentry/sentry-go/LICENSE new file mode 100644 index 00000000..3e66f28d --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/getsentry/sentry-go/MIGRATION.md b/vendor/github.com/getsentry/sentry-go/MIGRATION.md new file mode 100644 index 00000000..2c30d628 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/MIGRATION.md @@ -0,0 +1,3 @@ +# `raven-go` to `sentry-go` Migration Guide + +A [`raven-go` to `sentry-go` migration guide](https://docs.sentry.io/platforms/go/migration/) is available at the official Sentry documentation site. diff --git a/vendor/github.com/getsentry/sentry-go/README.md b/vendor/github.com/getsentry/sentry-go/README.md new file mode 100644 index 00000000..7bc1c705 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/README.md @@ -0,0 +1,105 @@ +

+ + + + + Sentry + + +

+ +# Official Sentry SDK for Go + +[![Build Status](https://github.com/getsentry/sentry-go/workflows/go-workflow/badge.svg)](https://github.com/getsentry/sentry-go/actions?query=workflow%3Ago-workflow) +[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/sentry-go)](https://goreportcard.com/report/github.com/getsentry/sentry-go) +[![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) +[![GoDoc](https://godoc.org/github.com/getsentry/sentry-go?status.svg)](https://godoc.org/github.com/getsentry/sentry-go) +[![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go) + +`sentry-go` provides a Sentry client implementation for the Go programming +language. This is the next generation of the Go SDK for [Sentry](https://sentry.io/), +intended to replace the `raven-go` package. + +> Looking for the old `raven-go` SDK documentation? See the Legacy client section [here](https://docs.sentry.io/clients/go/). +> If you want to start using `sentry-go` instead, check out the [migration guide](https://docs.sentry.io/platforms/go/migration/). + +## Requirements + +The only requirement is a Go compiler. + +We verify this package against the 3 most recent releases of Go. Those are the +supported versions. The exact versions are defined in +[`GitHub workflow`](.github/workflows/test.yml). + +In addition, we run tests against the current master branch of the Go toolchain, +though support for this configuration is best-effort. + +## Installation + +`sentry-go` can be installed like any other Go library through `go get`: + +```console +$ go get github.com/getsentry/sentry-go@latest +``` + +Check out the [list of released versions](https://pkg.go.dev/github.com/getsentry/sentry-go?tab=versions). + +## Configuration + +To use `sentry-go`, you’ll need to import the `sentry-go` package and initialize +it with your DSN and other [options](https://pkg.go.dev/github.com/getsentry/sentry-go#ClientOptions). + +If not specified in the SDK initialization, the +[DSN](https://docs.sentry.io/product/sentry-basics/dsn-explainer/), +[Release](https://docs.sentry.io/product/releases/) and +[Environment](https://docs.sentry.io/product/sentry-basics/environments/) +are read from the environment variables `SENTRY_DSN`, `SENTRY_RELEASE` and +`SENTRY_ENVIRONMENT`, respectively. + +More on this in the [Configuration section of the official Sentry Go SDK documentation](https://docs.sentry.io/platforms/go/configuration/). + +## Usage + +The SDK supports reporting errors and tracking application performance. + +To get started, have a look at one of our [examples](example/): +- [Basic error instrumentation](example/basic/main.go) +- [Error and tracing for HTTP servers](example/http/main.go) + +We also provide a [complete API reference](https://pkg.go.dev/github.com/getsentry/sentry-go). + +For more detailed information about how to get the most out of `sentry-go`, +checkout the official documentation: + +- [Sentry Go SDK documentation](https://docs.sentry.io/platforms/go/) +- Guides: + - [net/http](https://docs.sentry.io/platforms/go/guides/http/) + - [echo](https://docs.sentry.io/platforms/go/guides/echo/) + - [fasthttp](https://docs.sentry.io/platforms/go/guides/fasthttp/) + - [gin](https://docs.sentry.io/platforms/go/guides/gin/) + - [iris](https://docs.sentry.io/platforms/go/guides/iris/) + - [martini](https://docs.sentry.io/platforms/go/guides/martini/) + - [negroni](https://docs.sentry.io/platforms/go/guides/negroni/) + +## Resources + +- [Bug Tracker](https://github.com/getsentry/sentry-go/issues) +- [GitHub Project](https://github.com/getsentry/sentry-go) +- [![GoDoc](https://godoc.org/github.com/getsentry/sentry-go?status.svg)](https://godoc.org/github.com/getsentry/sentry-go) +- [![go.dev](https://img.shields.io/badge/go.dev-pkg-007d9c.svg?style=flat)](https://pkg.go.dev/github.com/getsentry/sentry-go) +- [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/go/) +- [![Discussions](https://img.shields.io/github/discussions/getsentry/sentry-go.svg)](https://github.com/getsentry/sentry-go/discussions) +- [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) +- [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) +- [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) + +## License + +Licensed under +[The 2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause), see +[`LICENSE`](LICENSE). + +## Community + +Join Sentry's [`#go` channel on Discord](https://discord.gg/Ww9hbqr) to get +involved and help us improve the SDK! diff --git a/vendor/github.com/getsentry/sentry-go/client.go b/vendor/github.com/getsentry/sentry-go/client.go new file mode 100644 index 00000000..2c580b03 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/client.go @@ -0,0 +1,682 @@ +package sentry + +import ( + "context" + "crypto/x509" + "fmt" + "io" + "log" + "math/rand" + "net/http" + "os" + "reflect" + "sort" + "strings" + "sync" + "time" + + "github.com/getsentry/sentry-go/internal/debug" +) + +// maxErrorDepth is the maximum number of errors reported in a chain of errors. +// This protects the SDK from an arbitrarily long chain of wrapped errors. +// +// An additional consideration is that arguably reporting a long chain of errors +// is of little use when debugging production errors with Sentry. The Sentry UI +// is not optimized for long chains either. The top-level error together with a +// stack trace is often the most useful information. +const maxErrorDepth = 10 + +// defaultMaxSpans limits the default number of recorded spans per transaction. The limit is +// meant to bound memory usage and prevent too large transaction events that +// would be rejected by Sentry. +const defaultMaxSpans = 1000 + +// hostname is the host name reported by the kernel. It is precomputed once to +// avoid syscalls when capturing events. +// +// The error is ignored because retrieving the host name is best-effort. If the +// error is non-nil, there is nothing to do other than retrying. We choose not +// to retry for now. +var hostname, _ = os.Hostname() + +// lockedRand is a random number generator safe for concurrent use. Its API is +// intentionally limited and it is not meant as a full replacement for a +// rand.Rand. +type lockedRand struct { + mu sync.Mutex + r *rand.Rand +} + +// Float64 returns a pseudo-random number in [0.0,1.0). +func (r *lockedRand) Float64() float64 { + r.mu.Lock() + defer r.mu.Unlock() + return r.r.Float64() +} + +// rng is the internal random number generator. +// +// We do not use the global functions from math/rand because, while they are +// safe for concurrent use, any package in a build could change the seed and +// affect the generated numbers, for instance making them deterministic. On the +// other hand, the source returned from rand.NewSource is not safe for +// concurrent use, so we need to couple its use with a sync.Mutex. +var rng = &lockedRand{ + // #nosec G404 -- We are fine using transparent, non-secure value here. + r: rand.New(rand.NewSource(time.Now().UnixNano())), +} + +// usageError is used to report to Sentry an SDK usage error. +// +// It is not exported because it is never returned by any function or method in +// the exported API. +type usageError struct { + error +} + +// Logger is an instance of log.Logger that is use to provide debug information about running Sentry Client +// can be enabled by either using Logger.SetOutput directly or with Debug client option. +var Logger = log.New(io.Discard, "[Sentry] ", log.LstdFlags) + +// EventProcessor is a function that processes an event. +// Event processors are used to change an event before it is sent to Sentry. +type EventProcessor func(event *Event, hint *EventHint) *Event + +// EventModifier is the interface that wraps the ApplyToEvent method. +// +// ApplyToEvent changes an event based on external data and/or +// an event hint. +type EventModifier interface { + ApplyToEvent(event *Event, hint *EventHint) *Event +} + +var globalEventProcessors []EventProcessor + +// AddGlobalEventProcessor adds processor to the global list of event +// processors. Global event processors apply to all events. +// +// AddGlobalEventProcessor is deprecated. Most users will prefer to initialize +// the SDK with Init and provide a ClientOptions.BeforeSend function or use +// Scope.AddEventProcessor instead. +func AddGlobalEventProcessor(processor EventProcessor) { + globalEventProcessors = append(globalEventProcessors, processor) +} + +// Integration allows for registering a functions that modify or discard captured events. +type Integration interface { + Name() string + SetupOnce(client *Client) +} + +// ClientOptions that configures a SDK Client. +type ClientOptions struct { + // The DSN to use. If the DSN is not set, the client is effectively + // disabled. + Dsn string + // In debug mode, the debug information is printed to stdout to help you + // understand what sentry is doing. + Debug bool + // Configures whether SDK should generate and attach stacktraces to pure + // capture message calls. + AttachStacktrace bool + // The sample rate for event submission in the range [0.0, 1.0]. By default, + // all events are sent. Thus, as a historical special case, the sample rate + // 0.0 is treated as if it was 1.0. To drop all events, set the DSN to the + // empty string. + SampleRate float64 + // Enable performance tracing. + EnableTracing bool + // The sample rate for sampling traces in the range [0.0, 1.0]. + TracesSampleRate float64 + // Used to customize the sampling of traces, overrides TracesSampleRate. + TracesSampler TracesSampler + // List of regexp strings that will be used to match against event's message + // and if applicable, caught errors type and value. + // If the match is found, then a whole event will be dropped. + IgnoreErrors []string + // If this flag is enabled, certain personally identifiable information (PII) is added by active integrations. + // By default, no such data is sent. + SendDefaultPII bool + // BeforeSend is called before error events are sent to Sentry. + // Use it to mutate the event or return nil to discard the event. + // See EventProcessor if you need to mutate transactions. + BeforeSend func(event *Event, hint *EventHint) *Event + // Before breadcrumb add callback. + BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb + // Integrations to be installed on the current Client, receives default + // integrations. + Integrations func([]Integration) []Integration + // io.Writer implementation that should be used with the Debug mode. + DebugWriter io.Writer + // The transport to use. Defaults to HTTPTransport. + Transport Transport + // The server name to be reported. + ServerName string + // The release to be sent with events. + // + // Some Sentry features are built around releases, and, thus, reporting + // events with a non-empty release improves the product experience. See + // https://docs.sentry.io/product/releases/. + // + // If Release is not set, the SDK will try to derive a default value + // from environment variables or the Git repository in the working + // directory. + // + // If you distribute a compiled binary, it is recommended to set the + // Release value explicitly at build time. As an example, you can use: + // + // go build -ldflags='-X main.release=VALUE' + // + // That will set the value of a predeclared variable 'release' in the + // 'main' package to 'VALUE'. Then, use that variable when initializing + // the SDK: + // + // sentry.Init(ClientOptions{Release: release}) + // + // See https://golang.org/cmd/go/ and https://golang.org/cmd/link/ for + // the official documentation of -ldflags and -X, respectively. + Release string + // The dist to be sent with events. + Dist string + // The environment to be sent with events. + Environment string + // Maximum number of breadcrumbs + // when MaxBreadcrumbs is negative then ignore breadcrumbs. + MaxBreadcrumbs int + // Maximum number of spans. + // + // See https://develop.sentry.dev/sdk/envelopes/#size-limits for size limits + // applied during event ingestion. Events that exceed these limits might get dropped. + MaxSpans int + // An optional pointer to http.Client that will be used with a default + // HTTPTransport. Using your own client will make HTTPTransport, HTTPProxy, + // HTTPSProxy and CaCerts options ignored. + HTTPClient *http.Client + // An optional pointer to http.Transport that will be used with a default + // HTTPTransport. Using your own transport will make HTTPProxy, HTTPSProxy + // and CaCerts options ignored. + HTTPTransport http.RoundTripper + // An optional HTTP proxy to use. + // This will default to the HTTP_PROXY environment variable. + HTTPProxy string + // An optional HTTPS proxy to use. + // This will default to the HTTPS_PROXY environment variable. + // HTTPS_PROXY takes precedence over HTTP_PROXY for https requests. + HTTPSProxy string + // An optional set of SSL certificates to use. + CaCerts *x509.CertPool + // MaxErrorDepth is the maximum number of errors reported in a chain of errors. + // This protects the SDK from an arbitrarily long chain of wrapped errors. + // + // An additional consideration is that arguably reporting a long chain of errors + // is of little use when debugging production errors with Sentry. The Sentry UI + // is not optimized for long chains either. The top-level error together with a + // stack trace is often the most useful information. + MaxErrorDepth int +} + +// Client is the underlying processor that is used by the main API and Hub +// instances. It must be created with NewClient. +type Client struct { + options ClientOptions + dsn *Dsn + eventProcessors []EventProcessor + integrations []Integration + // Transport is read-only. Replacing the transport of an existing client is + // not supported, create a new client instead. + Transport Transport +} + +// NewClient creates and returns an instance of Client configured using +// ClientOptions. +// +// Most users will not create clients directly. Instead, initialize the SDK with +// Init and use the package-level functions (for simple programs that run on a +// single goroutine) or hub methods (for concurrent programs, for example web +// servers). +func NewClient(options ClientOptions) (*Client, error) { + if options.Debug { + debugWriter := options.DebugWriter + if debugWriter == nil { + debugWriter = os.Stderr + } + Logger.SetOutput(debugWriter) + } + + if options.Dsn == "" { + options.Dsn = os.Getenv("SENTRY_DSN") + } + + if options.Release == "" { + options.Release = defaultRelease() + } + + if options.Environment == "" { + options.Environment = os.Getenv("SENTRY_ENVIRONMENT") + } + + if options.MaxErrorDepth == 0 { + options.MaxErrorDepth = maxErrorDepth + } + + if options.MaxSpans == 0 { + options.MaxSpans = defaultMaxSpans + } + + // SENTRYGODEBUG is a comma-separated list of key=value pairs (similar + // to GODEBUG). It is not a supported feature: recognized debug options + // may change any time. + // + // The intended public is SDK developers. It is orthogonal to + // options.Debug, which is also available for SDK users. + dbg := strings.Split(os.Getenv("SENTRYGODEBUG"), ",") + sort.Strings(dbg) + // dbgOpt returns true when the given debug option is enabled, for + // example SENTRYGODEBUG=someopt=1. + dbgOpt := func(opt string) bool { + s := opt + "=1" + return dbg[sort.SearchStrings(dbg, s)%len(dbg)] == s + } + if dbgOpt("httpdump") || dbgOpt("httptrace") { + options.HTTPTransport = &debug.Transport{ + RoundTripper: http.DefaultTransport, + Output: os.Stderr, + Dump: dbgOpt("httpdump"), + Trace: dbgOpt("httptrace"), + } + } + + var dsn *Dsn + if options.Dsn != "" { + var err error + dsn, err = NewDsn(options.Dsn) + if err != nil { + return nil, err + } + } + + client := Client{ + options: options, + dsn: dsn, + } + + client.setupTransport() + client.setupIntegrations() + + return &client, nil +} + +func (client *Client) setupTransport() { + opts := client.options + transport := opts.Transport + + if transport == nil { + if opts.Dsn == "" { + transport = new(noopTransport) + } else { + httpTransport := NewHTTPTransport() + // When tracing is enabled, use larger buffer to + // accommodate more concurrent events. + // TODO(tracing): consider using separate buffers per + // event type. + if opts.EnableTracing { + httpTransport.BufferSize = 1000 + } + transport = httpTransport + } + } + + transport.Configure(opts) + client.Transport = transport +} + +func (client *Client) setupIntegrations() { + integrations := []Integration{ + new(contextifyFramesIntegration), + new(environmentIntegration), + new(modulesIntegration), + new(ignoreErrorsIntegration), + } + + if client.options.Integrations != nil { + integrations = client.options.Integrations(integrations) + } + + for _, integration := range integrations { + if client.integrationAlreadyInstalled(integration.Name()) { + Logger.Printf("Integration %s is already installed\n", integration.Name()) + continue + } + client.integrations = append(client.integrations, integration) + integration.SetupOnce(client) + Logger.Printf("Integration installed: %s\n", integration.Name()) + } + + sort.Slice(client.integrations, func(i, j int) bool { + return client.integrations[i].Name() < client.integrations[j].Name() + }) +} + +// AddEventProcessor adds an event processor to the client. It must not be +// called from concurrent goroutines. Most users will prefer to use +// ClientOptions.BeforeSend or Scope.AddEventProcessor instead. +// +// Note that typical programs have only a single client created by Init and the +// client is shared among multiple hubs, one per goroutine, such that adding an +// event processor to the client affects all hubs that share the client. +func (client *Client) AddEventProcessor(processor EventProcessor) { + client.eventProcessors = append(client.eventProcessors, processor) +} + +// Options return ClientOptions for the current Client. +func (client Client) Options() ClientOptions { + return client.options +} + +// CaptureMessage captures an arbitrary message. +func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID { + event := client.eventFromMessage(message, LevelInfo) + return client.CaptureEvent(event, hint, scope) +} + +// CaptureException captures an error. +func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID { + event := client.eventFromException(exception, LevelError) + return client.CaptureEvent(event, hint, scope) +} + +// CaptureEvent captures an event on the currently active client if any. +// +// The event must already be assembled. Typically code would instead use +// the utility methods like CaptureException. The return value is the +// event ID. In case Sentry is disabled or event was dropped, the return value will be nil. +func (client *Client) CaptureEvent(event *Event, hint *EventHint, scope EventModifier) *EventID { + return client.processEvent(event, hint, scope) +} + +// Recover captures a panic. +// Returns EventID if successfully, or nil if there's no error to recover from. +func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModifier) *EventID { + if err == nil { + err = recover() + } + + // Normally we would not pass a nil Context, but RecoverWithContext doesn't + // use the Context for communicating deadline nor cancelation. All it does + // is store the Context in the EventHint and there nil means the Context is + // not available. + // nolint: staticcheck + return client.RecoverWithContext(nil, err, hint, scope) +} + +// RecoverWithContext captures a panic and passes relevant context object. +// Returns EventID if successfully, or nil if there's no error to recover from. +func (client *Client) RecoverWithContext( + ctx context.Context, + err interface{}, + hint *EventHint, + scope EventModifier, +) *EventID { + if err == nil { + err = recover() + } + if err == nil { + return nil + } + + if ctx != nil { + if hint == nil { + hint = &EventHint{} + } + if hint.Context == nil { + hint.Context = ctx + } + } + + var event *Event + switch err := err.(type) { + case error: + event = client.eventFromException(err, LevelFatal) + case string: + event = client.eventFromMessage(err, LevelFatal) + default: + event = client.eventFromMessage(fmt.Sprintf("%#v", err), LevelFatal) + } + return client.CaptureEvent(event, hint, scope) +} + +// Flush waits until the underlying Transport sends any buffered events to the +// Sentry server, blocking for at most the given timeout. It returns false if +// the timeout was reached. In that case, some events may not have been sent. +// +// Flush should be called before terminating the program to avoid +// unintentionally dropping events. +// +// Do not call Flush indiscriminately after every call to CaptureEvent, +// CaptureException or CaptureMessage. Instead, to have the SDK send events over +// the network synchronously, configure it to use the HTTPSyncTransport in the +// call to Init. +func (client *Client) Flush(timeout time.Duration) bool { + return client.Transport.Flush(timeout) +} + +func (client *Client) eventFromMessage(message string, level Level) *Event { + if message == "" { + err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())} + return client.eventFromException(err, level) + } + event := NewEvent() + event.Level = level + event.Message = message + + if client.Options().AttachStacktrace { + event.Threads = []Thread{{ + Stacktrace: NewStacktrace(), + Crashed: false, + Current: true, + }} + } + + return event +} + +func (client *Client) eventFromException(exception error, level Level) *Event { + err := exception + if err == nil { + err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())} + } + + event := NewEvent() + event.Level = level + + for i := 0; i < client.options.MaxErrorDepth && err != nil; i++ { + event.Exception = append(event.Exception, Exception{ + Value: err.Error(), + Type: reflect.TypeOf(err).String(), + Stacktrace: ExtractStacktrace(err), + }) + switch previous := err.(type) { + case interface{ Unwrap() error }: + err = previous.Unwrap() + case interface{ Cause() error }: + err = previous.Cause() + default: + err = nil + } + } + + // Add a trace of the current stack to the most recent error in a chain if + // it doesn't have a stack trace yet. + // We only add to the most recent error to avoid duplication and because the + // current stack is most likely unrelated to errors deeper in the chain. + if event.Exception[0].Stacktrace == nil { + event.Exception[0].Stacktrace = NewStacktrace() + } + + // event.Exception should be sorted such that the most recent error is last. + reverse(event.Exception) + + return event +} + +// reverse reverses the slice a in place. +func reverse(a []Exception) { + for i := len(a)/2 - 1; i >= 0; i-- { + opp := len(a) - 1 - i + a[i], a[opp] = a[opp], a[i] + } +} + +func (client *Client) processEvent(event *Event, hint *EventHint, scope EventModifier) *EventID { + if event == nil { + err := usageError{fmt.Errorf("%s called with nil event", callerFunctionName())} + return client.CaptureException(err, hint, scope) + } + + options := client.Options() + + // The default error event sample rate for all SDKs is 1.0 (send all). + // + // In Go, the zero value (default) for float64 is 0.0, which means that + // constructing a client with NewClient(ClientOptions{}), or, equivalently, + // initializing the SDK with Init(ClientOptions{}) without an explicit + // SampleRate would drop all events. + // + // To retain the desired default behavior, we exceptionally flip SampleRate + // from 0.0 to 1.0 here. Setting the sample rate to 0.0 is not very useful + // anyway, and the same end result can be achieved in many other ways like + // not initializing the SDK, setting the DSN to the empty string or using an + // event processor that always returns nil. + // + // An alternative API could be such that default options don't need to be + // the same as Go's zero values, for example using the Functional Options + // pattern. That would either require a breaking change if we want to reuse + // the obvious NewClient name, or a new function as an alternative + // constructor. + if options.SampleRate == 0.0 { + options.SampleRate = 1.0 + } + + // Transactions are sampled by options.TracesSampleRate or + // options.TracesSampler when they are started. All other events + // (errors, messages) are sampled here. + if event.Type != transactionType && !sample(options.SampleRate) { + Logger.Println("Event dropped due to SampleRate hit.") + return nil + } + + if event = client.prepareEvent(event, hint, scope); event == nil { + return nil + } + + // As per spec, transactions do not go through BeforeSend. + if event.Type != transactionType && options.BeforeSend != nil { + if hint == nil { + hint = &EventHint{} + } + if event = options.BeforeSend(event, hint); event == nil { + Logger.Println("Event dropped due to BeforeSend callback.") + return nil + } + } + + client.Transport.SendEvent(event) + + return &event.EventID +} + +func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventModifier) *Event { + if event.EventID == "" { + event.EventID = EventID(uuid()) + } + + if event.Timestamp.IsZero() { + event.Timestamp = time.Now() + } + + if event.Level == "" { + event.Level = LevelInfo + } + + if event.ServerName == "" { + event.ServerName = client.Options().ServerName + + if event.ServerName == "" { + event.ServerName = hostname + } + } + + if event.Release == "" { + event.Release = client.Options().Release + } + + if event.Dist == "" { + event.Dist = client.Options().Dist + } + + if event.Environment == "" { + event.Environment = client.Options().Environment + } + + event.Platform = "go" + event.Sdk = SdkInfo{ + Name: SDKIdentifier, + Version: SDKVersion, + Integrations: client.listIntegrations(), + Packages: []SdkPackage{{ + Name: "sentry-go", + Version: SDKVersion, + }}, + } + + if scope != nil { + event = scope.ApplyToEvent(event, hint) + if event == nil { + return nil + } + } + + for _, processor := range client.eventProcessors { + id := event.EventID + event = processor(event, hint) + if event == nil { + Logger.Printf("Event dropped by one of the Client EventProcessors: %s\n", id) + return nil + } + } + + for _, processor := range globalEventProcessors { + id := event.EventID + event = processor(event, hint) + if event == nil { + Logger.Printf("Event dropped by one of the Global EventProcessors: %s\n", id) + return nil + } + } + + return event +} + +func (client Client) listIntegrations() []string { + integrations := make([]string, len(client.integrations)) + for i, integration := range client.integrations { + integrations[i] = integration.Name() + } + return integrations +} + +func (client Client) integrationAlreadyInstalled(name string) bool { + for _, integration := range client.integrations { + if integration.Name() == name { + return true + } + } + return false +} + +// sample returns true with the given probability, which must be in the range +// [0.0, 1.0]. +func sample(probability float64) bool { + return rng.Float64() < probability +} diff --git a/vendor/github.com/getsentry/sentry-go/doc.go b/vendor/github.com/getsentry/sentry-go/doc.go new file mode 100644 index 00000000..f80fd402 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/doc.go @@ -0,0 +1,64 @@ +/* +Package sentry is the official Sentry SDK for Go. + +Use it to report errors and track application performance through distributed +tracing. + +For more information about Sentry and SDK features please have a look at the +documentation site https://docs.sentry.io/platforms/go/. + +# Basic Usage + +The first step is to initialize the SDK, providing at a minimum the DSN of your +Sentry project. This step is accomplished through a call to sentry.Init. + + func main() { + err := sentry.Init(...) + ... + } + +A more detailed yet simple example is available at +https://github.com/getsentry/sentry-go/blob/master/example/basic/main.go. + +# Error Reporting + +The Capture* functions report messages and errors to Sentry. + + sentry.CaptureMessage(...) + sentry.CaptureException(...) + sentry.CaptureEvent(...) + +Use similarly named functions in the Hub for concurrent programs like web +servers. + +# Performance Monitoring + +You can use Sentry to monitor your application's performance. More information +on the product page https://docs.sentry.io/product/performance/. + +The StartSpan function creates new spans. + + span := sentry.StartSpan(ctx, "operation") + ... + span.Finish() + +# Integrations + +The SDK has support for several Go frameworks, available as subpackages. + +# Getting Support + +For paid Sentry.io accounts, head out to https://sentry.io/support. + +For all users, support channels include: + + Forum: https://forum.sentry.io + Discord: https://discord.gg/Ww9hbqr (#go channel) + +If you found an issue with the SDK, please report through +https://github.com/getsentry/sentry-go/issues/new/choose. + +For responsibly disclosing a security issue, please follow the steps in +https://sentry.io/security/#vulnerability-disclosure. +*/ +package sentry diff --git a/vendor/github.com/getsentry/sentry-go/dsn.go b/vendor/github.com/getsentry/sentry-go/dsn.go new file mode 100644 index 00000000..5f5a13ce --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/dsn.go @@ -0,0 +1,204 @@ +package sentry + +import ( + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + "time" +) + +type scheme string + +const ( + schemeHTTP scheme = "http" + schemeHTTPS scheme = "https" +) + +func (scheme scheme) defaultPort() int { + switch scheme { + case schemeHTTPS: + return 443 + case schemeHTTP: + return 80 + default: + return 80 + } +} + +// DsnParseError represents an error that occurs if a Sentry +// DSN cannot be parsed. +type DsnParseError struct { + Message string +} + +func (e DsnParseError) Error() string { + return "[Sentry] DsnParseError: " + e.Message +} + +// Dsn is used as the remote address source to client transport. +type Dsn struct { + scheme scheme + publicKey string + secretKey string + host string + port int + path string + projectID string +} + +// NewDsn creates a Dsn by parsing rawURL. Most users will never call this +// function directly. It is provided for use in custom Transport +// implementations. +func NewDsn(rawURL string) (*Dsn, error) { + // Parse + parsedURL, err := url.Parse(rawURL) + if err != nil { + return nil, &DsnParseError{fmt.Sprintf("invalid url: %v", err)} + } + + // Scheme + var scheme scheme + switch parsedURL.Scheme { + case "http": + scheme = schemeHTTP + case "https": + scheme = schemeHTTPS + default: + return nil, &DsnParseError{"invalid scheme"} + } + + // PublicKey + publicKey := parsedURL.User.Username() + if publicKey == "" { + return nil, &DsnParseError{"empty username"} + } + + // SecretKey + var secretKey string + if parsedSecretKey, ok := parsedURL.User.Password(); ok { + secretKey = parsedSecretKey + } + + // Host + host := parsedURL.Hostname() + if host == "" { + return nil, &DsnParseError{"empty host"} + } + + // Port + var port int + if parsedURL.Port() != "" { + parsedPort, err := strconv.Atoi(parsedURL.Port()) + if err != nil { + return nil, &DsnParseError{"invalid port"} + } + port = parsedPort + } else { + port = scheme.defaultPort() + } + + // ProjectID + if parsedURL.Path == "" || parsedURL.Path == "/" { + return nil, &DsnParseError{"empty project id"} + } + pathSegments := strings.Split(parsedURL.Path[1:], "/") + projectID := pathSegments[len(pathSegments)-1] + + if projectID == "" { + return nil, &DsnParseError{"empty project id"} + } + + // Path + var path string + if len(pathSegments) > 1 { + path = "/" + strings.Join(pathSegments[0:len(pathSegments)-1], "/") + } + + return &Dsn{ + scheme: scheme, + publicKey: publicKey, + secretKey: secretKey, + host: host, + port: port, + path: path, + projectID: projectID, + }, nil +} + +// String formats Dsn struct into a valid string url. +func (dsn Dsn) String() string { + var url string + url += fmt.Sprintf("%s://%s", dsn.scheme, dsn.publicKey) + if dsn.secretKey != "" { + url += fmt.Sprintf(":%s", dsn.secretKey) + } + url += fmt.Sprintf("@%s", dsn.host) + if dsn.port != dsn.scheme.defaultPort() { + url += fmt.Sprintf(":%d", dsn.port) + } + if dsn.path != "" { + url += dsn.path + } + url += fmt.Sprintf("/%s", dsn.projectID) + return url +} + +// StoreAPIURL returns the URL of the store endpoint of the project associated +// with the DSN. +func (dsn Dsn) StoreAPIURL() *url.URL { + return dsn.getAPIURL("store") +} + +// EnvelopeAPIURL returns the URL of the envelope endpoint of the project +// associated with the DSN. +func (dsn Dsn) EnvelopeAPIURL() *url.URL { + return dsn.getAPIURL("envelope") +} + +func (dsn Dsn) getAPIURL(s string) *url.URL { + var rawURL string + rawURL += fmt.Sprintf("%s://%s", dsn.scheme, dsn.host) + if dsn.port != dsn.scheme.defaultPort() { + rawURL += fmt.Sprintf(":%d", dsn.port) + } + if dsn.path != "" { + rawURL += dsn.path + } + rawURL += fmt.Sprintf("/api/%s/%s/", dsn.projectID, s) + parsedURL, _ := url.Parse(rawURL) + return parsedURL +} + +// RequestHeaders returns all the necessary headers that have to be used in the transport. +func (dsn Dsn) RequestHeaders() map[string]string { + auth := fmt.Sprintf("Sentry sentry_version=%s, sentry_timestamp=%d, "+ + "sentry_client=sentry.go/%s, sentry_key=%s", apiVersion, time.Now().Unix(), Version, dsn.publicKey) + + if dsn.secretKey != "" { + auth = fmt.Sprintf("%s, sentry_secret=%s", auth, dsn.secretKey) + } + + return map[string]string{ + "Content-Type": "application/json", + "X-Sentry-Auth": auth, + } +} + +// MarshalJSON converts the Dsn struct to JSON. +func (dsn Dsn) MarshalJSON() ([]byte, error) { + return json.Marshal(dsn.String()) +} + +// UnmarshalJSON converts JSON data to the Dsn struct. +func (dsn *Dsn) UnmarshalJSON(data []byte) error { + var str string + _ = json.Unmarshal(data, &str) + newDsn, err := NewDsn(str) + if err != nil { + return err + } + *dsn = *newDsn + return nil +} diff --git a/vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go b/vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go new file mode 100644 index 00000000..3e54839b --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go @@ -0,0 +1,110 @@ +package sentry + +import ( + "strconv" + "strings" + + "github.com/getsentry/sentry-go/internal/otel/baggage" +) + +const ( + sentryPrefix = "sentry-" +) + +// DynamicSamplingContext holds information about the current event that can be used to make dynamic sampling decisions. +type DynamicSamplingContext struct { + Entries map[string]string + Frozen bool +} + +func DynamicSamplingContextFromHeader(header []byte) (DynamicSamplingContext, error) { + bag, err := baggage.Parse(string(header)) + if err != nil { + return DynamicSamplingContext{}, err + } + + entries := map[string]string{} + for _, member := range bag.Members() { + // We only store baggage members if their key starts with "sentry-". + if k, v := member.Key(), member.Value(); strings.HasPrefix(k, sentryPrefix) { + entries[strings.TrimPrefix(k, sentryPrefix)] = v + } + } + + return DynamicSamplingContext{ + Entries: entries, + Frozen: true, + }, nil +} + +func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext { + entries := map[string]string{} + + hub := hubFromContext(span.Context()) + scope := hub.Scope() + client := hub.Client() + options := client.Options() + + if traceID := span.TraceID.String(); traceID != "" { + entries["trace_id"] = traceID + } + if sampleRate := span.sampleRate; sampleRate != 0 { + entries["sample_rate"] = strconv.FormatFloat(sampleRate, 'f', -1, 64) + } + + if dsn := client.dsn; dsn != nil { + if publicKey := dsn.publicKey; publicKey != "" { + entries["public_key"] = publicKey + } + } + if release := options.Release; release != "" { + entries["release"] = release + } + if environment := options.Environment; environment != "" { + entries["environment"] = environment + } + + // Only include the transaction name if it's of good quality (not empty and not SourceURL) + if span.Source != "" && span.Source != SourceURL { + if transactionName := scope.Transaction(); transactionName != "" { + entries["transaction"] = transactionName + } + } + + if userSegment := scope.user.Segment; userSegment != "" { + entries["user_segment"] = userSegment + } + + return DynamicSamplingContext{ + Entries: entries, + Frozen: true, + } +} + +func (d DynamicSamplingContext) HasEntries() bool { + return len(d.Entries) > 0 +} + +func (d DynamicSamplingContext) IsFrozen() bool { + return d.Frozen +} + +func (d DynamicSamplingContext) String() string { + members := []baggage.Member{} + for k, entry := range d.Entries { + member, err := baggage.NewMember(sentryPrefix+k, entry) + if err != nil { + continue + } + members = append(members, member) + } + if len(members) > 0 { + baggage, err := baggage.New(members...) + if err != nil { + return "" + } + return baggage.String() + } + + return "" +} diff --git a/vendor/github.com/getsentry/sentry-go/hub.go b/vendor/github.com/getsentry/sentry-go/hub.go new file mode 100644 index 00000000..71608df3 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/hub.go @@ -0,0 +1,384 @@ +package sentry + +import ( + "context" + "sync" + "time" +) + +type contextKey int + +// Keys used to store values in a Context. Use with Context.Value to access +// values stored by the SDK. +const ( + // HubContextKey is the key used to store the current Hub. + HubContextKey = contextKey(1) + // RequestContextKey is the key used to store the current http.Request. + RequestContextKey = contextKey(2) +) + +// defaultMaxBreadcrumbs is the default maximum number of breadcrumbs added to +// an event. Can be overwritten with the maxBreadcrumbs option. +const defaultMaxBreadcrumbs = 30 + +// maxBreadcrumbs is the absolute maximum number of breadcrumbs added to an +// event. The maxBreadcrumbs option cannot be set higher than this value. +const maxBreadcrumbs = 100 + +// currentHub is the initial Hub with no Client bound and an empty Scope. +var currentHub = NewHub(nil, NewScope()) + +// Hub is the central object that manages scopes and clients. +// +// This can be used to capture events and manage the scope. +// The default hub that is available automatically. +// +// In most situations developers do not need to interface the hub. Instead +// toplevel convenience functions are exposed that will automatically dispatch +// to global (CurrentHub) hub. In some situations this might not be +// possible in which case it might become necessary to manually work with the +// hub. This is for instance the case when working with async code. +type Hub struct { + mu sync.RWMutex + stack *stack + lastEventID EventID +} + +type layer struct { + // mu protects concurrent reads and writes to client. + mu sync.RWMutex + client *Client + // scope is read-only, not protected by mu. + scope *Scope +} + +// Client returns the layer's client. Safe for concurrent use. +func (l *layer) Client() *Client { + l.mu.RLock() + defer l.mu.RUnlock() + return l.client +} + +// SetClient sets the layer's client. Safe for concurrent use. +func (l *layer) SetClient(c *Client) { + l.mu.Lock() + defer l.mu.Unlock() + l.client = c +} + +type stack []*layer + +// NewHub returns an instance of a Hub with provided Client and Scope bound. +func NewHub(client *Client, scope *Scope) *Hub { + hub := Hub{ + stack: &stack{{ + client: client, + scope: scope, + }}, + } + return &hub +} + +// CurrentHub returns an instance of previously initialized Hub stored in the global namespace. +func CurrentHub() *Hub { + return currentHub +} + +// LastEventID returns the ID of the last event (error or message) captured +// through the hub and sent to the underlying transport. +// +// Transactions and events dropped by sampling or event processors do not change +// the last event ID. +// +// LastEventID is a convenience method to cover use cases in which errors are +// captured indirectly and the ID is needed. For example, it can be used as part +// of an HTTP middleware to log the ID of the last error, if any. +// +// For more flexibility, consider instead using the ClientOptions.BeforeSend +// function or event processors. +func (hub *Hub) LastEventID() EventID { + hub.mu.RLock() + defer hub.mu.RUnlock() + + return hub.lastEventID +} + +// stackTop returns the top layer of the hub stack. Valid hubs always have at +// least one layer, therefore stackTop always return a non-nil pointer. +func (hub *Hub) stackTop() *layer { + hub.mu.RLock() + defer hub.mu.RUnlock() + + stack := hub.stack + stackLen := len(*stack) + top := (*stack)[stackLen-1] + return top +} + +// Clone returns a copy of the current Hub with top-most scope and client copied over. +func (hub *Hub) Clone() *Hub { + top := hub.stackTop() + scope := top.scope + if scope != nil { + scope = scope.Clone() + } + return NewHub(top.Client(), scope) +} + +// Scope returns top-level Scope of the current Hub or nil if no Scope is bound. +func (hub *Hub) Scope() *Scope { + top := hub.stackTop() + return top.scope +} + +// Client returns top-level Client of the current Hub or nil if no Client is bound. +func (hub *Hub) Client() *Client { + top := hub.stackTop() + return top.Client() +} + +// PushScope pushes a new scope for the current Hub and reuses previously bound Client. +func (hub *Hub) PushScope() *Scope { + top := hub.stackTop() + + var scope *Scope + if top.scope != nil { + scope = top.scope.Clone() + } else { + scope = NewScope() + } + + hub.mu.Lock() + defer hub.mu.Unlock() + + *hub.stack = append(*hub.stack, &layer{ + client: top.Client(), + scope: scope, + }) + + return scope +} + +// PopScope drops the most recent scope. +// +// Calls to PopScope must be coordinated with PushScope. For most cases, using +// WithScope should be more convenient. +// +// Calls to PopScope that do not match previous calls to PushScope are silently +// ignored. +func (hub *Hub) PopScope() { + hub.mu.Lock() + defer hub.mu.Unlock() + + stack := *hub.stack + stackLen := len(stack) + if stackLen > 1 { + // Never pop the last item off the stack, the stack should always have + // at least one item. + *hub.stack = stack[0 : stackLen-1] + } +} + +// BindClient binds a new Client for the current Hub. +func (hub *Hub) BindClient(client *Client) { + top := hub.stackTop() + top.SetClient(client) +} + +// WithScope runs f in an isolated temporary scope. +// +// It is useful when extra data should be sent with a single capture call, for +// instance a different level or tags. +// +// The scope passed to f starts as a clone of the current scope and can be +// freely modified without affecting the current scope. +// +// It is a shorthand for PushScope followed by PopScope. +func (hub *Hub) WithScope(f func(scope *Scope)) { + scope := hub.PushScope() + defer hub.PopScope() + f(scope) +} + +// ConfigureScope runs f in the current scope. +// +// It is useful to set data that applies to all events that share the current +// scope. +// +// Modifying the scope affects all references to the current scope. +// +// See also WithScope for making isolated temporary changes. +func (hub *Hub) ConfigureScope(f func(scope *Scope)) { + scope := hub.Scope() + f(scope) +} + +// CaptureEvent calls the method of a same name on currently bound Client instance +// passing it a top-level Scope. +// Returns EventID if successfully, or nil if there's no Scope or Client available. +func (hub *Hub) CaptureEvent(event *Event) *EventID { + client, scope := hub.Client(), hub.Scope() + if client == nil || scope == nil { + return nil + } + eventID := client.CaptureEvent(event, nil, scope) + + if event.Type != transactionType && eventID != nil { + hub.mu.Lock() + hub.lastEventID = *eventID + hub.mu.Unlock() + } + return eventID +} + +// CaptureMessage calls the method of a same name on currently bound Client instance +// passing it a top-level Scope. +// Returns EventID if successfully, or nil if there's no Scope or Client available. +func (hub *Hub) CaptureMessage(message string) *EventID { + client, scope := hub.Client(), hub.Scope() + if client == nil || scope == nil { + return nil + } + eventID := client.CaptureMessage(message, nil, scope) + + if eventID != nil { + hub.mu.Lock() + hub.lastEventID = *eventID + hub.mu.Unlock() + } + return eventID +} + +// CaptureException calls the method of a same name on currently bound Client instance +// passing it a top-level Scope. +// Returns EventID if successfully, or nil if there's no Scope or Client available. +func (hub *Hub) CaptureException(exception error) *EventID { + client, scope := hub.Client(), hub.Scope() + if client == nil || scope == nil { + return nil + } + eventID := client.CaptureException(exception, &EventHint{OriginalException: exception}, scope) + + if eventID != nil { + hub.mu.Lock() + hub.lastEventID = *eventID + hub.mu.Unlock() + } + return eventID +} + +// AddBreadcrumb records a new breadcrumb. +// +// The total number of breadcrumbs that can be recorded are limited by the +// configuration on the client. +func (hub *Hub) AddBreadcrumb(breadcrumb *Breadcrumb, hint *BreadcrumbHint) { + client := hub.Client() + + // If there's no client, just store it on the scope straight away + if client == nil { + hub.Scope().AddBreadcrumb(breadcrumb, maxBreadcrumbs) + return + } + + options := client.Options() + max := options.MaxBreadcrumbs + if max < 0 { + return + } + + if options.BeforeBreadcrumb != nil { + if hint == nil { + hint = &BreadcrumbHint{} + } + if breadcrumb = options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil { + Logger.Println("breadcrumb dropped due to BeforeBreadcrumb callback.") + return + } + } + + if max == 0 { + max = defaultMaxBreadcrumbs + } else if max > maxBreadcrumbs { + max = maxBreadcrumbs + } + + hub.Scope().AddBreadcrumb(breadcrumb, max) +} + +// Recover calls the method of a same name on currently bound Client instance +// passing it a top-level Scope. +// Returns EventID if successfully, or nil if there's no Scope or Client available. +func (hub *Hub) Recover(err interface{}) *EventID { + if err == nil { + err = recover() + } + client, scope := hub.Client(), hub.Scope() + if client == nil || scope == nil { + return nil + } + return client.Recover(err, &EventHint{RecoveredException: err}, scope) +} + +// RecoverWithContext calls the method of a same name on currently bound Client instance +// passing it a top-level Scope. +// Returns EventID if successfully, or nil if there's no Scope or Client available. +func (hub *Hub) RecoverWithContext(ctx context.Context, err interface{}) *EventID { + if err == nil { + err = recover() + } + client, scope := hub.Client(), hub.Scope() + if client == nil || scope == nil { + return nil + } + return client.RecoverWithContext(ctx, err, &EventHint{RecoveredException: err}, scope) +} + +// Flush waits until the underlying Transport sends any buffered events to the +// Sentry server, blocking for at most the given timeout. It returns false if +// the timeout was reached. In that case, some events may not have been sent. +// +// Flush should be called before terminating the program to avoid +// unintentionally dropping events. +// +// Do not call Flush indiscriminately after every call to CaptureEvent, +// CaptureException or CaptureMessage. Instead, to have the SDK send events over +// the network synchronously, configure it to use the HTTPSyncTransport in the +// call to Init. +func (hub *Hub) Flush(timeout time.Duration) bool { + client := hub.Client() + + if client == nil { + return false + } + + return client.Flush(timeout) +} + +// HasHubOnContext checks whether Hub instance is bound to a given Context struct. +func HasHubOnContext(ctx context.Context) bool { + _, ok := ctx.Value(HubContextKey).(*Hub) + return ok +} + +// GetHubFromContext tries to retrieve Hub instance from the given Context struct +// or return nil if one is not found. +func GetHubFromContext(ctx context.Context) *Hub { + if hub, ok := ctx.Value(HubContextKey).(*Hub); ok { + return hub + } + return nil +} + +// hubFromContext returns either a hub stored in the context or the current hub. +// The return value is guaranteed to be non-nil, unlike GetHubFromContext. +func hubFromContext(ctx context.Context) *Hub { + if hub, ok := ctx.Value(HubContextKey).(*Hub); ok { + return hub + } + return currentHub +} + +// SetHubOnContext stores given Hub instance on the Context struct and returns a new Context. +func SetHubOnContext(ctx context.Context, hub *Hub) context.Context { + return context.WithValue(ctx, HubContextKey, hub) +} diff --git a/vendor/github.com/getsentry/sentry-go/integrations.go b/vendor/github.com/getsentry/sentry-go/integrations.go new file mode 100644 index 00000000..6a9e55a2 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/integrations.go @@ -0,0 +1,293 @@ +package sentry + +import ( + "fmt" + "regexp" + "runtime" + "runtime/debug" + "strings" + "sync" +) + +// ================================ +// Modules Integration +// ================================ + +type modulesIntegration struct { + once sync.Once + modules map[string]string +} + +func (mi *modulesIntegration) Name() string { + return "Modules" +} + +func (mi *modulesIntegration) SetupOnce(client *Client) { + client.AddEventProcessor(mi.processor) +} + +func (mi *modulesIntegration) processor(event *Event, hint *EventHint) *Event { + if len(event.Modules) == 0 { + mi.once.Do(func() { + info, ok := debug.ReadBuildInfo() + if !ok { + Logger.Print("The Modules integration is not available in binaries built without module support.") + return + } + mi.modules = extractModules(info) + }) + } + event.Modules = mi.modules + return event +} + +func extractModules(info *debug.BuildInfo) map[string]string { + modules := map[string]string{ + info.Main.Path: info.Main.Version, + } + for _, dep := range info.Deps { + ver := dep.Version + if dep.Replace != nil { + ver += fmt.Sprintf(" => %s %s", dep.Replace.Path, dep.Replace.Version) + } + modules[dep.Path] = strings.TrimSuffix(ver, " ") + } + return modules +} + +// ================================ +// Environment Integration +// ================================ + +type environmentIntegration struct{} + +func (ei *environmentIntegration) Name() string { + return "Environment" +} + +func (ei *environmentIntegration) SetupOnce(client *Client) { + client.AddEventProcessor(ei.processor) +} + +func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Event { + // Initialize maps as necessary. + contextNames := []string{"device", "os", "runtime"} + if event.Contexts == nil { + event.Contexts = make(map[string]Context, len(contextNames)) + } + for _, name := range contextNames { + if event.Contexts[name] == nil { + event.Contexts[name] = make(Context) + } + } + + // Set contextual information preserving existing data. For each context, if + // the existing value is not of type map[string]interface{}, then no + // additional information is added. + if deviceContext, ok := event.Contexts["device"]; ok { + if _, ok := deviceContext["arch"]; !ok { + deviceContext["arch"] = runtime.GOARCH + } + if _, ok := deviceContext["num_cpu"]; !ok { + deviceContext["num_cpu"] = runtime.NumCPU() + } + } + if osContext, ok := event.Contexts["os"]; ok { + if _, ok := osContext["name"]; !ok { + osContext["name"] = runtime.GOOS + } + } + if runtimeContext, ok := event.Contexts["runtime"]; ok { + if _, ok := runtimeContext["name"]; !ok { + runtimeContext["name"] = "go" + } + if _, ok := runtimeContext["version"]; !ok { + runtimeContext["version"] = runtime.Version() + } + if _, ok := runtimeContext["go_numroutines"]; !ok { + runtimeContext["go_numroutines"] = runtime.NumGoroutine() + } + if _, ok := runtimeContext["go_maxprocs"]; !ok { + runtimeContext["go_maxprocs"] = runtime.GOMAXPROCS(0) + } + if _, ok := runtimeContext["go_numcgocalls"]; !ok { + runtimeContext["go_numcgocalls"] = runtime.NumCgoCall() + } + } + return event +} + +// ================================ +// Ignore Errors Integration +// ================================ + +type ignoreErrorsIntegration struct { + ignoreErrors []*regexp.Regexp +} + +func (iei *ignoreErrorsIntegration) Name() string { + return "IgnoreErrors" +} + +func (iei *ignoreErrorsIntegration) SetupOnce(client *Client) { + iei.ignoreErrors = transformStringsIntoRegexps(client.Options().IgnoreErrors) + client.AddEventProcessor(iei.processor) +} + +func (iei *ignoreErrorsIntegration) processor(event *Event, hint *EventHint) *Event { + suspects := getIgnoreErrorsSuspects(event) + + for _, suspect := range suspects { + for _, pattern := range iei.ignoreErrors { + if pattern.Match([]byte(suspect)) { + Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+ + "| Value matched: %s | Filter used: %s", suspect, pattern) + return nil + } + } + } + + return event +} + +func transformStringsIntoRegexps(strings []string) []*regexp.Regexp { + var exprs []*regexp.Regexp + + for _, s := range strings { + r, err := regexp.Compile(s) + if err == nil { + exprs = append(exprs, r) + } + } + + return exprs +} + +func getIgnoreErrorsSuspects(event *Event) []string { + suspects := []string{} + + if event.Message != "" { + suspects = append(suspects, event.Message) + } + + for _, ex := range event.Exception { + suspects = append(suspects, ex.Type, ex.Value) + } + + return suspects +} + +// ================================ +// Contextify Frames Integration +// ================================ + +type contextifyFramesIntegration struct { + sr sourceReader + contextLines int + cachedLocations sync.Map +} + +func (cfi *contextifyFramesIntegration) Name() string { + return "ContextifyFrames" +} + +func (cfi *contextifyFramesIntegration) SetupOnce(client *Client) { + cfi.sr = newSourceReader() + cfi.contextLines = 5 + + client.AddEventProcessor(cfi.processor) +} + +func (cfi *contextifyFramesIntegration) processor(event *Event, hint *EventHint) *Event { + // Range over all exceptions + for _, ex := range event.Exception { + // If it has no stacktrace, just bail out + if ex.Stacktrace == nil { + continue + } + + // If it does, it should have frames, so try to contextify them + ex.Stacktrace.Frames = cfi.contextify(ex.Stacktrace.Frames) + } + + // Range over all threads + for _, th := range event.Threads { + // If it has no stacktrace, just bail out + if th.Stacktrace == nil { + continue + } + + // If it does, it should have frames, so try to contextify them + th.Stacktrace.Frames = cfi.contextify(th.Stacktrace.Frames) + } + + return event +} + +func (cfi *contextifyFramesIntegration) contextify(frames []Frame) []Frame { + contextifiedFrames := make([]Frame, 0, len(frames)) + + for _, frame := range frames { + if !frame.InApp { + contextifiedFrames = append(contextifiedFrames, frame) + continue + } + + var path string + + if cachedPath, ok := cfi.cachedLocations.Load(frame.AbsPath); ok { + if p, ok := cachedPath.(string); ok { + path = p + } + } else { + // Optimize for happy path here + if fileExists(frame.AbsPath) { + path = frame.AbsPath + } else { + path = cfi.findNearbySourceCodeLocation(frame.AbsPath) + } + } + + if path == "" { + contextifiedFrames = append(contextifiedFrames, frame) + continue + } + + lines, contextLine := cfi.sr.readContextLines(path, frame.Lineno, cfi.contextLines) + contextifiedFrames = append(contextifiedFrames, cfi.addContextLinesToFrame(frame, lines, contextLine)) + } + + return contextifiedFrames +} + +func (cfi *contextifyFramesIntegration) findNearbySourceCodeLocation(originalPath string) string { + trimmedPath := strings.TrimPrefix(originalPath, "/") + components := strings.Split(trimmedPath, "/") + + for len(components) > 0 { + components = components[1:] + possibleLocation := strings.Join(components, "/") + + if fileExists(possibleLocation) { + cfi.cachedLocations.Store(originalPath, possibleLocation) + return possibleLocation + } + } + + cfi.cachedLocations.Store(originalPath, "") + return "" +} + +func (cfi *contextifyFramesIntegration) addContextLinesToFrame(frame Frame, lines [][]byte, contextLine int) Frame { + for i, line := range lines { + switch { + case i < contextLine: + frame.PreContext = append(frame.PreContext, string(line)) + case i == contextLine: + frame.ContextLine = string(line) + default: + frame.PostContext = append(frame.PostContext, string(line)) + } + } + return frame +} diff --git a/vendor/github.com/getsentry/sentry-go/interfaces.go b/vendor/github.com/getsentry/sentry-go/interfaces.go new file mode 100644 index 00000000..0d20ba39 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/interfaces.go @@ -0,0 +1,392 @@ +package sentry + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + "time" +) + +// Protocol Docs (kinda) +// https://github.com/getsentry/rust-sentry-types/blob/master/src/protocol/v7.rs + +// transactionType is the type of a transaction event. +const transactionType = "transaction" + +// Level marks the severity of the event. +type Level string + +// Describes the severity of the event. +const ( + LevelDebug Level = "debug" + LevelInfo Level = "info" + LevelWarning Level = "warning" + LevelError Level = "error" + LevelFatal Level = "fatal" +) + +func getSensitiveHeaders() map[string]bool { + return map[string]bool{ + "Authorization": true, + "Cookie": true, + "X-Forwarded-For": true, + "X-Real-Ip": true, + } +} + +// SdkInfo contains all metadata about about the SDK being used. +type SdkInfo struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + Integrations []string `json:"integrations,omitempty"` + Packages []SdkPackage `json:"packages,omitempty"` +} + +// SdkPackage describes a package that was installed. +type SdkPackage struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// TODO: This type could be more useful, as map of interface{} is too generic +// and requires a lot of type assertions in beforeBreadcrumb calls +// plus it could just be map[string]interface{} then. + +// BreadcrumbHint contains information that can be associated with a Breadcrumb. +type BreadcrumbHint map[string]interface{} + +// Breadcrumb specifies an application event that occurred before a Sentry event. +// An event may contain one or more breadcrumbs. +type Breadcrumb struct { + Type string `json:"type,omitempty"` + Category string `json:"category,omitempty"` + Message string `json:"message,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` + Level Level `json:"level,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// TODO: provide constants for known breadcrumb types. +// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types. + +// MarshalJSON converts the Breadcrumb struct to JSON. +func (b *Breadcrumb) MarshalJSON() ([]byte, error) { + // We want to omit time.Time zero values, otherwise the server will try to + // interpret dates too far in the past. However, encoding/json doesn't + // support the "omitempty" option for struct types. See + // https://golang.org/issues/11939. + // + // We overcome the limitation and achieve what we want by shadowing fields + // and a few type tricks. + + // breadcrumb aliases Breadcrumb to allow calling json.Marshal without an + // infinite loop. It preserves all fields while none of the attached + // methods. + type breadcrumb Breadcrumb + + if b.Timestamp.IsZero() { + return json.Marshal(struct { + // Embed all of the fields of Breadcrumb. + *breadcrumb + // Timestamp shadows the original Timestamp field and is meant to + // remain nil, triggering the omitempty behavior. + Timestamp json.RawMessage `json:"timestamp,omitempty"` + }{breadcrumb: (*breadcrumb)(b)}) + } + return json.Marshal((*breadcrumb)(b)) +} + +// User describes the user associated with an Event. If this is used, at least +// an ID or an IP address should be provided. +type User struct { + ID string `json:"id,omitempty"` + Email string `json:"email,omitempty"` + IPAddress string `json:"ip_address,omitempty"` + Username string `json:"username,omitempty"` + Name string `json:"name,omitempty"` + Segment string `json:"segment,omitempty"` + Data map[string]string `json:"data,omitempty"` +} + +func (u User) IsEmpty() bool { + if len(u.ID) > 0 { + return false + } + + if len(u.Email) > 0 { + return false + } + + if len(u.IPAddress) > 0 { + return false + } + + if len(u.Username) > 0 { + return false + } + + if len(u.Name) > 0 { + return false + } + + if len(u.Segment) > 0 { + return false + } + + if len(u.Data) > 0 { + return false + } + + return true +} + +// Request contains information on a HTTP request related to the event. +type Request struct { + URL string `json:"url,omitempty"` + Method string `json:"method,omitempty"` + Data string `json:"data,omitempty"` + QueryString string `json:"query_string,omitempty"` + Cookies string `json:"cookies,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +// NewRequest returns a new Sentry Request from the given http.Request. +// +// NewRequest avoids operations that depend on network access. In particular, it +// does not read r.Body. +func NewRequest(r *http.Request) *Request { + protocol := schemeHTTP + if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { + protocol = schemeHTTPS + } + url := fmt.Sprintf("%s://%s%s", protocol, r.Host, r.URL.Path) + + var cookies string + var env map[string]string + headers := map[string]string{} + + if client := CurrentHub().Client(); client != nil { + if client.Options().SendDefaultPII { + // We read only the first Cookie header because of the specification: + // https://tools.ietf.org/html/rfc6265#section-5.4 + // When the user agent generates an HTTP request, the user agent MUST NOT + // attach more than one Cookie header field. + cookies = r.Header.Get("Cookie") + + for k, v := range r.Header { + headers[k] = strings.Join(v, ",") + } + + if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil { + env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} + } + } + } else { + sensitiveHeaders := getSensitiveHeaders() + for k, v := range r.Header { + if _, ok := sensitiveHeaders[k]; !ok { + headers[k] = strings.Join(v, ",") + } + } + } + + headers["Host"] = r.Host + + return &Request{ + URL: url, + Method: r.Method, + QueryString: r.URL.RawQuery, + Cookies: cookies, + Headers: headers, + Env: env, + } +} + +// Exception specifies an error that occurred. +type Exception struct { + Type string `json:"type,omitempty"` // used as the main issue title + Value string `json:"value,omitempty"` // used as the main issue subtitle + Module string `json:"module,omitempty"` + ThreadID string `json:"thread_id,omitempty"` + Stacktrace *Stacktrace `json:"stacktrace,omitempty"` +} + +// SDKMetaData is a struct to stash data which is needed at some point in the SDK's event processing pipeline +// but which shouldn't get send to Sentry. +type SDKMetaData struct { + dsc DynamicSamplingContext +} + +// Contains information about how the name of the transaction was determined. +type TransactionInfo struct { + Source TransactionSource `json:"source,omitempty"` +} + +// EventID is a hexadecimal string representing a unique uuid4 for an Event. +// An EventID must be 32 characters long, lowercase and not have any dashes. +type EventID string + +type Context = map[string]interface{} + +// Event is the fundamental data structure that is sent to Sentry. +type Event struct { + Breadcrumbs []*Breadcrumb `json:"breadcrumbs,omitempty"` + Contexts map[string]Context `json:"contexts,omitempty"` + Dist string `json:"dist,omitempty"` + Environment string `json:"environment,omitempty"` + EventID EventID `json:"event_id,omitempty"` + Extra map[string]interface{} `json:"extra,omitempty"` + Fingerprint []string `json:"fingerprint,omitempty"` + Level Level `json:"level,omitempty"` + Message string `json:"message,omitempty"` + Platform string `json:"platform,omitempty"` + Release string `json:"release,omitempty"` + Sdk SdkInfo `json:"sdk,omitempty"` + ServerName string `json:"server_name,omitempty"` + Threads []Thread `json:"threads,omitempty"` + Tags map[string]string `json:"tags,omitempty"` + Timestamp time.Time `json:"timestamp"` + Transaction string `json:"transaction,omitempty"` + User User `json:"user,omitempty"` + Logger string `json:"logger,omitempty"` + Modules map[string]string `json:"modules,omitempty"` + Request *Request `json:"request,omitempty"` + Exception []Exception `json:"exception,omitempty"` + + // The fields below are only relevant for transactions. + + Type string `json:"type,omitempty"` + StartTime time.Time `json:"start_timestamp"` + Spans []*Span `json:"spans,omitempty"` + TransactionInfo *TransactionInfo `json:"transaction_info,omitempty"` + + // The fields below are not part of the final JSON payload. + + sdkMetaData SDKMetaData +} + +// TODO: Event.Contexts map[string]interface{} => map[string]EventContext, +// to prevent accidentally storing T when we mean *T. +// For example, the TraceContext must be stored as *TraceContext to pick up the +// MarshalJSON method (and avoid copying). +// type EventContext interface{ EventContext() } + +// MarshalJSON converts the Event struct to JSON. +func (e *Event) MarshalJSON() ([]byte, error) { + // We want to omit time.Time zero values, otherwise the server will try to + // interpret dates too far in the past. However, encoding/json doesn't + // support the "omitempty" option for struct types. See + // https://golang.org/issues/11939. + // + // We overcome the limitation and achieve what we want by shadowing fields + // and a few type tricks. + if e.Type == transactionType { + return e.transactionMarshalJSON() + } + return e.defaultMarshalJSON() +} + +func (e *Event) defaultMarshalJSON() ([]byte, error) { + // event aliases Event to allow calling json.Marshal without an infinite + // loop. It preserves all fields while none of the attached methods. + type event Event + + // errorEvent is like Event with shadowed fields for customizing JSON + // marshaling. + type errorEvent struct { + *event + + // Timestamp shadows the original Timestamp field. It allows us to + // include the timestamp when non-zero and omit it otherwise. + Timestamp json.RawMessage `json:"timestamp,omitempty"` + + // The fields below are not part of error events and only make sense to + // be sent for transactions. They shadow the respective fields in Event + // and are meant to remain nil, triggering the omitempty behavior. + + Type json.RawMessage `json:"type,omitempty"` + StartTime json.RawMessage `json:"start_timestamp,omitempty"` + Spans json.RawMessage `json:"spans,omitempty"` + TransactionInfo json.RawMessage `json:"transaction_info,omitempty"` + } + + x := errorEvent{event: (*event)(e)} + if !e.Timestamp.IsZero() { + b, err := e.Timestamp.MarshalJSON() + if err != nil { + return nil, err + } + x.Timestamp = b + } + return json.Marshal(x) +} + +func (e *Event) transactionMarshalJSON() ([]byte, error) { + // event aliases Event to allow calling json.Marshal without an infinite + // loop. It preserves all fields while none of the attached methods. + type event Event + + // transactionEvent is like Event with shadowed fields for customizing JSON + // marshaling. + type transactionEvent struct { + *event + + // The fields below shadow the respective fields in Event. They allow us + // to include timestamps when non-zero and omit them otherwise. + + StartTime json.RawMessage `json:"start_timestamp,omitempty"` + Timestamp json.RawMessage `json:"timestamp,omitempty"` + } + + x := transactionEvent{event: (*event)(e)} + if !e.Timestamp.IsZero() { + b, err := e.Timestamp.MarshalJSON() + if err != nil { + return nil, err + } + x.Timestamp = b + } + if !e.StartTime.IsZero() { + b, err := e.StartTime.MarshalJSON() + if err != nil { + return nil, err + } + x.StartTime = b + } + return json.Marshal(x) +} + +// NewEvent creates a new Event. +func NewEvent() *Event { + event := Event{ + Contexts: make(map[string]Context), + Extra: make(map[string]interface{}), + Tags: make(map[string]string), + Modules: make(map[string]string), + } + return &event +} + +// Thread specifies threads that were running at the time of an event. +type Thread struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Stacktrace *Stacktrace `json:"stacktrace,omitempty"` + Crashed bool `json:"crashed,omitempty"` + Current bool `json:"current,omitempty"` +} + +// EventHint contains information that can be associated with an Event. +type EventHint struct { + Data interface{} + EventID string + OriginalException error + RecoveredException interface{} + Context context.Context + Request *http.Request + Response *http.Response +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/debug/transport.go b/vendor/github.com/getsentry/sentry-go/internal/debug/transport.go new file mode 100644 index 00000000..199c3a30 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/debug/transport.go @@ -0,0 +1,79 @@ +package debug + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptrace" + "net/http/httputil" +) + +// Transport implements http.RoundTripper and can be used to wrap other HTTP +// transports for debugging, normally http.DefaultTransport. +type Transport struct { + http.RoundTripper + Output io.Writer + // Dump controls whether to dump HTTP request and responses. + Dump bool + // Trace enables usage of net/http/httptrace. + Trace bool +} + +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + var buf bytes.Buffer + if t.Dump { + b, err := httputil.DumpRequestOut(req, true) + if err != nil { + panic(err) + } + _, err = buf.Write(ensureTrailingNewline(b)) + if err != nil { + panic(err) + } + } + if t.Trace { + trace := &httptrace.ClientTrace{ + DNSDone: func(di httptrace.DNSDoneInfo) { + fmt.Fprintf(&buf, "* DNS %v → %v\n", req.Host, di.Addrs) + }, + GotConn: func(ci httptrace.GotConnInfo) { + fmt.Fprintf(&buf, "* Connection local=%v remote=%v", ci.Conn.LocalAddr(), ci.Conn.RemoteAddr()) + if ci.Reused { + fmt.Fprint(&buf, " (reused)") + } + if ci.WasIdle { + fmt.Fprintf(&buf, " (idle %v)", ci.IdleTime) + } + fmt.Fprintln(&buf) + }, + } + req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) + } + resp, err := t.RoundTripper.RoundTrip(req) + if err != nil { + return nil, err + } + if t.Dump { + b, err := httputil.DumpResponse(resp, true) + if err != nil { + panic(err) + } + _, err = buf.Write(ensureTrailingNewline(b)) + if err != nil { + panic(err) + } + } + _, err = io.Copy(t.Output, &buf) + if err != nil { + panic(err) + } + return resp, nil +} + +func ensureTrailingNewline(b []byte) []byte { + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + return b +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/baggage.go b/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/baggage.go new file mode 100644 index 00000000..16e8ed0c --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/baggage.go @@ -0,0 +1,573 @@ +// This file was vendored in unmodified from +// https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/baggage/baggage.go +// +// # Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package baggage + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strings" + + "github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage" +) + +const ( + maxMembers = 180 + maxBytesPerMembers = 4096 + maxBytesPerBaggageString = 8192 + + listDelimiter = "," + keyValueDelimiter = "=" + propertyDelimiter = ";" + + keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)` + valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)` + keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*` +) + +var ( + keyRe = regexp.MustCompile(`^` + keyDef + `$`) + valueRe = regexp.MustCompile(`^` + valueDef + `$`) + propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`) +) + +var ( + errInvalidKey = errors.New("invalid key") + errInvalidValue = errors.New("invalid value") + errInvalidProperty = errors.New("invalid baggage list-member property") + errInvalidMember = errors.New("invalid baggage list-member") + errMemberNumber = errors.New("too many list-members in baggage-string") + errMemberBytes = errors.New("list-member too large") + errBaggageBytes = errors.New("baggage-string too large") +) + +// Property is an additional metadata entry for a baggage list-member. +type Property struct { + key, value string + + // hasValue indicates if a zero-value value means the property does not + // have a value or if it was the zero-value. + hasValue bool + + // hasData indicates whether the created property contains data or not. + // Properties that do not contain data are invalid with no other check + // required. + hasData bool +} + +// NewKeyProperty returns a new Property for key. +// +// If key is invalid, an error will be returned. +func NewKeyProperty(key string) (Property, error) { + if !keyRe.MatchString(key) { + return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) + } + + p := Property{key: key, hasData: true} + return p, nil +} + +// NewKeyValueProperty returns a new Property for key with value. +// +// If key or value are invalid, an error will be returned. +func NewKeyValueProperty(key, value string) (Property, error) { + if !keyRe.MatchString(key) { + return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key) + } + if !valueRe.MatchString(value) { + return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value) + } + + p := Property{ + key: key, + value: value, + hasValue: true, + hasData: true, + } + return p, nil +} + +func newInvalidProperty() Property { + return Property{} +} + +// parseProperty attempts to decode a Property from the passed string. It +// returns an error if the input is invalid according to the W3C Baggage +// specification. +func parseProperty(property string) (Property, error) { + if property == "" { + return newInvalidProperty(), nil + } + + match := propertyRe.FindStringSubmatch(property) + if len(match) != 4 { + return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property) + } + + p := Property{hasData: true} + if match[1] != "" { + p.key = match[1] + } else { + p.key = match[2] + p.value = match[3] + p.hasValue = true + } + + return p, nil +} + +// validate ensures p conforms to the W3C Baggage specification, returning an +// error otherwise. +func (p Property) validate() error { + errFunc := func(err error) error { + return fmt.Errorf("invalid property: %w", err) + } + + if !p.hasData { + return errFunc(fmt.Errorf("%w: %q", errInvalidProperty, p)) + } + + if !keyRe.MatchString(p.key) { + return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key)) + } + if p.hasValue && !valueRe.MatchString(p.value) { + return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value)) + } + if !p.hasValue && p.value != "" { + return errFunc(errors.New("inconsistent value")) + } + return nil +} + +// Key returns the Property key. +func (p Property) Key() string { + return p.key +} + +// Value returns the Property value. Additionally, a boolean value is returned +// indicating if the returned value is the empty if the Property has a value +// that is empty or if the value is not set. +func (p Property) Value() (string, bool) { + return p.value, p.hasValue +} + +// String encodes Property into a string compliant with the W3C Baggage +// specification. +func (p Property) String() string { + if p.hasValue { + return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value) + } + return p.key +} + +type properties []Property + +func fromInternalProperties(iProps []baggage.Property) properties { + if len(iProps) == 0 { + return nil + } + + props := make(properties, len(iProps)) + for i, p := range iProps { + props[i] = Property{ + key: p.Key, + value: p.Value, + hasValue: p.HasValue, + } + } + return props +} + +func (p properties) asInternal() []baggage.Property { + if len(p) == 0 { + return nil + } + + iProps := make([]baggage.Property, len(p)) + for i, prop := range p { + iProps[i] = baggage.Property{ + Key: prop.key, + Value: prop.value, + HasValue: prop.hasValue, + } + } + return iProps +} + +func (p properties) Copy() properties { + if len(p) == 0 { + return nil + } + + props := make(properties, len(p)) + copy(props, p) + return props +} + +// validate ensures each Property in p conforms to the W3C Baggage +// specification, returning an error otherwise. +func (p properties) validate() error { + for _, prop := range p { + if err := prop.validate(); err != nil { + return err + } + } + return nil +} + +// String encodes properties into a string compliant with the W3C Baggage +// specification. +func (p properties) String() string { + props := make([]string, len(p)) + for i, prop := range p { + props[i] = prop.String() + } + return strings.Join(props, propertyDelimiter) +} + +// Member is a list-member of a baggage-string as defined by the W3C Baggage +// specification. +type Member struct { + key, value string + properties properties + + // hasData indicates whether the created property contains data or not. + // Properties that do not contain data are invalid with no other check + // required. + hasData bool +} + +// NewMember returns a new Member from the passed arguments. The key will be +// used directly while the value will be url decoded after validation. An error +// is returned if the created Member would be invalid according to the W3C +// Baggage specification. +func NewMember(key, value string, props ...Property) (Member, error) { + m := Member{ + key: key, + value: value, + properties: properties(props).Copy(), + hasData: true, + } + if err := m.validate(); err != nil { + return newInvalidMember(), err + } + decodedValue, err := url.QueryUnescape(value) + if err != nil { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) + } + m.value = decodedValue + return m, nil +} + +func newInvalidMember() Member { + return Member{} +} + +// parseMember attempts to decode a Member from the passed string. It returns +// an error if the input is invalid according to the W3C Baggage +// specification. +func parseMember(member string) (Member, error) { + if n := len(member); n > maxBytesPerMembers { + return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n) + } + + var ( + key, value string + props properties + ) + + parts := strings.SplitN(member, propertyDelimiter, 2) + switch len(parts) { + case 2: + // Parse the member properties. + for _, pStr := range strings.Split(parts[1], propertyDelimiter) { + p, err := parseProperty(pStr) + if err != nil { + return newInvalidMember(), err + } + props = append(props, p) + } + fallthrough + case 1: + // Parse the member key/value pair. + + // Take into account a value can contain equal signs (=). + kv := strings.SplitN(parts[0], keyValueDelimiter, 2) + if len(kv) != 2 { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member) + } + // "Leading and trailing whitespaces are allowed but MUST be trimmed + // when converting the header into a data structure." + key = strings.TrimSpace(kv[0]) + var err error + value, err = url.QueryUnescape(strings.TrimSpace(kv[1])) + if err != nil { + return newInvalidMember(), fmt.Errorf("%w: %q", err, value) + } + if !keyRe.MatchString(key) { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key) + } + if !valueRe.MatchString(value) { + return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value) + } + default: + // This should never happen unless a developer has changed the string + // splitting somehow. Panic instead of failing silently and allowing + // the bug to slip past the CI checks. + panic("failed to parse baggage member") + } + + return Member{key: key, value: value, properties: props, hasData: true}, nil +} + +// validate ensures m conforms to the W3C Baggage specification. +// A key is just an ASCII string, but a value must be URL encoded UTF-8, +// returning an error otherwise. +func (m Member) validate() error { + if !m.hasData { + return fmt.Errorf("%w: %q", errInvalidMember, m) + } + + if !keyRe.MatchString(m.key) { + return fmt.Errorf("%w: %q", errInvalidKey, m.key) + } + if !valueRe.MatchString(m.value) { + return fmt.Errorf("%w: %q", errInvalidValue, m.value) + } + return m.properties.validate() +} + +// Key returns the Member key. +func (m Member) Key() string { return m.key } + +// Value returns the Member value. +func (m Member) Value() string { return m.value } + +// Properties returns a copy of the Member properties. +func (m Member) Properties() []Property { return m.properties.Copy() } + +// String encodes Member into a string compliant with the W3C Baggage +// specification. +func (m Member) String() string { + // A key is just an ASCII string, but a value is URL encoded UTF-8. + s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value)) + if len(m.properties) > 0 { + s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String()) + } + return s +} + +// Baggage is a list of baggage members representing the baggage-string as +// defined by the W3C Baggage specification. +type Baggage struct { //nolint:golint + list baggage.List +} + +// New returns a new valid Baggage. It returns an error if it results in a +// Baggage exceeding limits set in that specification. +// +// It expects all the provided members to have already been validated. +func New(members ...Member) (Baggage, error) { + if len(members) == 0 { + return Baggage{}, nil + } + + b := make(baggage.List) + for _, m := range members { + if !m.hasData { + return Baggage{}, errInvalidMember + } + + // OpenTelemetry resolves duplicates by last-one-wins. + b[m.key] = baggage.Item{ + Value: m.value, + Properties: m.properties.asInternal(), + } + } + + // Check member numbers after deduplication. + if len(b) > maxMembers { + return Baggage{}, errMemberNumber + } + + bag := Baggage{b} + if n := len(bag.String()); n > maxBytesPerBaggageString { + return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n) + } + + return bag, nil +} + +// Parse attempts to decode a baggage-string from the passed string. It +// returns an error if the input is invalid according to the W3C Baggage +// specification. +// +// If there are duplicate list-members contained in baggage, the last one +// defined (reading left-to-right) will be the only one kept. This diverges +// from the W3C Baggage specification which allows duplicate list-members, but +// conforms to the OpenTelemetry Baggage specification. +func Parse(bStr string) (Baggage, error) { + if bStr == "" { + return Baggage{}, nil + } + + if n := len(bStr); n > maxBytesPerBaggageString { + return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n) + } + + b := make(baggage.List) + for _, memberStr := range strings.Split(bStr, listDelimiter) { + m, err := parseMember(memberStr) + if err != nil { + return Baggage{}, err + } + // OpenTelemetry resolves duplicates by last-one-wins. + b[m.key] = baggage.Item{ + Value: m.value, + Properties: m.properties.asInternal(), + } + } + + // OpenTelemetry does not allow for duplicate list-members, but the W3C + // specification does. Now that we have deduplicated, ensure the baggage + // does not exceed list-member limits. + if len(b) > maxMembers { + return Baggage{}, errMemberNumber + } + + return Baggage{b}, nil +} + +// Member returns the baggage list-member identified by key. +// +// If there is no list-member matching the passed key the returned Member will +// be a zero-value Member. +// The returned member is not validated, as we assume the validation happened +// when it was added to the Baggage. +func (b Baggage) Member(key string) Member { + v, ok := b.list[key] + if !ok { + // We do not need to worry about distinguishing between the situation + // where a zero-valued Member is included in the Baggage because a + // zero-valued Member is invalid according to the W3C Baggage + // specification (it has an empty key). + return newInvalidMember() + } + + return Member{ + key: key, + value: v.Value, + properties: fromInternalProperties(v.Properties), + hasData: true, + } +} + +// Members returns all the baggage list-members. +// The order of the returned list-members does not have significance. +// +// The returned members are not validated, as we assume the validation happened +// when they were added to the Baggage. +func (b Baggage) Members() []Member { + if len(b.list) == 0 { + return nil + } + + members := make([]Member, 0, len(b.list)) + for k, v := range b.list { + members = append(members, Member{ + key: k, + value: v.Value, + properties: fromInternalProperties(v.Properties), + hasData: true, + }) + } + return members +} + +// SetMember returns a copy the Baggage with the member included. If the +// baggage contains a Member with the same key the existing Member is +// replaced. +// +// If member is invalid according to the W3C Baggage specification, an error +// is returned with the original Baggage. +func (b Baggage) SetMember(member Member) (Baggage, error) { + if !member.hasData { + return b, errInvalidMember + } + + n := len(b.list) + if _, ok := b.list[member.key]; !ok { + n++ + } + list := make(baggage.List, n) + + for k, v := range b.list { + // Do not copy if we are just going to overwrite. + if k == member.key { + continue + } + list[k] = v + } + + list[member.key] = baggage.Item{ + Value: member.value, + Properties: member.properties.asInternal(), + } + + return Baggage{list: list}, nil +} + +// DeleteMember returns a copy of the Baggage with the list-member identified +// by key removed. +func (b Baggage) DeleteMember(key string) Baggage { + n := len(b.list) + if _, ok := b.list[key]; ok { + n-- + } + list := make(baggage.List, n) + + for k, v := range b.list { + if k == key { + continue + } + list[k] = v + } + + return Baggage{list: list} +} + +// Len returns the number of list-members in the Baggage. +func (b Baggage) Len() int { + return len(b.list) +} + +// String encodes Baggage into a string compliant with the W3C Baggage +// specification. The returned string will be invalid if the Baggage contains +// any invalid list-members. +func (b Baggage) String() string { + members := make([]string, 0, len(b.list)) + for k, v := range b.list { + members = append(members, Member{ + key: k, + value: v.Value, + properties: fromInternalProperties(v.Properties), + }.String()) + } + return strings.Join(members, listDelimiter) +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage/baggage.go b/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage/baggage.go new file mode 100644 index 00000000..04e41402 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage/baggage.go @@ -0,0 +1,46 @@ +// This file was vendored in unmodified from +// https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/internal/baggage/baggage.go +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package baggage provides base types and functionality to store and retrieve +baggage in Go context. This package exists because the OpenTracing bridge to +OpenTelemetry needs to synchronize state whenever baggage for a context is +modified and that context contains an OpenTracing span. If it were not for +this need this package would not need to exist and the +`go.opentelemetry.io/otel/baggage` package would be the singular place where +W3C baggage is handled. +*/ +package baggage + +// List is the collection of baggage members. The W3C allows for duplicates, +// but OpenTelemetry does not, therefore, this is represented as a map. +type List map[string]Item + +// Item is the value and metadata properties part of a list-member. +type Item struct { + Value string + Properties []Property +} + +// Property is a metadata entry for a list-member. +type Property struct { + Key, Value string + + // HasValue indicates if a zero-value value means the property does not + // have a value or if it was the zero-value. + HasValue bool +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go new file mode 100644 index 00000000..cf5dff50 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go @@ -0,0 +1,46 @@ +package ratelimit + +import ( + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// Reference: +// https://github.com/getsentry/relay/blob/0424a2e017d193a93918053c90cdae9472d164bf/relay-common/src/constants.rs#L116-L127 + +// Category classifies supported payload types that can be ingested by Sentry +// and, therefore, rate limited. +type Category string + +// Known rate limit categories. As a special case, the CategoryAll applies to +// all known payload types. +const ( + CategoryAll Category = "" + CategoryError Category = "error" + CategoryTransaction Category = "transaction" +) + +// knownCategories is the set of currently known categories. Other categories +// are ignored for the purpose of rate-limiting. +var knownCategories = map[Category]struct{}{ + CategoryAll: {}, + CategoryError: {}, + CategoryTransaction: {}, +} + +// String returns the category formatted for debugging. +func (c Category) String() string { + switch c { + case "": + return "CategoryAll" + default: + caser := cases.Title(language.English) + rv := "Category" + for _, w := range strings.Fields(string(c)) { + rv += caser.String(w) + } + return rv + } +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/deadline.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/deadline.go new file mode 100644 index 00000000..c0025833 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/deadline.go @@ -0,0 +1,22 @@ +package ratelimit + +import "time" + +// A Deadline is a time instant when a rate limit expires. +type Deadline time.Time + +// After reports whether the deadline d is after other. +func (d Deadline) After(other Deadline) bool { + return time.Time(d).After(time.Time(other)) +} + +// Equal reports whether d and e represent the same deadline. +func (d Deadline) Equal(e Deadline) bool { + return time.Time(d).Equal(time.Time(e)) +} + +// String returns the deadline formatted for debugging. +func (d Deadline) String() string { + // Like time.Time.String, but without the monotonic clock reading. + return time.Time(d).Round(0).String() +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/doc.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/doc.go new file mode 100644 index 00000000..80b9fdda --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/doc.go @@ -0,0 +1,3 @@ +// Package ratelimit provides tools to work with rate limits imposed by Sentry's +// data ingestion pipeline. +package ratelimit diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/map.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/map.go new file mode 100644 index 00000000..e590430e --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/map.go @@ -0,0 +1,64 @@ +package ratelimit + +import ( + "net/http" + "time" +) + +// Map maps categories to rate limit deadlines. +// +// A rate limit is in effect for a given category if either the category's +// deadline or the deadline for the special CategoryAll has not yet expired. +// +// Use IsRateLimited to check whether a category is rate-limited. +type Map map[Category]Deadline + +// IsRateLimited returns true if the category is currently rate limited. +func (m Map) IsRateLimited(c Category) bool { + return m.isRateLimited(c, time.Now()) +} + +func (m Map) isRateLimited(c Category, now time.Time) bool { + return m.Deadline(c).After(Deadline(now)) +} + +// Deadline returns the deadline when the rate limit for the given category or +// the special CategoryAll expire, whichever is furthest into the future. +func (m Map) Deadline(c Category) Deadline { + categoryDeadline := m[c] + allDeadline := m[CategoryAll] + if categoryDeadline.After(allDeadline) { + return categoryDeadline + } + return allDeadline +} + +// Merge merges the other map into m. +// +// If a category appears in both maps, the deadline that is furthest into the +// future is preserved. +func (m Map) Merge(other Map) { + for c, d := range other { + if d.After(m[c]) { + m[c] = d + } + } +} + +// FromResponse returns a rate limit map from an HTTP response. +func FromResponse(r *http.Response) Map { + return fromResponse(r, time.Now()) +} + +func fromResponse(r *http.Response, now time.Time) Map { + s := r.Header.Get("X-Sentry-Rate-Limits") + if s != "" { + return parseXSentryRateLimits(s, now) + } + if r.StatusCode == http.StatusTooManyRequests { + s := r.Header.Get("Retry-After") + deadline, _ := parseRetryAfter(s, now) + return Map{CategoryAll: deadline} + } + return Map{} +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/rate_limits.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/rate_limits.go new file mode 100644 index 00000000..2dcda27c --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/rate_limits.go @@ -0,0 +1,76 @@ +package ratelimit + +import ( + "errors" + "math" + "strconv" + "strings" + "time" +) + +var errInvalidXSRLRetryAfter = errors.New("invalid retry-after value") + +// parseXSentryRateLimits returns a RateLimits map by parsing an input string in +// the format of the X-Sentry-Rate-Limits header. +// +// Example +// +// X-Sentry-Rate-Limits: 60:transaction, 2700:default;error;security +// +// This will rate limit transactions for the next 60 seconds and errors for the +// next 2700 seconds. +// +// Limits for unknown categories are ignored. +func parseXSentryRateLimits(s string, now time.Time) Map { + // https://github.com/getsentry/relay/blob/0424a2e017d193a93918053c90cdae9472d164bf/relay-server/src/utils/rate_limits.rs#L44-L82 + m := make(Map, len(knownCategories)) + for _, limit := range strings.Split(s, ",") { + limit = strings.TrimSpace(limit) + if limit == "" { + continue + } + components := strings.Split(limit, ":") + if len(components) == 0 { + continue + } + retryAfter, err := parseXSRLRetryAfter(strings.TrimSpace(components[0]), now) + if err != nil { + continue + } + categories := "" + if len(components) > 1 { + categories = components[1] + } + for _, category := range strings.Split(categories, ";") { + c := Category(strings.ToLower(strings.TrimSpace(category))) + if _, ok := knownCategories[c]; !ok { + // skip unknown categories, keep m small + continue + } + // always keep the deadline furthest into the future + if retryAfter.After(m[c]) { + m[c] = retryAfter + } + } + } + return m +} + +// parseXSRLRetryAfter parses a string into a retry-after rate limit deadline. +// +// Valid input is a number, possibly signed and possibly floating-point, +// indicating the number of seconds to wait before sending another request. +// Negative values are treated as zero. Fractional values are rounded to the +// next integer. +func parseXSRLRetryAfter(s string, now time.Time) (Deadline, error) { + // https://github.com/getsentry/relay/blob/0424a2e017d193a93918053c90cdae9472d164bf/relay-quotas/src/rate_limit.rs#L88-L96 + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return Deadline{}, errInvalidXSRLRetryAfter + } + d := time.Duration(math.Ceil(math.Max(f, 0.0))) * time.Second + if d < 0 { + d = 0 + } + return Deadline(now.Add(d)), nil +} diff --git a/vendor/github.com/getsentry/sentry-go/internal/ratelimit/retry_after.go b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/retry_after.go new file mode 100644 index 00000000..576e29dc --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/internal/ratelimit/retry_after.go @@ -0,0 +1,40 @@ +package ratelimit + +import ( + "errors" + "strconv" + "time" +) + +const defaultRetryAfter = 1 * time.Minute + +var errInvalidRetryAfter = errors.New("invalid input") + +// parseRetryAfter parses a string s as in the standard Retry-After HTTP header +// and returns a deadline until when requests are rate limited and therefore new +// requests should not be sent. The input may be either a date or a non-negative +// integer number of seconds. +// +// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After +// +// parseRetryAfter always returns a usable deadline, even in case of an error. +// +// This is the original rate limiting mechanism used by Sentry, superseeded by +// the X-Sentry-Rate-Limits response header. +func parseRetryAfter(s string, now time.Time) (Deadline, error) { + if s == "" { + goto invalid + } + if n, err := strconv.Atoi(s); err == nil { + if n < 0 { + goto invalid + } + d := time.Duration(n) * time.Second + return Deadline(now.Add(d)), nil + } + if date, err := time.Parse(time.RFC1123, s); err == nil { + return Deadline(date), nil + } +invalid: + return Deadline(now.Add(defaultRetryAfter)), errInvalidRetryAfter +} diff --git a/vendor/github.com/getsentry/sentry-go/scope.go b/vendor/github.com/getsentry/sentry-go/scope.go new file mode 100644 index 00000000..063047b9 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/scope.go @@ -0,0 +1,428 @@ +package sentry + +import ( + "bytes" + "io" + "net/http" + "sync" + "time" +) + +// Scope holds contextual data for the current scope. +// +// The scope is an object that can cloned efficiently and stores data that is +// locally relevant to an event. For instance the scope will hold recorded +// breadcrumbs and similar information. +// +// The scope can be interacted with in two ways. First, the scope is routinely +// updated with information by functions such as AddBreadcrumb which will modify +// the current scope. Second, the current scope can be configured through the +// ConfigureScope function or Hub method of the same name. +// +// The scope is meant to be modified but not inspected directly. When preparing +// an event for reporting, the current client adds information from the current +// scope into the event. +type Scope struct { + mu sync.RWMutex + breadcrumbs []*Breadcrumb + user User + tags map[string]string + contexts map[string]Context + extra map[string]interface{} + fingerprint []string + level Level + transaction string + request *http.Request + // requestBody holds a reference to the original request.Body. + requestBody interface { + // Bytes returns bytes from the original body, lazily buffered as the + // original body is read. + Bytes() []byte + // Overflow returns true if the body is larger than the maximum buffer + // size. + Overflow() bool + } + eventProcessors []EventProcessor +} + +// NewScope creates a new Scope. +func NewScope() *Scope { + scope := Scope{ + breadcrumbs: make([]*Breadcrumb, 0), + tags: make(map[string]string), + contexts: make(map[string]Context), + extra: make(map[string]interface{}), + fingerprint: make([]string, 0), + } + + return &scope +} + +// AddBreadcrumb adds new breadcrumb to the current scope +// and optionally throws the old one if limit is reached. +func (scope *Scope) AddBreadcrumb(breadcrumb *Breadcrumb, limit int) { + if breadcrumb.Timestamp.IsZero() { + breadcrumb.Timestamp = time.Now() + } + + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.breadcrumbs = append(scope.breadcrumbs, breadcrumb) + if len(scope.breadcrumbs) > limit { + scope.breadcrumbs = scope.breadcrumbs[1 : limit+1] + } +} + +// ClearBreadcrumbs clears all breadcrumbs from the current scope. +func (scope *Scope) ClearBreadcrumbs() { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.breadcrumbs = []*Breadcrumb{} +} + +// SetUser sets the user for the current scope. +func (scope *Scope) SetUser(user User) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.user = user +} + +// SetRequest sets the request for the current scope. +func (scope *Scope) SetRequest(r *http.Request) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.request = r + + if r == nil { + return + } + + // Don't buffer request body if we know it is oversized. + if r.ContentLength > maxRequestBodyBytes { + return + } + // Don't buffer if there is no body. + if r.Body == nil || r.Body == http.NoBody { + return + } + buf := &limitedBuffer{Capacity: maxRequestBodyBytes} + r.Body = readCloser{ + Reader: io.TeeReader(r.Body, buf), + Closer: r.Body, + } + scope.requestBody = buf +} + +// SetRequestBody sets the request body for the current scope. +// +// This method should only be called when the body bytes are already available +// in memory. Typically, the request body is buffered lazily from the +// Request.Body from SetRequest. +func (scope *Scope) SetRequestBody(b []byte) { + scope.mu.Lock() + defer scope.mu.Unlock() + + capacity := maxRequestBodyBytes + overflow := false + if len(b) > capacity { + overflow = true + b = b[:capacity] + } + scope.requestBody = &limitedBuffer{ + Capacity: capacity, + Buffer: *bytes.NewBuffer(b), + overflow: overflow, + } +} + +// maxRequestBodyBytes is the default maximum request body size to send to +// Sentry. +const maxRequestBodyBytes = 10 * 1024 + +// A limitedBuffer is like a bytes.Buffer, but limited to store at most Capacity +// bytes. Any writes past the capacity are silently discarded, similar to +// io.Discard. +type limitedBuffer struct { + Capacity int + + bytes.Buffer + overflow bool +} + +// Write implements io.Writer. +func (b *limitedBuffer) Write(p []byte) (n int, err error) { + // Silently ignore writes after overflow. + if b.overflow { + return len(p), nil + } + left := b.Capacity - b.Len() + if left < 0 { + left = 0 + } + if len(p) > left { + b.overflow = true + p = p[:left] + } + return b.Buffer.Write(p) +} + +// Overflow returns true if the limitedBuffer discarded bytes written to it. +func (b *limitedBuffer) Overflow() bool { + return b.overflow +} + +// readCloser combines an io.Reader and an io.Closer to implement io.ReadCloser. +type readCloser struct { + io.Reader + io.Closer +} + +// SetTag adds a tag to the current scope. +func (scope *Scope) SetTag(key, value string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.tags[key] = value +} + +// SetTags assigns multiple tags to the current scope. +func (scope *Scope) SetTags(tags map[string]string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + for k, v := range tags { + scope.tags[k] = v + } +} + +// RemoveTag removes a tag from the current scope. +func (scope *Scope) RemoveTag(key string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + delete(scope.tags, key) +} + +// SetContext adds a context to the current scope. +func (scope *Scope) SetContext(key string, value Context) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.contexts[key] = value +} + +// SetContexts assigns multiple contexts to the current scope. +func (scope *Scope) SetContexts(contexts map[string]Context) { + scope.mu.Lock() + defer scope.mu.Unlock() + + for k, v := range contexts { + scope.contexts[k] = v + } +} + +// RemoveContext removes a context from the current scope. +func (scope *Scope) RemoveContext(key string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + delete(scope.contexts, key) +} + +// SetExtra adds an extra to the current scope. +func (scope *Scope) SetExtra(key string, value interface{}) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.extra[key] = value +} + +// SetExtras assigns multiple extras to the current scope. +func (scope *Scope) SetExtras(extra map[string]interface{}) { + scope.mu.Lock() + defer scope.mu.Unlock() + + for k, v := range extra { + scope.extra[k] = v + } +} + +// RemoveExtra removes a extra from the current scope. +func (scope *Scope) RemoveExtra(key string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + delete(scope.extra, key) +} + +// SetFingerprint sets new fingerprint for the current scope. +func (scope *Scope) SetFingerprint(fingerprint []string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.fingerprint = fingerprint +} + +// SetLevel sets new level for the current scope. +func (scope *Scope) SetLevel(level Level) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.level = level +} + +// SetTransaction sets the transaction name for the current transaction. +func (scope *Scope) SetTransaction(name string) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.transaction = name +} + +// Transaction returns the transaction name for the current transaction. +func (scope *Scope) Transaction() (name string) { + scope.mu.RLock() + defer scope.mu.RUnlock() + + return scope.transaction +} + +// Clone returns a copy of the current scope with all data copied over. +func (scope *Scope) Clone() *Scope { + scope.mu.RLock() + defer scope.mu.RUnlock() + + clone := NewScope() + clone.user = scope.user + clone.breadcrumbs = make([]*Breadcrumb, len(scope.breadcrumbs)) + copy(clone.breadcrumbs, scope.breadcrumbs) + for key, value := range scope.tags { + clone.tags[key] = value + } + for key, value := range scope.contexts { + clone.contexts[key] = value + } + for key, value := range scope.extra { + clone.extra[key] = value + } + clone.fingerprint = make([]string, len(scope.fingerprint)) + copy(clone.fingerprint, scope.fingerprint) + clone.level = scope.level + clone.transaction = scope.transaction + clone.request = scope.request + clone.requestBody = scope.requestBody + clone.eventProcessors = scope.eventProcessors + return clone +} + +// Clear removes the data from the current scope. Not safe for concurrent use. +func (scope *Scope) Clear() { + *scope = *NewScope() +} + +// AddEventProcessor adds an event processor to the current scope. +func (scope *Scope) AddEventProcessor(processor EventProcessor) { + scope.mu.Lock() + defer scope.mu.Unlock() + + scope.eventProcessors = append(scope.eventProcessors, processor) +} + +// ApplyToEvent takes the data from the current scope and attaches it to the event. +func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event { + scope.mu.RLock() + defer scope.mu.RUnlock() + + if len(scope.breadcrumbs) > 0 { + event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...) + } + + if len(scope.tags) > 0 { + if event.Tags == nil { + event.Tags = make(map[string]string, len(scope.tags)) + } + + for key, value := range scope.tags { + event.Tags[key] = value + } + } + + if len(scope.contexts) > 0 { + if event.Contexts == nil { + event.Contexts = make(map[string]Context) + } + + for key, value := range scope.contexts { + if key == "trace" && event.Type == transactionType { + // Do not override trace context of + // transactions, otherwise it breaks the + // transaction event representation. + // For error events, the trace context is used + // to link errors and traces/spans in Sentry. + continue + } + + // Ensure we are not overwriting event fields + if _, ok := event.Contexts[key]; !ok { + event.Contexts[key] = value + } + } + } + + if len(scope.extra) > 0 { + if event.Extra == nil { + event.Extra = make(map[string]interface{}, len(scope.extra)) + } + + for key, value := range scope.extra { + event.Extra[key] = value + } + } + + if event.User.IsEmpty() { + event.User = scope.user + } + + if len(event.Fingerprint) == 0 { + event.Fingerprint = append(event.Fingerprint, scope.fingerprint...) + } + + if scope.level != "" { + event.Level = scope.level + } + + if scope.transaction != "" { + event.Transaction = scope.transaction + } + + if event.Request == nil && scope.request != nil { + event.Request = NewRequest(scope.request) + // NOTE: The SDK does not attempt to send partial request body data. + // + // The reason being that Sentry's ingest pipeline and UI are optimized + // to show structured data. Additionally, tooling around PII scrubbing + // relies on structured data; truncated request bodies would create + // invalid payloads that are more prone to leaking PII data. + // + // Users can still send more data along their events if they want to, + // for example using Event.Extra. + if scope.requestBody != nil && !scope.requestBody.Overflow() { + event.Request.Data = string(scope.requestBody.Bytes()) + } + } + + for _, processor := range scope.eventProcessors { + id := event.EventID + event = processor(event, hint) + if event == nil { + Logger.Printf("Event dropped by one of the Scope EventProcessors: %s\n", id) + return nil + } + } + + return event +} diff --git a/vendor/github.com/getsentry/sentry-go/sentry.go b/vendor/github.com/getsentry/sentry-go/sentry.go new file mode 100644 index 00000000..ccceca85 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/sentry.go @@ -0,0 +1,136 @@ +package sentry + +import ( + "context" + "time" +) + +// Deprecated: Use SDKVersion instead. +const Version = SDKVersion + +// Version is the version of the SDK. +const SDKVersion = "0.16.0" + +// The identifier of the SDK. +const SDKIdentifier = "sentry.go" + +// apiVersion is the minimum version of the Sentry API compatible with the +// sentry-go SDK. +const apiVersion = "7" + +// userAgent is the User-Agent of outgoing HTTP requests. +const userAgent = "sentry-go/" + SDKVersion + +// Init initializes the SDK with options. The returned error is non-nil if +// options is invalid, for instance if a malformed DSN is provided. +func Init(options ClientOptions) error { + hub := CurrentHub() + client, err := NewClient(options) + if err != nil { + return err + } + hub.BindClient(client) + return nil +} + +// AddBreadcrumb records a new breadcrumb. +// +// The total number of breadcrumbs that can be recorded are limited by the +// configuration on the client. +func AddBreadcrumb(breadcrumb *Breadcrumb) { + hub := CurrentHub() + hub.AddBreadcrumb(breadcrumb, nil) +} + +// CaptureMessage captures an arbitrary message. +func CaptureMessage(message string) *EventID { + hub := CurrentHub() + return hub.CaptureMessage(message) +} + +// CaptureException captures an error. +func CaptureException(exception error) *EventID { + hub := CurrentHub() + return hub.CaptureException(exception) +} + +// CaptureEvent captures an event on the currently active client if any. +// +// The event must already be assembled. Typically code would instead use +// the utility methods like CaptureException. The return value is the +// event ID. In case Sentry is disabled or event was dropped, the return value will be nil. +func CaptureEvent(event *Event) *EventID { + hub := CurrentHub() + return hub.CaptureEvent(event) +} + +// Recover captures a panic. +func Recover() *EventID { + if err := recover(); err != nil { + hub := CurrentHub() + return hub.Recover(err) + } + return nil +} + +// RecoverWithContext captures a panic and passes relevant context object. +func RecoverWithContext(ctx context.Context) *EventID { + if err := recover(); err != nil { + var hub *Hub + + if HasHubOnContext(ctx) { + hub = GetHubFromContext(ctx) + } else { + hub = CurrentHub() + } + + return hub.RecoverWithContext(ctx, err) + } + return nil +} + +// WithScope is a shorthand for CurrentHub().WithScope. +func WithScope(f func(scope *Scope)) { + hub := CurrentHub() + hub.WithScope(f) +} + +// ConfigureScope is a shorthand for CurrentHub().ConfigureScope. +func ConfigureScope(f func(scope *Scope)) { + hub := CurrentHub() + hub.ConfigureScope(f) +} + +// PushScope is a shorthand for CurrentHub().PushScope. +func PushScope() { + hub := CurrentHub() + hub.PushScope() +} + +// PopScope is a shorthand for CurrentHub().PopScope. +func PopScope() { + hub := CurrentHub() + hub.PopScope() +} + +// Flush waits until the underlying Transport sends any buffered events to the +// Sentry server, blocking for at most the given timeout. It returns false if +// the timeout was reached. In that case, some events may not have been sent. +// +// Flush should be called before terminating the program to avoid +// unintentionally dropping events. +// +// Do not call Flush indiscriminately after every call to CaptureEvent, +// CaptureException or CaptureMessage. Instead, to have the SDK send events over +// the network synchronously, configure it to use the HTTPSyncTransport in the +// call to Init. +func Flush(timeout time.Duration) bool { + hub := CurrentHub() + return hub.Flush(timeout) +} + +// LastEventID returns an ID of last captured event. +func LastEventID() EventID { + hub := CurrentHub() + return hub.LastEventID() +} diff --git a/vendor/github.com/getsentry/sentry-go/sourcereader.go b/vendor/github.com/getsentry/sentry-go/sourcereader.go new file mode 100644 index 00000000..74a08384 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/sourcereader.go @@ -0,0 +1,70 @@ +package sentry + +import ( + "bytes" + "os" + "sync" +) + +type sourceReader struct { + mu sync.Mutex + cache map[string][][]byte +} + +func newSourceReader() sourceReader { + return sourceReader{ + cache: make(map[string][][]byte), + } +} + +func (sr *sourceReader) readContextLines(filename string, line, context int) ([][]byte, int) { + sr.mu.Lock() + defer sr.mu.Unlock() + + lines, ok := sr.cache[filename] + + if !ok { + data, err := os.ReadFile(filename) + if err != nil { + sr.cache[filename] = nil + return nil, 0 + } + lines = bytes.Split(data, []byte{'\n'}) + sr.cache[filename] = lines + } + + return sr.calculateContextLines(lines, line, context) +} + +func (sr *sourceReader) calculateContextLines(lines [][]byte, line, context int) ([][]byte, int) { + // Stacktrace lines are 1-indexed, slices are 0-indexed + line-- + + // contextLine points to a line that caused an issue itself, in relation to + // returned slice. + contextLine := context + + if lines == nil || line >= len(lines) || line < 0 { + return nil, 0 + } + + if context < 0 { + context = 0 + contextLine = 0 + } + + start := line - context + + if start < 0 { + contextLine += start + start = 0 + } + + end := line + context + 1 + + if end > len(lines) { + end = len(lines) + } + + return lines[start:end], contextLine +} diff --git a/vendor/github.com/getsentry/sentry-go/span_recorder.go b/vendor/github.com/getsentry/sentry-go/span_recorder.go new file mode 100644 index 00000000..137aa233 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/span_recorder.go @@ -0,0 +1,56 @@ +package sentry + +import ( + "sync" +) + +// A spanRecorder stores a span tree that makes up a transaction. Safe for +// concurrent use. It is okay to add child spans from multiple goroutines. +type spanRecorder struct { + mu sync.Mutex + spans []*Span + overflowOnce sync.Once +} + +// record stores a span. The first stored span is assumed to be the root of a +// span tree. +func (r *spanRecorder) record(s *Span) { + maxSpans := defaultMaxSpans + if client := CurrentHub().Client(); client != nil { + maxSpans = client.Options().MaxSpans + } + r.mu.Lock() + defer r.mu.Unlock() + if len(r.spans) >= maxSpans { + r.overflowOnce.Do(func() { + root := r.spans[0] + Logger.Printf("Too many spans: dropping spans from transaction with TraceID=%s SpanID=%s limit=%d", + root.TraceID, root.SpanID, maxSpans) + }) + // TODO(tracing): mark the transaction event in some way to + // communicate that spans were dropped. + return + } + r.spans = append(r.spans, s) +} + +// root returns the first recorded span. Returns nil if none have been recorded. +func (r *spanRecorder) root() *Span { + r.mu.Lock() + defer r.mu.Unlock() + if len(r.spans) == 0 { + return nil + } + return r.spans[0] +} + +// children returns a list of all recorded spans, except the root. Returns nil +// if there are no children. +func (r *spanRecorder) children() []*Span { + r.mu.Lock() + defer r.mu.Unlock() + if len(r.spans) < 2 { + return nil + } + return r.spans[1:] +} diff --git a/vendor/github.com/getsentry/sentry-go/stacktrace.go b/vendor/github.com/getsentry/sentry-go/stacktrace.go new file mode 100644 index 00000000..8c256ec9 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/stacktrace.go @@ -0,0 +1,343 @@ +package sentry + +import ( + "go/build" + "path/filepath" + "reflect" + "runtime" + "strings" +) + +const unknown string = "unknown" + +// The module download is split into two parts: downloading the go.mod and downloading the actual code. +// If you have dependencies only needed for tests, then they will show up in your go.mod, +// and go get will download their go.mods, but it will not download their code. +// The test-only dependencies get downloaded only when you need it, such as the first time you run go test. +// +// https://github.com/golang/go/issues/26913#issuecomment-411976222 + +// Stacktrace holds information about the frames of the stack. +type Stacktrace struct { + Frames []Frame `json:"frames,omitempty"` + FramesOmitted []uint `json:"frames_omitted,omitempty"` +} + +// NewStacktrace creates a stacktrace using runtime.Callers. +func NewStacktrace() *Stacktrace { + pcs := make([]uintptr, 100) + n := runtime.Callers(1, pcs) + + if n == 0 { + return nil + } + + frames := extractFrames(pcs[:n]) + frames = filterFrames(frames) + + stacktrace := Stacktrace{ + Frames: frames, + } + + return &stacktrace +} + +// TODO: Make it configurable so that anyone can provide their own implementation? +// Use of reflection allows us to not have a hard dependency on any given +// package, so we don't have to import it. + +// ExtractStacktrace creates a new Stacktrace based on the given error. +func ExtractStacktrace(err error) *Stacktrace { + method := extractReflectedStacktraceMethod(err) + + var pcs []uintptr + + if method.IsValid() { + pcs = extractPcs(method) + } else { + pcs = extractXErrorsPC(err) + } + + if len(pcs) == 0 { + return nil + } + + frames := extractFrames(pcs) + frames = filterFrames(frames) + + stacktrace := Stacktrace{ + Frames: frames, + } + + return &stacktrace +} + +func extractReflectedStacktraceMethod(err error) reflect.Value { + errValue := reflect.ValueOf(err) + + // https://github.com/go-errors/errors + methodStackFrames := errValue.MethodByName("StackFrames") + if methodStackFrames.IsValid() { + return methodStackFrames + } + + // https://github.com/pkg/errors + methodStackTrace := errValue.MethodByName("StackTrace") + if methodStackTrace.IsValid() { + return methodStackTrace + } + + // https://github.com/pingcap/errors + methodGetStackTracer := errValue.MethodByName("GetStackTracer") + if methodGetStackTracer.IsValid() { + stacktracer := methodGetStackTracer.Call(nil)[0] + stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace") + + if stacktracerStackTrace.IsValid() { + return stacktracerStackTrace + } + } + + return reflect.Value{} +} + +func extractPcs(method reflect.Value) []uintptr { + var pcs []uintptr + + stacktrace := method.Call(nil)[0] + + if stacktrace.Kind() != reflect.Slice { + return nil + } + + for i := 0; i < stacktrace.Len(); i++ { + pc := stacktrace.Index(i) + + switch pc.Kind() { + case reflect.Uintptr: + pcs = append(pcs, uintptr(pc.Uint())) + case reflect.Struct: + for _, fieldName := range []string{"ProgramCounter", "PC"} { + field := pc.FieldByName(fieldName) + if !field.IsValid() { + continue + } + if field.Kind() == reflect.Uintptr { + pcs = append(pcs, uintptr(field.Uint())) + break + } + } + } + } + + return pcs +} + +// extractXErrorsPC extracts program counters from error values compatible with +// the error types from golang.org/x/xerrors. +// +// It returns nil if err is not compatible with errors from that package or if +// no program counters are stored in err. +func extractXErrorsPC(err error) []uintptr { + // This implementation uses the reflect package to avoid a hard dependency + // on third-party packages. + + // We don't know if err matches the expected type. For simplicity, instead + // of trying to account for all possible ways things can go wrong, some + // assumptions are made and if they are violated the code will panic. We + // recover from any panic and ignore it, returning nil. + //nolint: errcheck + defer func() { recover() }() + + field := reflect.ValueOf(err).Elem().FieldByName("frame") // type Frame struct{ frames [3]uintptr } + field = field.FieldByName("frames") + field = field.Slice(1, field.Len()) // drop first pc pointing to xerrors.New + pc := make([]uintptr, field.Len()) + for i := 0; i < field.Len(); i++ { + pc[i] = uintptr(field.Index(i).Uint()) + } + return pc +} + +// Frame represents a function call and it's metadata. Frames are associated +// with a Stacktrace. +type Frame struct { + Function string `json:"function,omitempty"` + Symbol string `json:"symbol,omitempty"` + // Module is, despite the name, the Sentry protocol equivalent of a Go + // package's import path. + Module string `json:"module,omitempty"` + // Package is not used for Go stack trace frames. In other platforms it + // refers to a container where the Module can be found. For example, a + // Java JAR, a .NET Assembly, or a native dynamic library. + // It exists for completeness, allowing the construction and reporting + // of custom event payloads. + Package string `json:"package,omitempty"` + Filename string `json:"filename,omitempty"` + AbsPath string `json:"abs_path,omitempty"` + Lineno int `json:"lineno,omitempty"` + Colno int `json:"colno,omitempty"` + PreContext []string `json:"pre_context,omitempty"` + ContextLine string `json:"context_line,omitempty"` + PostContext []string `json:"post_context,omitempty"` + InApp bool `json:"in_app,omitempty"` + Vars map[string]interface{} `json:"vars,omitempty"` +} + +// NewFrame assembles a stacktrace frame out of runtime.Frame. +func NewFrame(f runtime.Frame) Frame { + var abspath, relpath string + // NOTE: f.File paths historically use forward slash as path separator even + // on Windows, though this is not yet documented, see + // https://golang.org/issues/3335. In any case, filepath.IsAbs can work with + // paths with either slash or backslash on Windows. + switch { + case f.File == "": + relpath = unknown + // Leave abspath as the empty string to be omitted when serializing + // event as JSON. + abspath = "" + case filepath.IsAbs(f.File): + abspath = f.File + // TODO: in the general case, it is not trivial to come up with a + // "project relative" path with the data we have in run time. + // We shall not use filepath.Base because it creates ambiguous paths and + // affects the "Suspect Commits" feature. + // For now, leave relpath empty to be omitted when serializing the event + // as JSON. Improve this later. + relpath = "" + default: + // f.File is a relative path. This may happen when the binary is built + // with the -trimpath flag. + relpath = f.File + // Omit abspath when serializing the event as JSON. + abspath = "" + } + + function := f.Function + var pkg string + + if function != "" { + pkg, function = splitQualifiedFunctionName(function) + } + + frame := Frame{ + AbsPath: abspath, + Filename: relpath, + Lineno: f.Line, + Module: pkg, + Function: function, + } + + frame.InApp = isInAppFrame(frame) + + return frame +} + +// splitQualifiedFunctionName splits a package path-qualified function name into +// package name and function name. Such qualified names are found in +// runtime.Frame.Function values. +func splitQualifiedFunctionName(name string) (pkg string, fun string) { + pkg = packageName(name) + fun = strings.TrimPrefix(name, pkg+".") + return +} + +func extractFrames(pcs []uintptr) []Frame { + var frames = make([]Frame, 0, len(pcs)) + callersFrames := runtime.CallersFrames(pcs) + + for { + callerFrame, more := callersFrames.Next() + + frames = append(frames, NewFrame(callerFrame)) + + if !more { + break + } + } + + // reverse + for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 { + frames[i], frames[j] = frames[j], frames[i] + } + + return frames +} + +// filterFrames filters out stack frames that are not meant to be reported to +// Sentry. Those are frames internal to the SDK or Go. +func filterFrames(frames []Frame) []Frame { + if len(frames) == 0 { + return nil + } + + // reuse + filteredFrames := frames[:0] + + for _, frame := range frames { + // Skip Go internal frames. + if frame.Module == "runtime" || frame.Module == "testing" { + continue + } + // Skip Sentry internal frames, except for frames in _test packages (for + // testing). + if strings.HasPrefix(frame.Module, "github.com/getsentry/sentry-go") && + !strings.HasSuffix(frame.Module, "_test") { + continue + } + filteredFrames = append(filteredFrames, frame) + } + + return filteredFrames +} + +func isInAppFrame(frame Frame) bool { + if strings.HasPrefix(frame.AbsPath, build.Default.GOROOT) || + strings.Contains(frame.Module, "vendor") || + strings.Contains(frame.Module, "third_party") { + return false + } + + return true +} + +func callerFunctionName() string { + pcs := make([]uintptr, 1) + runtime.Callers(3, pcs) + callersFrames := runtime.CallersFrames(pcs) + callerFrame, _ := callersFrames.Next() + return baseName(callerFrame.Function) +} + +// packageName returns the package part of the symbol name, or the empty string +// if there is none. +// It replicates https://golang.org/pkg/debug/gosym/#Sym.PackageName, avoiding a +// dependency on debug/gosym. +func packageName(name string) string { + // A prefix of "type." and "go." is a compiler-generated symbol that doesn't belong to any package. + // See variable reservedimports in cmd/compile/internal/gc/subr.go + if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") { + return "" + } + + pathend := strings.LastIndex(name, "/") + if pathend < 0 { + pathend = 0 + } + + if i := strings.Index(name[pathend:], "."); i != -1 { + return name[:pathend+i] + } + return "" +} + +// baseName returns the symbol name without the package or receiver name. +// It replicates https://golang.org/pkg/debug/gosym/#Sym.BaseName, avoiding a +// dependency on debug/gosym. +func baseName(name string) string { + if i := strings.LastIndex(name, "."); i != -1 { + return name[i+1:] + } + return name +} diff --git a/vendor/github.com/getsentry/sentry-go/traces_sampler.go b/vendor/github.com/getsentry/sentry-go/traces_sampler.go new file mode 100644 index 00000000..69e7cb7f --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/traces_sampler.go @@ -0,0 +1,19 @@ +package sentry + +// A SamplingContext is passed to a TracesSampler to determine a sampling +// decision. +// +// TODO(tracing): possibly expand SamplingContext to include custom / +// user-provided data. +type SamplingContext struct { + Span *Span // The current span, always non-nil. + Parent *Span // The parent span, may be nil. +} + +// The TracesSample type is an adapter to allow the use of ordinary +// functions as a TracesSampler. +type TracesSampler func(ctx SamplingContext) float64 + +func (f TracesSampler) Sample(ctx SamplingContext) float64 { + return f(ctx) +} diff --git a/vendor/github.com/getsentry/sentry-go/tracing.go b/vendor/github.com/getsentry/sentry-go/tracing.go new file mode 100644 index 00000000..5a70c07b --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/tracing.go @@ -0,0 +1,776 @@ +package sentry + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "regexp" + "strings" + "time" +) + +// A Span is the building block of a Sentry transaction. Spans build up a tree +// structure of timed operations. The span tree makes up a transaction event +// that is sent to Sentry when the root span is finished. +// +// Spans must be started with either StartSpan or Span.StartChild. +type Span struct { //nolint: maligned // prefer readability over optimal memory layout (see note below *) + TraceID TraceID `json:"trace_id"` + SpanID SpanID `json:"span_id"` + ParentSpanID SpanID `json:"parent_span_id"` + Op string `json:"op,omitempty"` + Description string `json:"description,omitempty"` + Status SpanStatus `json:"status,omitempty"` + Tags map[string]string `json:"tags,omitempty"` + StartTime time.Time `json:"start_timestamp"` + EndTime time.Time `json:"timestamp"` + Data map[string]interface{} `json:"data,omitempty"` + Sampled Sampled `json:"-"` + Source TransactionSource `json:"-"` + + // sample rate the span was sampled with. + sampleRate float64 + // ctx is the context where the span was started. Always non-nil. + ctx context.Context + // Dynamic Sampling context + dynamicSamplingContext DynamicSamplingContext + // parent refers to the immediate local parent span. A remote parent span is + // only referenced by setting ParentSpanID. + parent *Span + // isTransaction is true only for the root span of a local span tree. The + // root span is the first span started in a context. Note that a local root + // span may have a remote parent belonging to the same trace, therefore + // isTransaction depends on ctx and not on parent. + isTransaction bool + // recorder stores all spans in a transaction. Guaranteed to be non-nil. + recorder *spanRecorder +} + +// (*) Note on maligned: +// +// We prefer readability over optimal memory layout. If we ever decide to +// reorder fields, we can use a tool: +// +// go run honnef.co/go/tools/cmd/structlayout -json . Span | go run honnef.co/go/tools/cmd/structlayout-optimize +// +// Other structs would deserve reordering as well, for example Event. + +// TODO: make Span.Tags and Span.Data opaque types (struct{unexported []slice}). +// An opaque type allows us to add methods and make it more convenient to use +// than maps, because maps require careful nil checks to use properly or rely on +// explicit initialization for every span, even when there might be no +// tags/data. For Span.Data, must gracefully handle values that cannot be +// marshaled into JSON (see transport.go:getRequestBodyFromEvent). + +// StartSpan starts a new span to describe an operation. The new span will be a +// child of the last span stored in ctx, if any. +// +// One or more options can be used to modify the span properties. Typically one +// option as a function literal is enough. Combining multiple options can be +// useful to define and reuse specific properties with named functions. +// +// Caller should call the Finish method on the span to mark its end. Finishing a +// root span sends the span and all of its children, recursively, as a +// transaction to Sentry. +func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Span { + parent, hasParent := ctx.Value(spanContextKey{}).(*Span) + var span Span + span = Span{ + // defaults + Op: operation, + StartTime: time.Now(), + Sampled: SampledUndefined, + + ctx: context.WithValue(ctx, spanContextKey{}, &span), + parent: parent, + isTransaction: !hasParent, + } + if hasParent { + span.TraceID = parent.TraceID + } else { + // Only set the Source if this is a transaction + span.Source = SourceCustom + + // Implementation note: + // + // While math/rand is ~2x faster than crypto/rand (exact + // difference depends on hardware / OS), crypto/rand is probably + // fast enough and a safer choice. + // + // For reference, OpenTelemetry [1] uses crypto/rand to seed + // math/rand. AFAICT this approach does not preserve the + // properties from crypto/rand that make it suitable for + // cryptography. While it might be debatable whether those + // properties are important for us here, again, we're taking the + // safer path. + // + // See [2a] & [2b] for a discussion of some of the properties we + // obtain by using crypto/rand and [3a] & [3b] for why we avoid + // math/rand. + // + // Because the math/rand seed has only 64 bits (int64), if the + // first thing we do after seeding an RNG is to read in a random + // TraceID, there are only 2^64 possible values. Compared to + // UUID v4 that have 122 random bits, there is a much greater + // chance of collision [4a] & [4b]. + // + // [1]: https://github.com/open-telemetry/opentelemetry-go/blob/958041ddf619a128/sdk/trace/trace.go#L25-L31 + // [2a]: https://security.stackexchange.com/q/120352/246345 + // [2b]: https://security.stackexchange.com/a/120365/246345 + // [3a]: https://github.com/golang/go/issues/11871#issuecomment-126333686 + // [3b]: https://github.com/golang/go/issues/11871#issuecomment-126357889 + // [4a]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions + // [4b]: https://www.wolframalpha.com/input/?i=sqrt%282*2%5E64*ln%281%2F%281-0.5%29%29%29 + _, err := rand.Read(span.TraceID[:]) + if err != nil { + panic(err) + } + } + _, err := rand.Read(span.SpanID[:]) + if err != nil { + panic(err) + } + if hasParent { + span.ParentSpanID = parent.SpanID + } + + // Apply options to override defaults. + for _, option := range options { + option(&span) + } + + span.Sampled = span.sample() + + if hasParent { + span.recorder = parent.spanRecorder() + } else { + span.recorder = &spanRecorder{} + } + span.recorder.record(&span) + + // Update scope so that all events include a trace context, allowing + // Sentry to correlate errors to transactions/spans. + hubFromContext(ctx).Scope().SetContext("trace", span.traceContext().Map()) + + return &span +} + +// Finish sets the span's end time, unless already set. If the span is the root +// of a span tree, Finish sends the span tree to Sentry as a transaction. +func (s *Span) Finish() { + // TODO(tracing): maybe make Finish run at most once, such that + // (incorrectly) calling it twice never double sends to Sentry. + + if s.EndTime.IsZero() { + s.EndTime = monotonicTimeSince(s.StartTime) + } + if !s.Sampled.Bool() { + return + } + event := s.toEvent() + if event == nil { + return + } + + // TODO(tracing): add breadcrumbs + // (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372) + + hub := hubFromContext(s.ctx) + if hub.Scope().Transaction() == "" { + Logger.Printf("Missing transaction name for span with op = %q", s.Op) + } + hub.CaptureEvent(event) +} + +// Context returns the context containing the span. +func (s *Span) Context() context.Context { return s.ctx } + +// StartChild starts a new child span. +// +// The call span.StartChild(operation, options...) is a shortcut for +// StartSpan(span.Context(), operation, options...). +func (s *Span) StartChild(operation string, options ...SpanOption) *Span { + return StartSpan(s.Context(), operation, options...) +} + +// SetTag sets a tag on the span. It is recommended to use SetTag instead of +// accessing the tags map directly as SetTag takes care of initializing the map +// when necessary. +func (s *Span) SetTag(name, value string) { + if s.Tags == nil { + s.Tags = make(map[string]string) + } + s.Tags[name] = value +} + +// TODO(tracing): maybe add shortcuts to get/set transaction name. Right now the +// transaction name is in the Scope, as it has existed there historically, prior +// to tracing. +// +// See Scope.Transaction() and Scope.SetTransaction(). +// +// func (s *Span) TransactionName() string +// func (s *Span) SetTransactionName(name string) + +// ToSentryTrace returns the trace propagation value used with the sentry-trace +// HTTP header. +func (s *Span) ToSentryTrace() string { + // TODO(tracing): add instrumentation for outgoing HTTP requests using + // ToSentryTrace. + var b strings.Builder + fmt.Fprintf(&b, "%s-%s", s.TraceID.Hex(), s.SpanID.Hex()) + switch s.Sampled { + case SampledTrue: + b.WriteString("-1") + case SampledFalse: + b.WriteString("-0") + } + return b.String() +} + +func (s *Span) ToBaggage() string { + return s.dynamicSamplingContext.String() +} + +// sentryTracePattern matches either +// +// TRACE_ID - SPAN_ID +// [[:xdigit:]]{32}-[[:xdigit:]]{16} +// +// or +// +// TRACE_ID - SPAN_ID - SAMPLED +// [[:xdigit:]]{32}-[[:xdigit:]]{16}-[01] +var sentryTracePattern = regexp.MustCompile(`^([[:xdigit:]]{32})-([[:xdigit:]]{16})(?:-([01]))?$`) + +// updateFromSentryTrace parses a sentry-trace HTTP header (as returned by +// ToSentryTrace) and updates fields of the span. If the header cannot be +// recognized as valid, the span is left unchanged. +func (s *Span) updateFromSentryTrace(header []byte) { + m := sentryTracePattern.FindSubmatch(header) + if m == nil { + // no match + return + } + _, _ = hex.Decode(s.TraceID[:], m[1]) + _, _ = hex.Decode(s.ParentSpanID[:], m[2]) + if len(m[3]) != 0 { + switch m[3][0] { + case '0': + s.Sampled = SampledFalse + case '1': + s.Sampled = SampledTrue + } + } +} + +func (s *Span) updateFromBaggage(header []byte) { + if s.isTransaction { + dsc, err := DynamicSamplingContextFromHeader(header) + if err != nil { + return + } + + s.dynamicSamplingContext = dsc + } +} + +func (s *Span) MarshalJSON() ([]byte, error) { + // span aliases Span to allow calling json.Marshal without an infinite loop. + // It preserves all fields while none of the attached methods. + type span Span + var parentSpanID string + if s.ParentSpanID != zeroSpanID { + parentSpanID = s.ParentSpanID.String() + } + return json.Marshal(struct { + *span + ParentSpanID string `json:"parent_span_id,omitempty"` + }{ + span: (*span)(s), + ParentSpanID: parentSpanID, + }) +} + +func (s *Span) sample() Sampled { + hub := hubFromContext(s.ctx) + var clientOptions ClientOptions + client := hub.Client() + if client != nil { + clientOptions = hub.Client().Options() + } + + // https://develop.sentry.dev/sdk/performance/#sampling + // #1 tracing is not enabled. + if !clientOptions.EnableTracing { + Logger.Printf("Dropping transaction: EnableTracing is set to %t", clientOptions.EnableTracing) + s.sampleRate = 0.0 + return SampledFalse + } + + // #2 explicit sampling decision via StartSpan/StartTransaction options. + if s.Sampled != SampledUndefined { + Logger.Printf("Using explicit sampling decision from StartSpan/StartTransaction: %v", s.Sampled) + switch s.Sampled { + case SampledTrue: + s.sampleRate = 1.0 + case SampledFalse: + s.sampleRate = 0.0 + } + return s.Sampled + } + + // Variant for non-transaction spans: they inherit the parent decision. + // Note: non-transaction should always have a parent, but we check both + // conditions anyway -- the first for semantic meaning, the second to + // avoid a nil pointer dereference. + if !s.isTransaction && s.parent != nil { + return s.parent.Sampled + } + + // #3 use TracesSampler from ClientOptions. + sampler := clientOptions.TracesSampler + samplingContext := SamplingContext{Span: s, Parent: s.parent} + if sampler != nil { + tracesSamplerSampleRate := sampler.Sample(samplingContext) + s.sampleRate = tracesSamplerSampleRate + if tracesSamplerSampleRate < 0.0 || tracesSamplerSampleRate > 1.0 { + Logger.Printf("Dropping transaction: Returned TracesSampler rate is out of range [0.0, 1.0]: %f", tracesSamplerSampleRate) + return SampledFalse + } + if tracesSamplerSampleRate == 0 { + Logger.Printf("Dropping transaction: Returned TracesSampler rate is: %f", tracesSamplerSampleRate) + return SampledFalse + } + + if rng.Float64() < tracesSamplerSampleRate { + return SampledTrue + } + Logger.Printf("Dropping transaction: TracesSampler returned rate: %f", tracesSamplerSampleRate) + return SampledFalse + } + // #4 inherit parent decision. + if s.parent != nil { + Logger.Printf("Using sampling decision from parent: %v", s.parent.Sampled) + switch s.parent.Sampled { + case SampledTrue: + s.sampleRate = 1.0 + case SampledFalse: + s.sampleRate = 0.0 + } + return s.parent.Sampled + } + + // #5 use TracesSampleRate from ClientOptions. + sampleRate := clientOptions.TracesSampleRate + s.sampleRate = sampleRate + if sampleRate < 0.0 || sampleRate > 1.0 { + Logger.Printf("Dropping transaction: TracesSamplerRate out of range [0.0, 1.0]: %f", sampleRate) + return SampledFalse + } + if sampleRate == 0.0 { + Logger.Printf("Dropping transaction: TracesSampleRate rate is: %f", sampleRate) + return SampledFalse + } + + if rng.Float64() < sampleRate { + return SampledTrue + } + + return SampledFalse +} + +func (s *Span) toEvent() *Event { + if !s.isTransaction { + return nil // only transactions can be transformed into events + } + hub := hubFromContext(s.ctx) + + children := s.recorder.children() + finished := make([]*Span, 0, len(children)) + for _, child := range children { + if child.EndTime.IsZero() { + Logger.Printf("Dropped unfinished span: Op=%q TraceID=%s SpanID=%s", child.Op, child.TraceID, child.SpanID) + continue + } + finished = append(finished, child) + } + + // Create and attach a DynamicSamplingContext to the transaction. + // If the DynamicSamplingContext is not frozen at this point, we can assume being head of trace. + if !s.dynamicSamplingContext.IsFrozen() { + s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s) + } + + return &Event{ + Type: transactionType, + Transaction: hub.Scope().Transaction(), + Contexts: map[string]Context{ + "trace": s.traceContext().Map(), + }, + Tags: s.Tags, + Extra: s.Data, + Timestamp: s.EndTime, + StartTime: s.StartTime, + Spans: finished, + TransactionInfo: &TransactionInfo{ + Source: s.Source, + }, + sdkMetaData: SDKMetaData{ + dsc: s.dynamicSamplingContext, + }, + } +} + +func (s *Span) traceContext() *TraceContext { + return &TraceContext{ + TraceID: s.TraceID, + SpanID: s.SpanID, + ParentSpanID: s.ParentSpanID, + Op: s.Op, + Description: s.Description, + Status: s.Status, + } +} + +// spanRecorder stores the span tree. Guaranteed to be non-nil. +func (s *Span) spanRecorder() *spanRecorder { return s.recorder } + +// TraceID identifies a trace. +type TraceID [16]byte + +func (id TraceID) Hex() []byte { + b := make([]byte, hex.EncodedLen(len(id))) + hex.Encode(b, id[:]) + return b +} + +func (id TraceID) String() string { + return string(id.Hex()) +} + +func (id TraceID) MarshalText() ([]byte, error) { + return id.Hex(), nil +} + +// SpanID identifies a span. +type SpanID [8]byte + +func (id SpanID) Hex() []byte { + b := make([]byte, hex.EncodedLen(len(id))) + hex.Encode(b, id[:]) + return b +} + +func (id SpanID) String() string { + return string(id.Hex()) +} + +func (id SpanID) MarshalText() ([]byte, error) { + return id.Hex(), nil +} + +// Zero values of TraceID and SpanID used for comparisons. +var ( + zeroTraceID TraceID + zeroSpanID SpanID +) + +// Contains information about how the name of the transaction was determined. +type TransactionSource string + +const ( + SourceCustom TransactionSource = "custom" + SourceURL TransactionSource = "url" + SourceRoute TransactionSource = "route" + SourceView TransactionSource = "view" + SourceComponent TransactionSource = "component" + SourceTask TransactionSource = "task" +) + +// SpanStatus is the status of a span. +type SpanStatus uint8 + +// Implementation note: +// +// In Relay (ingestion), the SpanStatus type is an enum used as +// Annotated when embedded in structs, making it effectively +// Option. It means the status is either null or one of the known +// string values. +// +// In Snuba (search), the SpanStatus is stored as an uint8 and defaulted to 2 +// ("unknown") when not set. It means that Discover searches for +// `transaction.status:unknown` return both transactions/spans with status +// `null` or `"unknown"`. Searches for `transaction.status:""` return nothing. +// +// With that in mind, the Go SDK default is SpanStatusUndefined, which is +// null/omitted when serializing to JSON, but integrations may update the status +// automatically based on contextual information. + +const ( + SpanStatusUndefined SpanStatus = iota + SpanStatusOK + SpanStatusCanceled + SpanStatusUnknown + SpanStatusInvalidArgument + SpanStatusDeadlineExceeded + SpanStatusNotFound + SpanStatusAlreadyExists + SpanStatusPermissionDenied + SpanStatusResourceExhausted + SpanStatusFailedPrecondition + SpanStatusAborted + SpanStatusOutOfRange + SpanStatusUnimplemented + SpanStatusInternalError + SpanStatusUnavailable + SpanStatusDataLoss + SpanStatusUnauthenticated + maxSpanStatus +) + +func (ss SpanStatus) String() string { + if ss >= maxSpanStatus { + return "" + } + m := [maxSpanStatus]string{ + "", + "ok", + "cancelled", // [sic] + "unknown", + "invalid_argument", + "deadline_exceeded", + "not_found", + "already_exists", + "permission_denied", + "resource_exhausted", + "failed_precondition", + "aborted", + "out_of_range", + "unimplemented", + "internal_error", + "unavailable", + "data_loss", + "unauthenticated", + } + return m[ss] +} + +func (ss SpanStatus) MarshalJSON() ([]byte, error) { + s := ss.String() + if s == "" { + return []byte("null"), nil + } + return json.Marshal(s) +} + +// A TraceContext carries information about an ongoing trace and is meant to be +// stored in Event.Contexts (as *TraceContext). +type TraceContext struct { + TraceID TraceID `json:"trace_id"` + SpanID SpanID `json:"span_id"` + ParentSpanID SpanID `json:"parent_span_id"` + Op string `json:"op,omitempty"` + Description string `json:"description,omitempty"` + Status SpanStatus `json:"status,omitempty"` +} + +func (tc *TraceContext) MarshalJSON() ([]byte, error) { + // traceContext aliases TraceContext to allow calling json.Marshal without + // an infinite loop. It preserves all fields while none of the attached + // methods. + type traceContext TraceContext + var parentSpanID string + if tc.ParentSpanID != zeroSpanID { + parentSpanID = tc.ParentSpanID.String() + } + return json.Marshal(struct { + *traceContext + ParentSpanID string `json:"parent_span_id,omitempty"` + }{ + traceContext: (*traceContext)(tc), + ParentSpanID: parentSpanID, + }) +} + +func (tc TraceContext) Map() map[string]interface{} { + m := map[string]interface{}{ + "trace_id": tc.TraceID, + "span_id": tc.SpanID, + } + + if tc.ParentSpanID != [8]byte{} { + m["parent_span_id"] = tc.ParentSpanID + } + + if tc.Op != "" { + m["op"] = tc.Op + } + + if tc.Description != "" { + m["description"] = tc.Description + } + + if tc.Status > 0 && tc.Status < maxSpanStatus { + m["status"] = tc.Status + } + + return m +} + +// Sampled signifies a sampling decision. +type Sampled int8 + +// The possible trace sampling decisions are: SampledFalse, SampledUndefined +// (default) and SampledTrue. +const ( + SampledFalse Sampled = -1 + SampledUndefined Sampled = 0 + SampledTrue Sampled = 1 +) + +func (s Sampled) String() string { + switch s { + case SampledFalse: + return "SampledFalse" + case SampledUndefined: + return "SampledUndefined" + case SampledTrue: + return "SampledTrue" + default: + return fmt.Sprintf("SampledInvalid(%d)", s) + } +} + +// Bool returns true if the sample decision is SampledTrue, false otherwise. +func (s Sampled) Bool() bool { + return s == SampledTrue +} + +// A SpanOption is a function that can modify the properties of a span. +type SpanOption func(s *Span) + +// The TransactionName option sets the name of the current transaction. +// +// A span tree has a single transaction name, therefore using this option when +// starting a span affects the span tree as a whole, potentially overwriting a +// name set previously. +func TransactionName(name string) SpanOption { + return func(s *Span) { + hubFromContext(s.Context()).Scope().SetTransaction(name) + } +} + +// OpName sets the operation name for a given span. +func OpName(name string) SpanOption { + return func(s *Span) { + s.Op = name + } +} + +// TransctionSource sets the source of the transaction name. +func TransctionSource(source TransactionSource) SpanOption { + return func(s *Span) { + s.Source = source + } +} + +// ContinueFromRequest returns a span option that updates the span to continue +// an existing trace. If it cannot detect an existing trace in the request, the +// span will be left unchanged. +// +// ContinueFromRequest is an alias for: +// +// ContinueFromHeaders(r.Header.Get("sentry-trace"), r.Header.Get("baggage")). +func ContinueFromRequest(r *http.Request) SpanOption { + return ContinueFromHeaders(r.Header.Get("sentry-trace"), r.Header.Get("baggage")) +} + +// ContinueFromHeaders returns a span option that updates the span to continue +// an existing TraceID and propagates the Dynamic Sampling context. +func ContinueFromHeaders(trace, baggage string) SpanOption { + return func(s *Span) { + if trace != "" { + s.updateFromSentryTrace([]byte(trace)) + } + if baggage != "" { + s.updateFromBaggage([]byte(baggage)) + } + // In case a sentry-trace header is present but no baggage header, + // create an empty, frozen DynamicSamplingContext. + if trace != "" && baggage == "" { + s.dynamicSamplingContext = DynamicSamplingContext{ + Frozen: true, + } + } + } +} + +// ContinueFromTrace returns a span option that updates the span to continue +// an existing TraceID. +func ContinueFromTrace(trace string) SpanOption { + return func(s *Span) { + if trace == "" { + return + } + s.updateFromSentryTrace([]byte(trace)) + } +} + +// spanContextKey is used to store span values in contexts. +type spanContextKey struct{} + +// TransactionFromContext returns the root span of the current transaction. It +// returns nil if no transaction is tracked in the context. +func TransactionFromContext(ctx context.Context) *Span { + if span, ok := ctx.Value(spanContextKey{}).(*Span); ok { + return span.recorder.root() + } + return nil +} + +// spanFromContext returns the last span stored in the context or a dummy +// non-nil span. +// +// TODO(tracing): consider exporting this. Without this, users cannot retrieve a +// span from a context since spanContextKey is not exported. +// +// This can be added retroactively, and in the meantime think better whether it +// should return nil (like GetHubFromContext), always non-nil (like +// HubFromContext), or both: two exported functions. +// +// Note the equivalence: +// +// SpanFromContext(ctx).StartChild(...) === StartSpan(ctx, ...) +// +// So we don't aim spanFromContext at creating spans, but mutating existing +// spans that you'd have no access otherwise (because it was created in code you +// do not control, for example SDK auto-instrumentation). +// +// For now we provide TransactionFromContext, which solves the more common case +// of setting tags, etc, on the current transaction. +func spanFromContext(ctx context.Context) *Span { + if span, ok := ctx.Value(spanContextKey{}).(*Span); ok { + return span + } + return nil +} + +// StartTransaction will create a transaction (root span) if there's no existing +// transaction in the context otherwise, it will return the existing transaction. +func StartTransaction(ctx context.Context, name string, options ...SpanOption) *Span { + currentTransaction, exists := ctx.Value(spanContextKey{}).(*Span) + if exists { + return currentTransaction + } + + options = append(options, TransactionName(name)) + return StartSpan( + ctx, + "", + options..., + ) +} diff --git a/vendor/github.com/getsentry/sentry-go/transport.go b/vendor/github.com/getsentry/sentry-go/transport.go new file mode 100644 index 00000000..3722217e --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/transport.go @@ -0,0 +1,592 @@ +package sentry + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "sync" + "time" + + "github.com/getsentry/sentry-go/internal/ratelimit" +) + +const defaultBufferSize = 30 +const defaultTimeout = time.Second * 30 + +// maxDrainResponseBytes is the maximum number of bytes that transport +// implementations will read from response bodies when draining them. +// +// Sentry's ingestion API responses are typically short and the SDK doesn't need +// the contents of the response body. However, the net/http HTTP client requires +// response bodies to be fully drained (and closed) for TCP keep-alive to work. +// +// maxDrainResponseBytes strikes a balance between reading too much data (if the +// server is misbehaving) and reusing TCP connections. +const maxDrainResponseBytes = 16 << 10 + +// Transport is used by the Client to deliver events to remote server. +type Transport interface { + Flush(timeout time.Duration) bool + Configure(options ClientOptions) + SendEvent(event *Event) +} + +func getProxyConfig(options ClientOptions) func(*http.Request) (*url.URL, error) { + if options.HTTPSProxy != "" { + return func(*http.Request) (*url.URL, error) { + return url.Parse(options.HTTPSProxy) + } + } + + if options.HTTPProxy != "" { + return func(*http.Request) (*url.URL, error) { + return url.Parse(options.HTTPProxy) + } + } + + return http.ProxyFromEnvironment +} + +func getTLSConfig(options ClientOptions) *tls.Config { + if options.CaCerts != nil { + // #nosec G402 -- We should be using `MinVersion: tls.VersionTLS12`, + // but we don't want to break peoples code without the major bump. + return &tls.Config{ + RootCAs: options.CaCerts, + } + } + + return nil +} + +func getRequestBodyFromEvent(event *Event) []byte { + body, err := json.Marshal(event) + if err == nil { + return body + } + + msg := fmt.Sprintf("Could not encode original event as JSON. "+ + "Succeeded by removing Breadcrumbs, Contexts and Extra. "+ + "Please verify the data you attach to the scope. "+ + "Error: %s", err) + // Try to serialize the event, with all the contextual data that allows for interface{} stripped. + event.Breadcrumbs = nil + event.Contexts = nil + event.Extra = map[string]interface{}{ + "info": msg, + } + body, err = json.Marshal(event) + if err == nil { + Logger.Println(msg) + return body + } + + // This should _only_ happen when Event.Exception[0].Stacktrace.Frames[0].Vars is unserializable + // Which won't ever happen, as we don't use it now (although it's the part of public interface accepted by Sentry) + // Juuust in case something, somehow goes utterly wrong. + Logger.Println("Event couldn't be marshaled, even with stripped contextual data. Skipping delivery. " + + "Please notify the SDK owners with possibly broken payload.") + return nil +} + +func transactionEnvelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) { + var b bytes.Buffer + enc := json.NewEncoder(&b) + + // Construct the trace envelope header + var trace = map[string]string{} + if dsc := event.sdkMetaData.dsc; dsc.HasEntries() { + for k, v := range dsc.Entries { + trace[k] = v + } + } + + // Envelope header + err := enc.Encode(struct { + EventID EventID `json:"event_id"` + SentAt time.Time `json:"sent_at"` + Dsn string `json:"dsn"` + Sdk map[string]string `json:"sdk"` + Trace map[string]string `json:"trace,omitempty"` + }{ + EventID: event.EventID, + SentAt: sentAt, + Trace: trace, + Dsn: dsn.String(), + Sdk: map[string]string{ + "name": event.Sdk.Name, + "version": event.Sdk.Version, + }, + }) + if err != nil { + return nil, err + } + + // Item header + err = enc.Encode(struct { + Type string `json:"type"` + Length int `json:"length"` + }{ + Type: transactionType, + Length: len(body), + }) + if err != nil { + return nil, err + } + // payload + err = enc.Encode(body) + if err != nil { + return nil, err + } + + return &b, nil +} + +func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) { + defer func() { + if r != nil { + r.Header.Set("User-Agent", userAgent) + } + }() + body := getRequestBodyFromEvent(event) + if body == nil { + return nil, errors.New("event could not be marshaled") + } + if event.Type == transactionType { + b, err := transactionEnvelopeFromBody(event, dsn, time.Now(), body) + if err != nil { + return nil, err + } + return http.NewRequest( + http.MethodPost, + dsn.EnvelopeAPIURL().String(), + b, + ) + } + return http.NewRequest( + http.MethodPost, + dsn.StoreAPIURL().String(), + bytes.NewReader(body), + ) +} + +func categoryFor(eventType string) ratelimit.Category { + switch eventType { + case "": + return ratelimit.CategoryError + case transactionType: + return ratelimit.CategoryTransaction + default: + return ratelimit.Category(eventType) + } +} + +// ================================ +// HTTPTransport +// ================================ + +// A batch groups items that are processed sequentially. +type batch struct { + items chan batchItem + started chan struct{} // closed to signal items started to be worked on + done chan struct{} // closed to signal completion of all items +} + +type batchItem struct { + request *http.Request + category ratelimit.Category +} + +// HTTPTransport is the default, non-blocking, implementation of Transport. +// +// Clients using this transport will enqueue requests in a buffer and return to +// the caller before any network communication has happened. Requests are sent +// to Sentry sequentially from a background goroutine. +type HTTPTransport struct { + dsn *Dsn + client *http.Client + transport http.RoundTripper + + // buffer is a channel of batches. Calling Flush terminates work on the + // current in-flight items and starts a new batch for subsequent events. + buffer chan batch + + start sync.Once + + // Size of the transport buffer. Defaults to 30. + BufferSize int + // HTTP Client request timeout. Defaults to 30 seconds. + Timeout time.Duration + + mu sync.RWMutex + limits ratelimit.Map +} + +// NewHTTPTransport returns a new pre-configured instance of HTTPTransport. +func NewHTTPTransport() *HTTPTransport { + transport := HTTPTransport{ + BufferSize: defaultBufferSize, + Timeout: defaultTimeout, + limits: make(ratelimit.Map), + } + return &transport +} + +// Configure is called by the Client itself, providing it it's own ClientOptions. +func (t *HTTPTransport) Configure(options ClientOptions) { + dsn, err := NewDsn(options.Dsn) + if err != nil { + Logger.Printf("%v\n", err) + return + } + t.dsn = dsn + + // A buffered channel with capacity 1 works like a mutex, ensuring only one + // goroutine can access the current batch at a given time. Access is + // synchronized by reading from and writing to the channel. + t.buffer = make(chan batch, 1) + t.buffer <- batch{ + items: make(chan batchItem, t.BufferSize), + started: make(chan struct{}), + done: make(chan struct{}), + } + + if options.HTTPTransport != nil { + t.transport = options.HTTPTransport + } else { + t.transport = &http.Transport{ + Proxy: getProxyConfig(options), + TLSClientConfig: getTLSConfig(options), + } + } + + if options.HTTPClient != nil { + t.client = options.HTTPClient + } else { + t.client = &http.Client{ + Transport: t.transport, + Timeout: t.Timeout, + } + } + + t.start.Do(func() { + go t.worker() + }) +} + +// SendEvent assembles a new packet out of Event and sends it to remote server. +func (t *HTTPTransport) SendEvent(event *Event) { + if t.dsn == nil { + return + } + + category := categoryFor(event.Type) + + if t.disabled(category) { + return + } + + request, err := getRequestFromEvent(event, t.dsn) + if err != nil { + return + } + + for headerKey, headerValue := range t.dsn.RequestHeaders() { + request.Header.Set(headerKey, headerValue) + } + + // <-t.buffer is equivalent to acquiring a lock to access the current batch. + // A few lines below, t.buffer <- b releases the lock. + // + // The lock must be held during the select block below to guarantee that + // b.items is not closed while trying to send to it. Remember that sending + // on a closed channel panics. + // + // Note that the select block takes a bounded amount of CPU time because of + // the default case that is executed if sending on b.items would block. That + // is, the event is dropped if it cannot be sent immediately to the b.items + // channel (used as a queue). + b := <-t.buffer + + select { + case b.items <- batchItem{ + request: request, + category: category, + }: + var eventType string + if event.Type == transactionType { + eventType = "transaction" + } else { + eventType = fmt.Sprintf("%s event", event.Level) + } + Logger.Printf( + "Sending %s [%s] to %s project: %s", + eventType, + event.EventID, + t.dsn.host, + t.dsn.projectID, + ) + default: + Logger.Println("Event dropped due to transport buffer being full.") + } + + t.buffer <- b +} + +// Flush waits until any buffered events are sent to the Sentry server, blocking +// for at most the given timeout. It returns false if the timeout was reached. +// In that case, some events may not have been sent. +// +// Flush should be called before terminating the program to avoid +// unintentionally dropping events. +// +// Do not call Flush indiscriminately after every call to SendEvent. Instead, to +// have the SDK send events over the network synchronously, configure it to use +// the HTTPSyncTransport in the call to Init. +func (t *HTTPTransport) Flush(timeout time.Duration) bool { + toolate := time.After(timeout) + + // Wait until processing the current batch has started or the timeout. + // + // We must wait until the worker has seen the current batch, because it is + // the only way b.done will be closed. If we do not wait, there is a + // possible execution flow in which b.done is never closed, and the only way + // out of Flush would be waiting for the timeout, which is undesired. + var b batch + for { + select { + case b = <-t.buffer: + select { + case <-b.started: + goto started + default: + t.buffer <- b + } + case <-toolate: + goto fail + } + } + +started: + // Signal that there won't be any more items in this batch, so that the + // worker inner loop can end. + close(b.items) + // Start a new batch for subsequent events. + t.buffer <- batch{ + items: make(chan batchItem, t.BufferSize), + started: make(chan struct{}), + done: make(chan struct{}), + } + + // Wait until the current batch is done or the timeout. + select { + case <-b.done: + Logger.Println("Buffer flushed successfully.") + return true + case <-toolate: + goto fail + } + +fail: + Logger.Println("Buffer flushing reached the timeout.") + return false +} + +func (t *HTTPTransport) worker() { + for b := range t.buffer { + // Signal that processing of the current batch has started. + close(b.started) + + // Return the batch to the buffer so that other goroutines can use it. + // Equivalent to releasing a lock. + t.buffer <- b + + // Process all batch items. + for item := range b.items { + if t.disabled(item.category) { + continue + } + + response, err := t.client.Do(item.request) + if err != nil { + Logger.Printf("There was an issue with sending an event: %v", err) + continue + } + t.mu.Lock() + t.limits.Merge(ratelimit.FromResponse(response)) + t.mu.Unlock() + // Drain body up to a limit and close it, allowing the + // transport to reuse TCP connections. + _, _ = io.CopyN(io.Discard, response.Body, maxDrainResponseBytes) + response.Body.Close() + } + + // Signal that processing of the batch is done. + close(b.done) + } +} + +func (t *HTTPTransport) disabled(c ratelimit.Category) bool { + t.mu.RLock() + defer t.mu.RUnlock() + disabled := t.limits.IsRateLimited(c) + if disabled { + Logger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c)) + } + return disabled +} + +// ================================ +// HTTPSyncTransport +// ================================ + +// HTTPSyncTransport is a blocking implementation of Transport. +// +// Clients using this transport will send requests to Sentry sequentially and +// block until a response is returned. +// +// The blocking behavior is useful in a limited set of use cases. For example, +// use it when deploying code to a Function as a Service ("Serverless") +// platform, where any work happening in a background goroutine is not +// guaranteed to execute. +// +// For most cases, prefer HTTPTransport. +type HTTPSyncTransport struct { + dsn *Dsn + client *http.Client + transport http.RoundTripper + + mu sync.Mutex + limits ratelimit.Map + + // HTTP Client request timeout. Defaults to 30 seconds. + Timeout time.Duration +} + +// NewHTTPSyncTransport returns a new pre-configured instance of HTTPSyncTransport. +func NewHTTPSyncTransport() *HTTPSyncTransport { + transport := HTTPSyncTransport{ + Timeout: defaultTimeout, + limits: make(ratelimit.Map), + } + + return &transport +} + +// Configure is called by the Client itself, providing it it's own ClientOptions. +func (t *HTTPSyncTransport) Configure(options ClientOptions) { + dsn, err := NewDsn(options.Dsn) + if err != nil { + Logger.Printf("%v\n", err) + return + } + t.dsn = dsn + + if options.HTTPTransport != nil { + t.transport = options.HTTPTransport + } else { + t.transport = &http.Transport{ + Proxy: getProxyConfig(options), + TLSClientConfig: getTLSConfig(options), + } + } + + if options.HTTPClient != nil { + t.client = options.HTTPClient + } else { + t.client = &http.Client{ + Transport: t.transport, + Timeout: t.Timeout, + } + } +} + +// SendEvent assembles a new packet out of Event and sends it to remote server. +func (t *HTTPSyncTransport) SendEvent(event *Event) { + if t.dsn == nil { + return + } + + if t.disabled(categoryFor(event.Type)) { + return + } + + request, err := getRequestFromEvent(event, t.dsn) + if err != nil { + return + } + + for headerKey, headerValue := range t.dsn.RequestHeaders() { + request.Header.Set(headerKey, headerValue) + } + + var eventType string + if event.Type == transactionType { + eventType = "transaction" + } else { + eventType = fmt.Sprintf("%s event", event.Level) + } + Logger.Printf( + "Sending %s [%s] to %s project: %s", + eventType, + event.EventID, + t.dsn.host, + t.dsn.projectID, + ) + + response, err := t.client.Do(request) + if err != nil { + Logger.Printf("There was an issue with sending an event: %v", err) + return + } + t.mu.Lock() + t.limits.Merge(ratelimit.FromResponse(response)) + t.mu.Unlock() + + // Drain body up to a limit and close it, allowing the + // transport to reuse TCP connections. + _, _ = io.CopyN(io.Discard, response.Body, maxDrainResponseBytes) + response.Body.Close() +} + +// Flush is a no-op for HTTPSyncTransport. It always returns true immediately. +func (t *HTTPSyncTransport) Flush(_ time.Duration) bool { + return true +} + +func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool { + t.mu.Lock() + defer t.mu.Unlock() + disabled := t.limits.IsRateLimited(c) + if disabled { + Logger.Printf("Too many requests for %q, backing off till: %v", c, t.limits.Deadline(c)) + } + return disabled +} + +// ================================ +// noopTransport +// ================================ + +// noopTransport is an implementation of Transport interface which drops all the events. +// Only used internally when an empty DSN is provided, which effectively disables the SDK. +type noopTransport struct{} + +var _ Transport = noopTransport{} + +func (noopTransport) Configure(ClientOptions) { + Logger.Println("Sentry client initialized with an empty DSN. Using noopTransport. No events will be delivered.") +} + +func (noopTransport) SendEvent(*Event) { + Logger.Println("Event dropped due to noopTransport usage.") +} + +func (noopTransport) Flush(time.Duration) bool { + return true +} diff --git a/vendor/github.com/getsentry/sentry-go/util.go b/vendor/github.com/getsentry/sentry-go/util.go new file mode 100644 index 00000000..e5717c63 --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/util.go @@ -0,0 +1,91 @@ +package sentry + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + exec "golang.org/x/sys/execabs" +) + +func uuid() string { + id := make([]byte, 16) + // Prefer rand.Read over rand.Reader, see https://go-review.googlesource.com/c/go/+/272326/. + _, _ = rand.Read(id) + id[6] &= 0x0F // clear version + id[6] |= 0x40 // set version to 4 (random uuid) + id[8] &= 0x3F // clear variant + id[8] |= 0x80 // set to IETF variant + return hex.EncodeToString(id) +} + +func fileExists(fileName string) bool { + _, err := os.Stat(fileName) + return err == nil +} + +// monotonicTimeSince replaces uses of time.Now() to take into account the +// monotonic clock reading stored in start, such that duration = end - start is +// unaffected by changes in the system wall clock. +func monotonicTimeSince(start time.Time) (end time.Time) { + return start.Add(time.Since(start)) +} + +// nolint: deadcode, unused +func prettyPrint(data interface{}) { + dbg, _ := json.MarshalIndent(data, "", " ") + fmt.Println(string(dbg)) +} + +// defaultRelease attempts to guess a default release for the currently running +// program. +func defaultRelease() (release string) { + // Return first non-empty environment variable known to hold release info, if any. + envs := []string{ + "SENTRY_RELEASE", + "HEROKU_SLUG_COMMIT", + "SOURCE_VERSION", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CIRCLE_SHA1", + "GAE_DEPLOYMENT_ID", + "GITHUB_SHA", // GitHub Actions - https://help.github.com/en/actions + "COMMIT_REF", // Netlify - https://docs.netlify.com/ + "VERCEL_GIT_COMMIT_SHA", // Vercel - https://vercel.com/ + "ZEIT_GITHUB_COMMIT_SHA", // Zeit (now known as Vercel) + "ZEIT_GITLAB_COMMIT_SHA", + "ZEIT_BITBUCKET_COMMIT_SHA", + } + for _, e := range envs { + if release = os.Getenv(e); release != "" { + Logger.Printf("Using release from environment variable %s: %s", e, release) + return release + } + } + + // Derive a version string from Git. Example outputs: + // v1.0.1-0-g9de4 + // v2.0-8-g77df-dirty + // 4f72d7 + cmd := exec.Command("git", "describe", "--long", "--always", "--dirty") + b, err := cmd.Output() + if err != nil { + // Either Git is not available or the current directory is not a + // Git repository. + var s strings.Builder + fmt.Fprintf(&s, "Release detection failed: %v", err) + if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 { + fmt.Fprintf(&s, ": %s", err.Stderr) + } + Logger.Print(s.String()) + Logger.Print("Some Sentry features will not be available. See https://docs.sentry.io/product/releases/.") + Logger.Print("To stop seeing this message, pass a Release to sentry.Init or set the SENTRY_RELEASE environment variable.") + return "" + } + release = strings.TrimSpace(string(b)) + Logger.Printf("Using release from Git: %s", release) + return release +} diff --git a/vendor/github.com/go-chi/chi/v5/.gitignore b/vendor/github.com/go-chi/chi/v5/.gitignore new file mode 100644 index 00000000..ba22c99a --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/.gitignore @@ -0,0 +1,3 @@ +.idea +*.sw? +.vscode diff --git a/vendor/github.com/go-chi/chi/v5/CHANGELOG.md b/vendor/github.com/go-chi/chi/v5/CHANGELOG.md new file mode 100644 index 00000000..a1feeec0 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/CHANGELOG.md @@ -0,0 +1,320 @@ +# Changelog + +## v5.0.8 (2022-12-07) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8 + + +## v5.0.7 (2021-11-18) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.6...v5.0.7 + + +## v5.0.6 (2021-11-15) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.5...v5.0.6 + + +## v5.0.5 (2021-10-27) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.4...v5.0.5 + + +## v5.0.4 (2021-08-29) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.3...v5.0.4 + + +## v5.0.3 (2021-04-29) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.2...v5.0.3 + + +## v5.0.2 (2021-03-25) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.1...v5.0.2 + + +## v5.0.1 (2021-03-10) + +- Small improvements +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.0...v5.0.1 + + +## v5.0.0 (2021-02-27) + +- chi v5, `github.com/go-chi/chi/v5` introduces the adoption of Go's SIV to adhere to the current state-of-the-tools in Go. +- chi v1.5.x did not work out as planned, as the Go tooling is too powerful and chi's adoption is too wide. + The most responsible thing to do for everyone's benefit is to just release v5 with SIV, so I present to you all, + chi v5 at `github.com/go-chi/chi/v5`. I hope someday the developer experience and ergonomics I've been seeking + will still come to fruition in some form, see https://github.com/golang/go/issues/44550 +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.4...v5.0.0 + + +## v1.5.4 (2021-02-27) + +- Undo prior retraction in v1.5.3 as we prepare for v5.0.0 release +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.3...v1.5.4 + + +## v1.5.3 (2021-02-21) + +- Update go.mod to go 1.16 with new retract directive marking all versions without prior go.mod support +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.2...v1.5.3 + + +## v1.5.2 (2021-02-10) + +- Reverting allocation optimization as a precaution as go test -race fails. +- Minor improvements, see history below +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.1...v1.5.2 + + +## v1.5.1 (2020-12-06) + +- Performance improvement: removing 1 allocation by foregoing context.WithValue, thank you @bouk for + your contribution (https://github.com/go-chi/chi/pull/555). Note: new benchmarks posted in README. +- `middleware.CleanPath`: new middleware that clean's request path of double slashes +- deprecate & remove `chi.ServerBaseContext` in favour of stdlib `http.Server#BaseContext` +- plus other tiny improvements, see full commit history below +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.2...v1.5.1 + + +## v1.5.0 (2020-11-12) - now with go.mod support + +`chi` dates back to 2016 with it's original implementation as one of the first routers to adopt the newly introduced +context.Context api to the stdlib -- set out to design a router that is faster, more modular and simpler than anything +else out there -- while not introducing any custom handler types or dependencies. Today, `chi` still has zero dependencies, +and in many ways is future proofed from changes, given it's minimal nature. Between versions, chi's iterations have been very +incremental, with the architecture and api being the same today as it was originally designed in 2016. For this reason it +makes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years +to who all help make chi better (total of 86 contributors to date -- thanks all!). + +Chi has been an labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance +and simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size, +and always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting +middlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from +companies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of +joy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :) + +For me, the asthetics of chi's code and usage are very important. With the introduction of Go's module support +(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path +of "github.com/go-chi/chi/v4", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462. +Haha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt "/vXX" convention in the import +path -- which isn't horrible in general -- but for chi, I'm unable to accept it as I strive for perfection in it's API design, +aesthetics and simplicity. It just doesn't feel good to me given chi's simple nature -- I do not foresee a "v5" or "v6", +and upgrading between versions in the future will also be just incremental. + +I do understand versioning is a part of the API design as well, which is why the solution for a while has been to "do nothing", +as Go supports both old and new import paths with/out go.mod. However, now that Go module support has had time to iron out kinks and +is adopted everywhere, it's time for chi to get with the times. Luckily, I've discovered a path forward that will make me happy, +while also not breaking anyone's app who adopted a prior versioning from tags in v2/v3/v4. I've made an experimental release of +v1.5.0 with go.mod silently, and tested it with new and old projects, to ensure the developer experience is preserved, and it's +largely unnoticed. Fortunately, Go's toolchain will check the tags of a repo and consider the "latest" tag the one with go.mod. +However, you can still request a specific older tag such as v4.1.2, and everything will "just work". But new users can just +`go get github.com/go-chi/chi` or `go get github.com/go-chi/chi@latest` and they will get the latest version which contains +go.mod support, which is v1.5.0+. `chi` will not change very much over the years, just like it hasn't changed much from 4 years ago. +Therefore, we will stay on v1.x from here on, starting from v1.5.0. Any breaking changes will bump a "minor" release and +backwards-compatible improvements/fixes will bump a "tiny" release. + +For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`, +which will get you on the go.mod version line (as Go's mod cache may still remember v4.x). Brand new systems can run +`go get -u github.com/go-chi/chi` or `go get -u github.com/go-chi/chi@latest` to install chi, which will install v1.5.0+ +built with go.mod support. + +My apologies to the developers who will disagree with the decisions above, but, hope you'll try it and see it's a very +minor request which is backwards compatible and won't break your existing installations. + +Cheers all, happy coding! + + +--- + + +## v4.1.2 (2020-06-02) + +- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution +- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2 + + +## v4.1.1 (2020-04-16) + +- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp + route to the correct handler through a recursive tree search, thanks to @Jahaja for the PR/fix! +- new middleware.RouteHeaders as a simple router for request headers with wildcard support +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.0...v4.1.1 + + +## v4.1.0 (2020-04-1) + +- middleware.LogEntry: Write method on interface now passes the response header + and an extra interface type useful for custom logger implementations. +- middleware.WrapResponseWriter: minor fix +- middleware.Recoverer: a bit prettier +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0 + +## v4.0.4 (2020-03-24) + +- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496) +- a few minor improvements and fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4 + + +## v4.0.3 (2020-01-09) + +- core: fix regexp routing to include default value when param is not matched +- middleware: rewrite of middleware.Compress +- middleware: suppress http.ErrAbortHandler in middleware.Recoverer +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3 + + +## v4.0.2 (2019-02-26) + +- Minor fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2 + + +## v4.0.1 (2019-01-21) + +- Fixes issue with compress middleware: #382 #385 +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1 + + +## v4.0.0 (2019-01-10) + +- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 +- router: respond with 404 on router with no routes (#362) +- router: additional check to ensure wildcard is at the end of a url pattern (#333) +- middleware: deprecate use of http.CloseNotifier (#347) +- middleware: fix RedirectSlashes to include query params on redirect (#334) +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 + + +## v3.3.4 (2019-01-07) + +- Minor middleware improvements. No changes to core library/router. Moving v3 into its +- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 + + +## v3.3.3 (2018-08-27) + +- Minor release +- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 + + +## v3.3.2 (2017-12-22) + +- Support to route trailing slashes on mounted sub-routers (#281) +- middleware: new `ContentCharset` to check matching charsets. Thank you + @csucu for your community contribution! + + +## v3.3.1 (2017-11-20) + +- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types +- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value +- Minor bug fixes + + +## v3.3.0 (2017-10-10) + +- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage +- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function + + +## v3.2.1 (2017-08-31) + +- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface + and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path +- Add new `RouteMethod` to `*Context` +- Add new `Routes` pointer to `*Context` +- Add new `middleware.GetHead` to route missing HEAD requests to GET handler +- Updated benchmarks (see README) + + +## v3.1.5 (2017-08-02) + +- Setup golint and go vet for the project +- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` + to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` + + +## v3.1.0 (2017-07-10) + +- Fix a few minor issues after v3 release +- Move `docgen` sub-pkg to https://github.com/go-chi/docgen +- Move `render` sub-pkg to https://github.com/go-chi/render +- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime + suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in + https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. + + +## v3.0.0 (2017-06-21) + +- Major update to chi library with many exciting updates, but also some *breaking changes* +- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as + `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the + same router +- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: + `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` +- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as + `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like + in `_examples/custom-handler` +- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their + own using file handler with the stdlib, see `_examples/fileserver` for an example +- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` +- Moved the chi project to its own organization, to allow chi-related community packages to + be easily discovered and supported, at: https://github.com/go-chi +- *NOTE:* please update your import paths to `"github.com/go-chi/chi"` +- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 + + +## v2.1.0 (2017-03-30) + +- Minor improvements and update to the chi core library +- Introduced a brand new `chi/render` sub-package to complete the story of building + APIs to offer a pattern for managing well-defined request / response payloads. Please + check out the updated `_examples/rest` example for how it works. +- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface + + +## v2.0.0 (2017-01-06) + +- After many months of v2 being in an RC state with many companies and users running it in + production, the inclusion of some improvements to the middlewares, we are very pleased to + announce v2.0.0 of chi. + + +## v2.0.0-rc1 (2016-07-26) + +- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular + community `"net/context"` package has been included in the standard library as `"context"` and + utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other + request-scoped values. We're very excited about the new context addition and are proud to + introduce chi v2, a minimal and powerful routing package for building large HTTP services, + with zero external dependencies. Chi focuses on idiomatic design and encourages the use of + stdlib HTTP handlers and middlwares. +- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` +- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` +- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, + which provides direct access to URL routing parameters, the routing path and the matching + routing patterns. +- Users upgrading from chi v1 to v2, need to: + 1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to + the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` + 2. Use `chi.URLParam(r *http.Request, paramKey string) string` + or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value + + +## v1.0.0 (2016-07-01) + +- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. + + +## v0.9.0 (2016-03-31) + +- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) +- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters + has changed to: `chi.URLParam(ctx, "id")` diff --git a/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md new file mode 100644 index 00000000..c0ac2dfe --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing + +## Prerequisites + +1. [Install Go][go-install]. +2. Download the sources and switch the working directory: + + ```bash + go get -u -d github.com/go-chi/chi + cd $GOPATH/src/github.com/go-chi/chi + ``` + +## Submitting a Pull Request + +A typical workflow is: + +1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] +2. [Create a topic branch.][branch] +3. Add tests for your change. +4. Run `go test`. If your tests pass, return to the step 3. +5. Implement the change and ensure the steps from the previous step pass. +6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline. +7. [Add, commit and push your changes.][git-help] +8. [Submit a pull request.][pull-req] + +[go-install]: https://golang.org/doc/install +[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html +[fork]: https://help.github.com/articles/fork-a-repo +[branch]: http://learn.github.com/p/branching.html +[git-help]: https://guides.github.com +[pull-req]: https://help.github.com/articles/using-pull-requests diff --git a/vendor/github.com/go-chi/chi/v5/LICENSE b/vendor/github.com/go-chi/chi/v5/LICENSE new file mode 100644 index 00000000..d99f02ff --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. + +MIT License + +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. diff --git a/vendor/github.com/go-chi/chi/v5/Makefile b/vendor/github.com/go-chi/chi/v5/Makefile new file mode 100644 index 00000000..e0f18c7d --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/Makefile @@ -0,0 +1,22 @@ +.PHONY: all +all: + @echo "**********************************************************" + @echo "** chi build tool **" + @echo "**********************************************************" + + +.PHONY: test +test: + go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware + +.PHONY: test-router +test-router: + go test -race -v . + +.PHONY: test-middleware +test-middleware: + go test -race -v ./middleware + +.PHONY: docs +docs: + npx docsify-cli serve ./docs diff --git a/vendor/github.com/go-chi/chi/v5/README.md b/vendor/github.com/go-chi/chi/v5/README.md new file mode 100644 index 00000000..3e4cc4a2 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/README.md @@ -0,0 +1,500 @@ +# chi + + +[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] + +`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's +especially good at helping you write large REST API services that are kept maintainable as your +project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to +handle signaling, cancelation and request-scoped values across a handler chain. + +The focus of the project has been to seek out an elegant and comfortable design for writing +REST API servers, written during the development of the Pressly API service that powers our +public API service, which in turn powers all of our client-side applications. + +The key considerations of chi's design are: project structure, maintainability, standard http +handlers (stdlib-only), developer productivity, and deconstructing a large system into many small +parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also +included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) +and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! + +## Install + +`go get -u github.com/go-chi/chi/v5` + + +## Features + +* **Lightweight** - cloc'd in ~1000 LOC for the chi router +* **Fast** - yes, see [benchmarks](#benchmarks) +* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` +* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and sub-router mounting +* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts +* **Robust** - in production at Pressly, Cloudflare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) +* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown +* **Go.mod support** - as of v5, go.mod support (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)) +* **No external dependencies** - plain ol' Go stdlib + net/http + + +## Examples + +See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. + + +**As easy as:** + +```go +package main + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func main() { + r := chi.NewRouter() + r.Use(middleware.Logger) + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + http.ListenAndServe(":3000", r) +} +``` + +**REST Preview:** + +Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs +in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in +Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). + +I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed +above, they will show you all the features of chi and serve as a good form of documentation. + +```go +import ( + //... + "context" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func main() { + r := chi.NewRouter() + + // A good base middleware stack + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + // Set a timeout value on the request context (ctx), that will signal + // through ctx.Done() that the request has timed out and further + // processing should be stopped. + r.Use(middleware.Timeout(60 * time.Second)) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hi")) + }) + + // RESTy routes for "articles" resource + r.Route("/articles", func(r chi.Router) { + r.With(paginate).Get("/", listArticles) // GET /articles + r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 + + r.Post("/", createArticle) // POST /articles + r.Get("/search", searchArticles) // GET /articles/search + + // Regexp url parameters: + r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto + + // Subrouters: + r.Route("/{articleID}", func(r chi.Router) { + r.Use(ArticleCtx) + r.Get("/", getArticle) // GET /articles/123 + r.Put("/", updateArticle) // PUT /articles/123 + r.Delete("/", deleteArticle) // DELETE /articles/123 + }) + }) + + // Mount the admin sub-router + r.Mount("/admin", adminRouter()) + + http.ListenAndServe(":3333", r) +} + +func ArticleCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + articleID := chi.URLParam(r, "articleID") + article, err := dbGetArticle(articleID) + if err != nil { + http.Error(w, http.StatusText(404), 404) + return + } + ctx := context.WithValue(r.Context(), "article", article) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func getArticle(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + article, ok := ctx.Value("article").(*Article) + if !ok { + http.Error(w, http.StatusText(422), 422) + return + } + w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) +} + +// A completely separate router for administrator routes +func adminRouter() http.Handler { + r := chi.NewRouter() + r.Use(AdminOnly) + r.Get("/", adminIndex) + r.Get("/accounts", adminListAccounts) + return r +} + +func AdminOnly(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + perm, ok := ctx.Value("acl.permission").(YourPermissionType) + if !ok || !perm.IsAdmin() { + http.Error(w, http.StatusText(403), 403) + return + } + next.ServeHTTP(w, r) + }) +} +``` + + +## Router interface + +chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). +The router is fully compatible with `net/http`. + +Built on top of the tree is the `Router` interface: + +```go +// Router consisting of the core routing methods used by chi's Mux, +// using only the standard net/http. +type Router interface { + http.Handler + Routes + + // Use appends one or more middlewares onto the Router stack. + Use(middlewares ...func(http.Handler) http.Handler) + + // With adds inline middlewares for an endpoint handler. + With(middlewares ...func(http.Handler) http.Handler) Router + + // Group adds a new inline-Router along the current routing + // path, with a fresh middleware stack for the inline-Router. + Group(fn func(r Router)) Router + + // Route mounts a sub-Router along a `pattern`` string. + Route(pattern string, fn func(r Router)) Router + + // Mount attaches another http.Handler along ./pattern/* + Mount(pattern string, h http.Handler) + + // Handle and HandleFunc adds routes for `pattern` that matches + // all HTTP methods. + Handle(pattern string, h http.Handler) + HandleFunc(pattern string, h http.HandlerFunc) + + // Method and MethodFunc adds routes for `pattern` that matches + // the `method` HTTP method. + Method(method, pattern string, h http.Handler) + MethodFunc(method, pattern string, h http.HandlerFunc) + + // HTTP-method routing along `pattern` + Connect(pattern string, h http.HandlerFunc) + Delete(pattern string, h http.HandlerFunc) + Get(pattern string, h http.HandlerFunc) + Head(pattern string, h http.HandlerFunc) + Options(pattern string, h http.HandlerFunc) + Patch(pattern string, h http.HandlerFunc) + Post(pattern string, h http.HandlerFunc) + Put(pattern string, h http.HandlerFunc) + Trace(pattern string, h http.HandlerFunc) + + // NotFound defines a handler to respond whenever a route could + // not be found. + NotFound(h http.HandlerFunc) + + // MethodNotAllowed defines a handler to respond whenever a method is + // not allowed. + MethodNotAllowed(h http.HandlerFunc) +} + +// Routes interface adds two methods for router traversal, which is also +// used by the github.com/go-chi/docgen package to generate documentation for Routers. +type Routes interface { + // Routes returns the routing tree in an easily traversable structure. + Routes() []Route + + // Middlewares returns the list of middlewares in use by the router. + Middlewares() Middlewares + + // Match searches the routing tree for a handler that matches + // the method/path - similar to routing a http request, but without + // executing the handler thereafter. + Match(rctx *Context, method, path string) bool +} +``` + +Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern +supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters +can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters +and `chi.URLParam(r, "*")` for a wildcard parameter. + + +### Middleware handlers + +chi's middlewares are just stdlib net/http middleware handlers. There is nothing special +about them, which means the router and all the tooling is designed to be compatible and +friendly with any middleware in the community. This offers much better extensibility and reuse +of packages and is at the heart of chi's purpose. + +Here is an example of a standard net/http middleware where we assign a context key `"user"` +the value of `"123"`. This middleware sets a hypothetical user identifier on the request +context and calls the next handler in the chain. + +```go +// HTTP middleware setting a value on the request context +func MyMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // create new context from `r` request context, and assign key `"user"` + // to value of `"123"` + ctx := context.WithValue(r.Context(), "user", "123") + + // call the next handler in the chain, passing the response writer and + // the updated request object with the new context value. + // + // note: context.Context values are nested, so any previously set + // values will be accessible as well, and the new `"user"` key + // will be accessible from this point forward. + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +``` + + +### Request handlers + +chi uses standard net/http request handlers. This little snippet is an example of a http.Handler +func that reads a user identifier from the request context - hypothetically, identifying +the user sending an authenticated request, validated+set by a previous middleware handler. + +```go +// HTTP handler accessing data from the request context. +func MyRequestHandler(w http.ResponseWriter, r *http.Request) { + // here we read from the request context and fetch out `"user"` key set in + // the MyMiddleware example above. + user := r.Context().Value("user").(string) + + // respond to the client + w.Write([]byte(fmt.Sprintf("hi %s", user))) +} +``` + + +### URL parameters + +chi's router parses and stores URL parameters right onto the request context. Here is +an example of how to access URL params in your net/http handlers. And of course, middlewares +are able to access the same information. + +```go +// HTTP handler accessing the url routing parameters. +func MyRequestHandler(w http.ResponseWriter, r *http.Request) { + // fetch the url parameter `"userID"` from the request of a matching + // routing pattern. An example routing pattern could be: /users/{userID} + userID := chi.URLParam(r, "userID") + + // fetch `"key"` from the request context + ctx := r.Context() + key := ctx.Value("key").(string) + + // respond to the client + w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key))) +} +``` + + +## Middlewares + +chi comes equipped with an optional `middleware` package, providing a suite of standard +`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible +with `net/http` can be used with chi's mux. + +### Core middlewares + +---------------------------------------------------------------------------------------------------- +| chi/middleware Handler | description | +| :--------------------- | :---------------------------------------------------------------------- | +| [AllowContentEncoding] | Enforces a whitelist of request Content-Encoding headers | +| [AllowContentType] | Explicit whitelist of accepted request Content-Types | +| [BasicAuth] | Basic HTTP authentication | +| [Compress] | Gzip compression for clients that accept compressed responses | +| [ContentCharset] | Ensure charset for Content-Type request headers | +| [CleanPath] | Clean double slashes from request path | +| [GetHead] | Automatically route undefined HEAD requests to GET handlers | +| [Heartbeat] | Monitoring endpoint to check the servers pulse | +| [Logger] | Logs the start and end of each request with the elapsed processing time | +| [NoCache] | Sets response headers to prevent clients from caching | +| [Profiler] | Easily attach net/http/pprof to your routers | +| [RealIP] | Sets a http.Request's RemoteAddr to either X-Real-IP or X-Forwarded-For | +| [Recoverer] | Gracefully absorb panics and prints the stack trace | +| [RequestID] | Injects a request ID into the context of each request | +| [RedirectSlashes] | Redirect slashes on routing paths | +| [RouteHeaders] | Route handling for request headers | +| [SetHeader] | Short-hand middleware to set a response header key/value | +| [StripSlashes] | Strip slashes on routing paths | +| [Throttle] | Puts a ceiling on the number of concurrent requests | +| [Timeout] | Signals to the request context when the timeout deadline is reached | +| [URLFormat] | Parse extension from url and put it on request context | +| [WithValue] | Short-hand middleware to set a key/value on the request context | +---------------------------------------------------------------------------------------------------- + +[AllowContentEncoding]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentEncoding +[AllowContentType]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentType +[BasicAuth]: https://pkg.go.dev/github.com/go-chi/chi/middleware#BasicAuth +[Compress]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compress +[ContentCharset]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ContentCharset +[CleanPath]: https://pkg.go.dev/github.com/go-chi/chi/middleware#CleanPath +[GetHead]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetHead +[GetReqID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetReqID +[Heartbeat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Heartbeat +[Logger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Logger +[NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache +[Profiler]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Profiler +[RealIP]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RealIP +[Recoverer]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Recoverer +[RedirectSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RedirectSlashes +[RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger +[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID +[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders +[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader +[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes +[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle +[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog +[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts +[Timeout]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Timeout +[URLFormat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#URLFormat +[WithLogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithLogEntry +[WithValue]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithValue +[Compressor]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compressor +[DefaultLogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#DefaultLogFormatter +[EncoderFunc]: https://pkg.go.dev/github.com/go-chi/chi/middleware#EncoderFunc +[HeaderRoute]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRoute +[HeaderRouter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRouter +[LogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogEntry +[LogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogFormatter +[LoggerInterface]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LoggerInterface +[ThrottleOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleOpts +[WrapResponseWriter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WrapResponseWriter + +### Extra middlewares & packages + +Please see https://github.com/go-chi for additional packages. + +-------------------------------------------------------------------------------------------------------------------- +| package | description | +|:---------------------------------------------------|:------------------------------------------------------------- +| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) | +| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime | +| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication | +| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing | +| [httplog](https://github.com/go-chi/httplog) | Small but powerful structured HTTP request logging | +| [httprate](https://github.com/go-chi/httprate) | HTTP request rate limiter | +| [httptracer](https://github.com/go-chi/httptracer) | HTTP request performance tracing library | +| [httpvcr](https://github.com/go-chi/httpvcr) | Write deterministic tests for external sources | +| [stampede](https://github.com/go-chi/stampede) | HTTP request coalescer | +-------------------------------------------------------------------------------------------------------------------- + + +## context? + +`context` is a tiny pkg that provides simple interface to signal context across call stacks +and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani) +and is available in stdlib since go1.7. + +Learn more at https://blog.golang.org/context + +and.. +* Docs: https://golang.org/pkg/context +* Source: https://github.com/golang/go/tree/master/src/context + + +## Benchmarks + +The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark + +Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x + +```shell +BenchmarkChi_Param 3075895 384 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Param5 2116603 566 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Param20 964117 1227 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParamWrite 2863413 420 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubStatic 3045488 395 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubParam 2204115 540 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubAll 10000 113811 ns/op 81203 B/op 406 allocs/op +BenchmarkChi_GPlusStatic 3337485 359 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlusParam 2825853 423 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlus2Params 2471697 483 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlusAll 194220 5950 ns/op 5200 B/op 26 allocs/op +BenchmarkChi_ParseStatic 3365324 356 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParseParam 2976614 404 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Parse2Params 2638084 439 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParseAll 109567 11295 ns/op 10400 B/op 52 allocs/op +BenchmarkChi_StaticAll 16846 71308 ns/op 62802 B/op 314 allocs/op +``` + +Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc + +NOTE: the allocs in the benchmark above are from the calls to http.Request's +`WithContext(context.Context)` method that clones the http.Request, sets the `Context()` +on the duplicated (alloc'd) request and returns it the new request object. This is just +how setting context on a request in Go works. + + +## Credits + +* Carl Jackson for https://github.com/zenazn/goji + * Parts of chi's thinking comes from goji, and chi's middleware package + sources from goji. +* Armon Dadgar for https://github.com/armon/go-radix +* Contributions: [@VojtechVitek](https://github.com/VojtechVitek) + +We'll be more than happy to see [your contributions](./CONTRIBUTING.md)! + + +## Beyond REST + +chi is just a http router that lets you decompose request handling into many smaller layers. +Many companies use chi to write REST services for their public APIs. But, REST is just a convention +for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server +system or network of microservices. + +Looking beyond REST, I also recommend some newer works in the field: +* [webrpc](https://github.com/webrpc/webrpc) - Web-focused RPC client+server framework with code-gen +* [gRPC](https://github.com/grpc/grpc-go) - Google's RPC framework via protobufs +* [graphql](https://github.com/99designs/gqlgen) - Declarative query language +* [NATS](https://nats.io) - lightweight pub-sub + + +## License + +Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) + +Licensed under [MIT License](./LICENSE) + +[GoDoc]: https://pkg.go.dev/github.com/go-chi/chi?tab=versions +[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg +[Travis]: https://travis-ci.org/go-chi/chi +[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master diff --git a/vendor/github.com/go-chi/chi/v5/chain.go b/vendor/github.com/go-chi/chi/v5/chain.go new file mode 100644 index 00000000..a2278414 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/chain.go @@ -0,0 +1,49 @@ +package chi + +import "net/http" + +// Chain returns a Middlewares type from a slice of middleware handlers. +func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { + return Middlewares(middlewares) +} + +// Handler builds and returns a http.Handler from the chain of middlewares, +// with `h http.Handler` as the final handler. +func (mws Middlewares) Handler(h http.Handler) http.Handler { + return &ChainHandler{h, chain(mws, h), mws} +} + +// HandlerFunc builds and returns a http.Handler from the chain of middlewares, +// with `h http.Handler` as the final handler. +func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler { + return &ChainHandler{h, chain(mws, h), mws} +} + +// ChainHandler is a http.Handler with support for handler composition and +// execution. +type ChainHandler struct { + Endpoint http.Handler + chain http.Handler + Middlewares Middlewares +} + +func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c.chain.ServeHTTP(w, r) +} + +// chain builds a http.Handler composed of an inline middleware stack and endpoint +// handler in the order they are passed. +func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler { + // Return ahead of time if there aren't any middlewares for the chain + if len(middlewares) == 0 { + return endpoint + } + + // Wrap the end handler with the middleware chain + h := middlewares[len(middlewares)-1](endpoint) + for i := len(middlewares) - 2; i >= 0; i-- { + h = middlewares[i](h) + } + + return h +} diff --git a/vendor/github.com/go-chi/chi/v5/chi.go b/vendor/github.com/go-chi/chi/v5/chi.go new file mode 100644 index 00000000..a1691bbe --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/chi.go @@ -0,0 +1,134 @@ +// Package chi is a small, idiomatic and composable router for building HTTP services. +// +// chi requires Go 1.14 or newer. +// +// Example: +// +// package main +// +// import ( +// "net/http" +// +// "github.com/go-chi/chi/v5" +// "github.com/go-chi/chi/v5/middleware" +// ) +// +// func main() { +// r := chi.NewRouter() +// r.Use(middleware.Logger) +// r.Use(middleware.Recoverer) +// +// r.Get("/", func(w http.ResponseWriter, r *http.Request) { +// w.Write([]byte("root.")) +// }) +// +// http.ListenAndServe(":3333", r) +// } +// +// See github.com/go-chi/chi/_examples/ for more in-depth examples. +// +// URL patterns allow for easy matching of path components in HTTP +// requests. The matching components can then be accessed using +// chi.URLParam(). All patterns must begin with a slash. +// +// A simple named placeholder {name} matches any sequence of characters +// up to the next / or the end of the URL. Trailing slashes on paths must +// be handled explicitly. +// +// A placeholder with a name followed by a colon allows a regular +// expression match, for example {number:\\d+}. The regular expression +// syntax is Go's normal regexp RE2 syntax, except that regular expressions +// including { or } are not supported, and / will never be +// matched. An anonymous regexp pattern is allowed, using an empty string +// before the colon in the placeholder, such as {:\\d+} +// +// The special placeholder of asterisk matches the rest of the requested +// URL. Any trailing characters in the pattern are ignored. This is the only +// placeholder which will match / characters. +// +// Examples: +// +// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" +// "/user/{name}/info" matches "/user/jsmith/info" +// "/page/*" matches "/page/intro/latest" +// "/page/{other}/index" also matches "/page/intro/latest" +// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" +package chi + +import "net/http" + +// NewRouter returns a new Mux object that implements the Router interface. +func NewRouter() *Mux { + return NewMux() +} + +// Router consisting of the core routing methods used by chi's Mux, +// using only the standard net/http. +type Router interface { + http.Handler + Routes + + // Use appends one or more middlewares onto the Router stack. + Use(middlewares ...func(http.Handler) http.Handler) + + // With adds inline middlewares for an endpoint handler. + With(middlewares ...func(http.Handler) http.Handler) Router + + // Group adds a new inline-Router along the current routing + // path, with a fresh middleware stack for the inline-Router. + Group(fn func(r Router)) Router + + // Route mounts a sub-Router along a `pattern`` string. + Route(pattern string, fn func(r Router)) Router + + // Mount attaches another http.Handler along ./pattern/* + Mount(pattern string, h http.Handler) + + // Handle and HandleFunc adds routes for `pattern` that matches + // all HTTP methods. + Handle(pattern string, h http.Handler) + HandleFunc(pattern string, h http.HandlerFunc) + + // Method and MethodFunc adds routes for `pattern` that matches + // the `method` HTTP method. + Method(method, pattern string, h http.Handler) + MethodFunc(method, pattern string, h http.HandlerFunc) + + // HTTP-method routing along `pattern` + Connect(pattern string, h http.HandlerFunc) + Delete(pattern string, h http.HandlerFunc) + Get(pattern string, h http.HandlerFunc) + Head(pattern string, h http.HandlerFunc) + Options(pattern string, h http.HandlerFunc) + Patch(pattern string, h http.HandlerFunc) + Post(pattern string, h http.HandlerFunc) + Put(pattern string, h http.HandlerFunc) + Trace(pattern string, h http.HandlerFunc) + + // NotFound defines a handler to respond whenever a route could + // not be found. + NotFound(h http.HandlerFunc) + + // MethodNotAllowed defines a handler to respond whenever a method is + // not allowed. + MethodNotAllowed(h http.HandlerFunc) +} + +// Routes interface adds two methods for router traversal, which is also +// used by the `docgen` subpackage to generation documentation for Routers. +type Routes interface { + // Routes returns the routing tree in an easily traversable structure. + Routes() []Route + + // Middlewares returns the list of middlewares in use by the router. + Middlewares() Middlewares + + // Match searches the routing tree for a handler that matches + // the method/path - similar to routing a http request, but without + // executing the handler thereafter. + Match(rctx *Context, method, path string) bool +} + +// Middlewares type is a slice of standard middleware handlers with methods +// to compose middleware chains and http.Handler's. +type Middlewares []func(http.Handler) http.Handler diff --git a/vendor/github.com/go-chi/chi/v5/context.go b/vendor/github.com/go-chi/chi/v5/context.go new file mode 100644 index 00000000..e78a2385 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/context.go @@ -0,0 +1,159 @@ +package chi + +import ( + "context" + "net/http" + "strings" +) + +// URLParam returns the url parameter from a http.Request object. +func URLParam(r *http.Request, key string) string { + if rctx := RouteContext(r.Context()); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// URLParamFromCtx returns the url parameter from a http.Request Context. +func URLParamFromCtx(ctx context.Context, key string) string { + if rctx := RouteContext(ctx); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// RouteContext returns chi's routing Context object from a +// http.Request Context. +func RouteContext(ctx context.Context) *Context { + val, _ := ctx.Value(RouteCtxKey).(*Context) + return val +} + +// NewRouteContext returns a new routing Context object. +func NewRouteContext() *Context { + return &Context{} +} + +var ( + // RouteCtxKey is the context.Context key to store the request context. + RouteCtxKey = &contextKey{"RouteContext"} +) + +// Context is the default routing context set on the root node of a +// request context to track route patterns, URL parameters and +// an optional routing path. +type Context struct { + Routes Routes + + // parentCtx is the parent of this one, for using Context as a + // context.Context directly. This is an optimization that saves + // 1 allocation. + parentCtx context.Context + + // Routing path/method override used during the route search. + // See Mux#routeHTTP method. + RoutePath string + RouteMethod string + + // URLParams are the stack of routeParams captured during the + // routing lifecycle across a stack of sub-routers. + URLParams RouteParams + + // Route parameters matched for the current sub-router. It is + // intentionally unexported so it cant be tampered. + routeParams RouteParams + + // The endpoint routing pattern that matched the request URI path + // or `RoutePath` of the current sub-router. This value will update + // during the lifecycle of a request passing through a stack of + // sub-routers. + routePattern string + + // Routing pattern stack throughout the lifecycle of the request, + // across all connected routers. It is a record of all matching + // patterns across a stack of sub-routers. + RoutePatterns []string + + // methodNotAllowed hint + methodNotAllowed bool +} + +// Reset a routing context to its initial state. +func (x *Context) Reset() { + x.Routes = nil + x.RoutePath = "" + x.RouteMethod = "" + x.RoutePatterns = x.RoutePatterns[:0] + x.URLParams.Keys = x.URLParams.Keys[:0] + x.URLParams.Values = x.URLParams.Values[:0] + + x.routePattern = "" + x.routeParams.Keys = x.routeParams.Keys[:0] + x.routeParams.Values = x.routeParams.Values[:0] + x.methodNotAllowed = false + x.parentCtx = nil +} + +// URLParam returns the corresponding URL parameter value from the request +// routing context. +func (x *Context) URLParam(key string) string { + for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { + if x.URLParams.Keys[k] == key { + return x.URLParams.Values[k] + } + } + return "" +} + +// RoutePattern builds the routing pattern string for the particular +// request, at the particular point during routing. This means, the value +// will change throughout the execution of a request in a router. That is +// why its advised to only use this value after calling the next handler. +// +// For example, +// +// func Instrument(next http.Handler) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// next.ServeHTTP(w, r) +// routePattern := chi.RouteContext(r.Context()).RoutePattern() +// measure(w, r, routePattern) +// }) +// } +func (x *Context) RoutePattern() string { + routePattern := strings.Join(x.RoutePatterns, "") + routePattern = replaceWildcards(routePattern) + routePattern = strings.TrimSuffix(routePattern, "//") + routePattern = strings.TrimSuffix(routePattern, "/") + return routePattern +} + +// replaceWildcards takes a route pattern and recursively replaces all +// occurrences of "/*/" to "/". +func replaceWildcards(p string) string { + if strings.Contains(p, "/*/") { + return replaceWildcards(strings.Replace(p, "/*/", "/", -1)) + } + return p +} + +// RouteParams is a structure to track URL routing parameters efficiently. +type RouteParams struct { + Keys, Values []string +} + +// Add will append a URL parameter to the end of the route param +func (s *RouteParams) Add(key, value string) { + s.Keys = append(s.Keys, key) + s.Values = append(s.Values, value) +} + +// contextKey is a value for use with context.WithValue. It's used as +// a pointer so it fits in an interface{} without allocation. This technique +// for defining context keys was copied from Go 1.7's new use of context in net/http. +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "chi context value " + k.name +} diff --git a/vendor/github.com/go-chi/chi/v5/mux.go b/vendor/github.com/go-chi/chi/v5/mux.go new file mode 100644 index 00000000..47e64cf2 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/mux.go @@ -0,0 +1,487 @@ +package chi + +import ( + "context" + "fmt" + "net/http" + "strings" + "sync" +) + +var _ Router = &Mux{} + +// Mux is a simple HTTP route multiplexer that parses a request path, +// records any URL params, and executes an end handler. It implements +// the http.Handler interface and is friendly with the standard library. +// +// Mux is designed to be fast, minimal and offer a powerful API for building +// modular and composable HTTP services with a large set of handlers. It's +// particularly useful for writing large REST API services that break a handler +// into many smaller parts composed of middlewares and end handlers. +type Mux struct { + // The computed mux handler made of the chained middleware stack and + // the tree router + handler http.Handler + + // The radix trie router + tree *node + + // Custom method not allowed handler + methodNotAllowedHandler http.HandlerFunc + + // A reference to the parent mux used by subrouters when mounting + // to a parent mux + parent *Mux + + // Routing context pool + pool *sync.Pool + + // Custom route not found handler + notFoundHandler http.HandlerFunc + + // The middleware stack + middlewares []func(http.Handler) http.Handler + + // Controls the behaviour of middleware chain generation when a mux + // is registered as an inline group inside another mux. + inline bool +} + +// NewMux returns a newly initialized Mux object that implements the Router +// interface. +func NewMux() *Mux { + mux := &Mux{tree: &node{}, pool: &sync.Pool{}} + mux.pool.New = func() interface{} { + return NewRouteContext() + } + return mux +} + +// ServeHTTP is the single method of the http.Handler interface that makes +// Mux interoperable with the standard library. It uses a sync.Pool to get and +// reuse routing contexts for each request. +func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Ensure the mux has some routes defined on the mux + if mx.handler == nil { + mx.NotFoundHandler().ServeHTTP(w, r) + return + } + + // Check if a routing context already exists from a parent router. + rctx, _ := r.Context().Value(RouteCtxKey).(*Context) + if rctx != nil { + mx.handler.ServeHTTP(w, r) + return + } + + // Fetch a RouteContext object from the sync pool, and call the computed + // mx.handler that is comprised of mx.middlewares + mx.routeHTTP. + // Once the request is finished, reset the routing context and put it back + // into the pool for reuse from another request. + rctx = mx.pool.Get().(*Context) + rctx.Reset() + rctx.Routes = mx + rctx.parentCtx = r.Context() + + // NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation + r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx)) + + // Serve the request and once its done, put the request context back in the sync pool + mx.handler.ServeHTTP(w, r) + mx.pool.Put(rctx) +} + +// Use appends a middleware handler to the Mux middleware stack. +// +// The middleware stack for any Mux will execute before searching for a matching +// route to a specific handler, which provides opportunity to respond early, +// change the course of the request execution, or set request-scoped values for +// the next http.Handler. +func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { + if mx.handler != nil { + panic("chi: all middlewares must be defined before routes on a mux") + } + mx.middlewares = append(mx.middlewares, middlewares...) +} + +// Handle adds the route `pattern` that matches any http method to +// execute the `handler` http.Handler. +func (mx *Mux) Handle(pattern string, handler http.Handler) { + mx.handle(mALL, pattern, handler) +} + +// HandleFunc adds the route `pattern` that matches any http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mALL, pattern, handlerFn) +} + +// Method adds the route `pattern` that matches `method` http method to +// execute the `handler` http.Handler. +func (mx *Mux) Method(method, pattern string, handler http.Handler) { + m, ok := methodMap[strings.ToUpper(method)] + if !ok { + panic(fmt.Sprintf("chi: '%s' http method is not supported.", method)) + } + mx.handle(m, pattern, handler) +} + +// MethodFunc adds the route `pattern` that matches `method` http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) { + mx.Method(method, pattern, handlerFn) +} + +// Connect adds the route `pattern` that matches a CONNECT http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mCONNECT, pattern, handlerFn) +} + +// Delete adds the route `pattern` that matches a DELETE http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mDELETE, pattern, handlerFn) +} + +// Get adds the route `pattern` that matches a GET http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mGET, pattern, handlerFn) +} + +// Head adds the route `pattern` that matches a HEAD http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mHEAD, pattern, handlerFn) +} + +// Options adds the route `pattern` that matches a OPTIONS http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mOPTIONS, pattern, handlerFn) +} + +// Patch adds the route `pattern` that matches a PATCH http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPATCH, pattern, handlerFn) +} + +// Post adds the route `pattern` that matches a POST http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPOST, pattern, handlerFn) +} + +// Put adds the route `pattern` that matches a PUT http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPUT, pattern, handlerFn) +} + +// Trace adds the route `pattern` that matches a TRACE http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mTRACE, pattern, handlerFn) +} + +// NotFound sets a custom http.HandlerFunc for routing paths that could +// not be found. The default 404 handler is `http.NotFound`. +func (mx *Mux) NotFound(handlerFn http.HandlerFunc) { + // Build NotFound handler chain + m := mx + hFn := handlerFn + if mx.inline && mx.parent != nil { + m = mx.parent + hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP + } + + // Update the notFoundHandler from this point forward + m.notFoundHandler = hFn + m.updateSubRoutes(func(subMux *Mux) { + if subMux.notFoundHandler == nil { + subMux.NotFound(hFn) + } + }) +} + +// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the +// method is unresolved. The default handler returns a 405 with an empty body. +func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { + // Build MethodNotAllowed handler chain + m := mx + hFn := handlerFn + if mx.inline && mx.parent != nil { + m = mx.parent + hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP + } + + // Update the methodNotAllowedHandler from this point forward + m.methodNotAllowedHandler = hFn + m.updateSubRoutes(func(subMux *Mux) { + if subMux.methodNotAllowedHandler == nil { + subMux.MethodNotAllowed(hFn) + } + }) +} + +// With adds inline middlewares for an endpoint handler. +func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { + // Similarly as in handle(), we must build the mux handler once additional + // middleware registration isn't allowed for this stack, like now. + if !mx.inline && mx.handler == nil { + mx.updateRouteHandler() + } + + // Copy middlewares from parent inline muxs + var mws Middlewares + if mx.inline { + mws = make(Middlewares, len(mx.middlewares)) + copy(mws, mx.middlewares) + } + mws = append(mws, middlewares...) + + im := &Mux{ + pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws, + notFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler, + } + + return im +} + +// Group creates a new inline-Mux with a fresh middleware stack. It's useful +// for a group of handlers along the same routing path that use an additional +// set of middlewares. See _examples/. +func (mx *Mux) Group(fn func(r Router)) Router { + im := mx.With().(*Mux) + if fn != nil { + fn(im) + } + return im +} + +// Route creates a new Mux with a fresh middleware stack and mounts it +// along the `pattern` as a subrouter. Effectively, this is a short-hand +// call to Mount. See _examples/. +func (mx *Mux) Route(pattern string, fn func(r Router)) Router { + if fn == nil { + panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern)) + } + subRouter := NewRouter() + fn(subRouter) + mx.Mount(pattern, subRouter) + return subRouter +} + +// Mount attaches another http.Handler or chi Router as a subrouter along a routing +// path. It's very useful to split up a large API as many independent routers and +// compose them as a single service using Mount. See _examples/. +// +// Note that Mount() simply sets a wildcard along the `pattern` that will continue +// routing at the `handler`, which in most cases is another chi.Router. As a result, +// if you define two Mount() routes on the exact same pattern the mount will panic. +func (mx *Mux) Mount(pattern string, handler http.Handler) { + if handler == nil { + panic(fmt.Sprintf("chi: attempting to Mount() a nil handler on '%s'", pattern)) + } + + // Provide runtime safety for ensuring a pattern isn't mounted on an existing + // routing pattern. + if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { + panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern)) + } + + // Assign sub-Router's with the parent not found & method not allowed handler if not specified. + subr, ok := handler.(*Mux) + if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil { + subr.NotFound(mx.notFoundHandler) + } + if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil { + subr.MethodNotAllowed(mx.methodNotAllowedHandler) + } + + mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rctx := RouteContext(r.Context()) + + // shift the url path past the previous subrouter + rctx.RoutePath = mx.nextRoutePath(rctx) + + // reset the wildcard URLParam which connects the subrouter + n := len(rctx.URLParams.Keys) - 1 + if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n { + rctx.URLParams.Values[n] = "" + } + + handler.ServeHTTP(w, r) + }) + + if pattern == "" || pattern[len(pattern)-1] != '/' { + mx.handle(mALL|mSTUB, pattern, mountHandler) + mx.handle(mALL|mSTUB, pattern+"/", mountHandler) + pattern += "/" + } + + method := mALL + subroutes, _ := handler.(Routes) + if subroutes != nil { + method |= mSTUB + } + n := mx.handle(method, pattern+"*", mountHandler) + + if subroutes != nil { + n.subroutes = subroutes + } +} + +// Routes returns a slice of routing information from the tree, +// useful for traversing available routes of a router. +func (mx *Mux) Routes() []Route { + return mx.tree.routes() +} + +// Middlewares returns a slice of middleware handler functions. +func (mx *Mux) Middlewares() Middlewares { + return mx.middlewares +} + +// Match searches the routing tree for a handler that matches the method/path. +// It's similar to routing a http request, but without executing the handler +// thereafter. +// +// Note: the *Context state is updated during execution, so manage +// the state carefully or make a NewRouteContext(). +func (mx *Mux) Match(rctx *Context, method, path string) bool { + m, ok := methodMap[method] + if !ok { + return false + } + + node, _, h := mx.tree.FindRoute(rctx, m, path) + + if node != nil && node.subroutes != nil { + rctx.RoutePath = mx.nextRoutePath(rctx) + return node.subroutes.Match(rctx, method, rctx.RoutePath) + } + + return h != nil +} + +// NotFoundHandler returns the default Mux 404 responder whenever a route +// cannot be found. +func (mx *Mux) NotFoundHandler() http.HandlerFunc { + if mx.notFoundHandler != nil { + return mx.notFoundHandler + } + return http.NotFound +} + +// MethodNotAllowedHandler returns the default Mux 405 responder whenever +// a method cannot be resolved for a route. +func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc { + if mx.methodNotAllowedHandler != nil { + return mx.methodNotAllowedHandler + } + return methodNotAllowedHandler +} + +// handle registers a http.Handler in the routing tree for a particular http method +// and routing pattern. +func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node { + if len(pattern) == 0 || pattern[0] != '/' { + panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) + } + + // Build the computed routing handler for this routing pattern. + if !mx.inline && mx.handler == nil { + mx.updateRouteHandler() + } + + // Build endpoint handler with inline middlewares for the route + var h http.Handler + if mx.inline { + mx.handler = http.HandlerFunc(mx.routeHTTP) + h = Chain(mx.middlewares...).Handler(handler) + } else { + h = handler + } + + // Add the endpoint to the tree and return the node + return mx.tree.InsertRoute(method, pattern, h) +} + +// routeHTTP routes a http.Request through the Mux routing tree to serve +// the matching handler for a particular http method. +func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { + // Grab the route context object + rctx := r.Context().Value(RouteCtxKey).(*Context) + + // The request routing path + routePath := rctx.RoutePath + if routePath == "" { + if r.URL.RawPath != "" { + routePath = r.URL.RawPath + } else { + routePath = r.URL.Path + } + if routePath == "" { + routePath = "/" + } + } + + // Check if method is supported by chi + if rctx.RouteMethod == "" { + rctx.RouteMethod = r.Method + } + method, ok := methodMap[rctx.RouteMethod] + if !ok { + mx.MethodNotAllowedHandler().ServeHTTP(w, r) + return + } + + // Find the route + if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { + h.ServeHTTP(w, r) + return + } + if rctx.methodNotAllowed { + mx.MethodNotAllowedHandler().ServeHTTP(w, r) + } else { + mx.NotFoundHandler().ServeHTTP(w, r) + } +} + +func (mx *Mux) nextRoutePath(rctx *Context) string { + routePath := "/" + nx := len(rctx.routeParams.Keys) - 1 // index of last param in list + if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx { + routePath = "/" + rctx.routeParams.Values[nx] + } + return routePath +} + +// Recursively update data on child routers. +func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { + for _, r := range mx.tree.routes() { + subMux, ok := r.SubRoutes.(*Mux) + if !ok { + continue + } + fn(subMux) + } +} + +// updateRouteHandler builds the single mux handler that is a chain of the middleware +// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this +// point, no other middlewares can be registered on this Mux's stack. But you can still +// compose additional middlewares via Group()'s or using a chained middleware handler. +func (mx *Mux) updateRouteHandler() { + mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) +} + +// methodNotAllowedHandler is a helper function to respond with a 405, +// method not allowed. +func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(405) + w.Write(nil) +} diff --git a/vendor/github.com/go-chi/chi/v5/tree.go b/vendor/github.com/go-chi/chi/v5/tree.go new file mode 100644 index 00000000..4189b522 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/tree.go @@ -0,0 +1,866 @@ +package chi + +// Radix tree implementation below is a based on the original work by +// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go +// (MIT licensed). It's been heavily modified for use as a HTTP routing tree. + +import ( + "fmt" + "net/http" + "regexp" + "sort" + "strconv" + "strings" +) + +type methodTyp uint + +const ( + mSTUB methodTyp = 1 << iota + mCONNECT + mDELETE + mGET + mHEAD + mOPTIONS + mPATCH + mPOST + mPUT + mTRACE +) + +var mALL = mCONNECT | mDELETE | mGET | mHEAD | + mOPTIONS | mPATCH | mPOST | mPUT | mTRACE + +var methodMap = map[string]methodTyp{ + http.MethodConnect: mCONNECT, + http.MethodDelete: mDELETE, + http.MethodGet: mGET, + http.MethodHead: mHEAD, + http.MethodOptions: mOPTIONS, + http.MethodPatch: mPATCH, + http.MethodPost: mPOST, + http.MethodPut: mPUT, + http.MethodTrace: mTRACE, +} + +// RegisterMethod adds support for custom HTTP method handlers, available +// via Router#Method and Router#MethodFunc +func RegisterMethod(method string) { + if method == "" { + return + } + method = strings.ToUpper(method) + if _, ok := methodMap[method]; ok { + return + } + n := len(methodMap) + if n > strconv.IntSize-2 { + panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) + } + mt := methodTyp(2 << n) + methodMap[method] = mt + mALL |= mt +} + +type nodeTyp uint8 + +const ( + ntStatic nodeTyp = iota // /home + ntRegexp // /{id:[0-9]+} + ntParam // /{user} + ntCatchAll // /api/v1/* +) + +type node struct { + // subroutes on the leaf node + subroutes Routes + + // regexp matcher for regexp nodes + rex *regexp.Regexp + + // HTTP handler endpoints on the leaf node + endpoints endpoints + + // prefix is the common prefix we ignore + prefix string + + // child nodes should be stored in-order for iteration, + // in groups of the node type. + children [ntCatchAll + 1]nodes + + // first byte of the child prefix + tail byte + + // node type: static, regexp, param, catchAll + typ nodeTyp + + // first byte of the prefix + label byte +} + +// endpoints is a mapping of http method constants to handlers +// for a given route. +type endpoints map[methodTyp]*endpoint + +type endpoint struct { + // endpoint handler + handler http.Handler + + // pattern is the routing pattern for handler nodes + pattern string + + // parameter keys recorded on handler nodes + paramKeys []string +} + +func (s endpoints) Value(method methodTyp) *endpoint { + mh, ok := s[method] + if !ok { + mh = &endpoint{} + s[method] = mh + } + return mh +} + +func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node { + var parent *node + search := pattern + + for { + // Handle key exhaustion + if len(search) == 0 { + // Insert or update the node's leaf handler + n.setEndpoint(method, handler, pattern) + return n + } + + // We're going to be searching for a wild node next, + // in this case, we need to get the tail + var label = search[0] + var segTail byte + var segEndIdx int + var segTyp nodeTyp + var segRexpat string + if label == '{' || label == '*' { + segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) + } + + var prefix string + if segTyp == ntRegexp { + prefix = segRexpat + } + + // Look for the edge to attach to + parent = n + n = n.getEdge(segTyp, label, segTail, prefix) + + // No edge, create one + if n == nil { + child := &node{label: label, tail: segTail, prefix: search} + hn := parent.addChild(child, search) + hn.setEndpoint(method, handler, pattern) + + return hn + } + + // Found an edge to match the pattern + + if n.typ > ntStatic { + // We found a param node, trim the param from the search path and continue. + // This param/wild pattern segment would already be on the tree from a previous + // call to addChild when creating a new node. + search = search[segEndIdx:] + continue + } + + // Static nodes fall below here. + // Determine longest prefix of the search key on match. + commonPrefix := longestPrefix(search, n.prefix) + if commonPrefix == len(n.prefix) { + // the common prefix is as long as the current node's prefix we're attempting to insert. + // keep the search going. + search = search[commonPrefix:] + continue + } + + // Split the node + child := &node{ + typ: ntStatic, + prefix: search[:commonPrefix], + } + parent.replaceChild(search[0], segTail, child) + + // Restore the existing node + n.label = n.prefix[commonPrefix] + n.prefix = n.prefix[commonPrefix:] + child.addChild(n, n.prefix) + + // If the new key is a subset, set the method/handler on this node and finish. + search = search[commonPrefix:] + if len(search) == 0 { + child.setEndpoint(method, handler, pattern) + return child + } + + // Create a new edge for the node + subchild := &node{ + typ: ntStatic, + label: search[0], + prefix: search, + } + hn := child.addChild(subchild, search) + hn.setEndpoint(method, handler, pattern) + return hn + } +} + +// addChild appends the new `child` node to the tree using the `pattern` as the trie key. +// For a URL router like chi's, we split the static, param, regexp and wildcard segments +// into different nodes. In addition, addChild will recursively call itself until every +// pattern segment is added to the url pattern tree as individual nodes, depending on type. +func (n *node) addChild(child *node, prefix string) *node { + search := prefix + + // handler leaf node added to the tree is the child. + // this may be overridden later down the flow + hn := child + + // Parse next segment + segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) + + // Add child depending on next up segment + switch segTyp { + + case ntStatic: + // Search prefix is all static (that is, has no params in path) + // noop + + default: + // Search prefix contains a param, regexp or wildcard + + if segTyp == ntRegexp { + rex, err := regexp.Compile(segRexpat) + if err != nil { + panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat)) + } + child.prefix = segRexpat + child.rex = rex + } + + if segStartIdx == 0 { + // Route starts with a param + child.typ = segTyp + + if segTyp == ntCatchAll { + segStartIdx = -1 + } else { + segStartIdx = segEndIdx + } + if segStartIdx < 0 { + segStartIdx = len(search) + } + child.tail = segTail // for params, we set the tail + + if segStartIdx != len(search) { + // add static edge for the remaining part, split the end. + // its not possible to have adjacent param nodes, so its certainly + // going to be a static node next. + + search = search[segStartIdx:] // advance search position + + nn := &node{ + typ: ntStatic, + label: search[0], + prefix: search, + } + hn = child.addChild(nn, search) + } + + } else if segStartIdx > 0 { + // Route has some param + + // starts with a static segment + child.typ = ntStatic + child.prefix = search[:segStartIdx] + child.rex = nil + + // add the param edge node + search = search[segStartIdx:] + + nn := &node{ + typ: segTyp, + label: search[0], + tail: segTail, + } + hn = child.addChild(nn, search) + + } + } + + n.children[child.typ] = append(n.children[child.typ], child) + n.children[child.typ].Sort() + return hn +} + +func (n *node) replaceChild(label, tail byte, child *node) { + for i := 0; i < len(n.children[child.typ]); i++ { + if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail { + n.children[child.typ][i] = child + n.children[child.typ][i].label = label + n.children[child.typ][i].tail = tail + return + } + } + panic("chi: replacing missing child") +} + +func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { + nds := n.children[ntyp] + for i := 0; i < len(nds); i++ { + if nds[i].label == label && nds[i].tail == tail { + if ntyp == ntRegexp && nds[i].prefix != prefix { + continue + } + return nds[i] + } + } + return nil +} + +func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) { + // Set the handler for the method type on the node + if n.endpoints == nil { + n.endpoints = make(endpoints) + } + + paramKeys := patParamKeys(pattern) + + if method&mSTUB == mSTUB { + n.endpoints.Value(mSTUB).handler = handler + } + if method&mALL == mALL { + h := n.endpoints.Value(mALL) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + for _, m := range methodMap { + h := n.endpoints.Value(m) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + } + } else { + h := n.endpoints.Value(method) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + } +} + +func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) { + // Reset the context routing pattern and params + rctx.routePattern = "" + rctx.routeParams.Keys = rctx.routeParams.Keys[:0] + rctx.routeParams.Values = rctx.routeParams.Values[:0] + + // Find the routing handlers for the path + rn := n.findRoute(rctx, method, path) + if rn == nil { + return nil, nil, nil + } + + // Record the routing params in the request lifecycle + rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...) + rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...) + + // Record the routing pattern in the request lifecycle + if rn.endpoints[method].pattern != "" { + rctx.routePattern = rn.endpoints[method].pattern + rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern) + } + + return rn, rn.endpoints, rn.endpoints[method].handler +} + +// Recursive edge traversal by checking all nodeTyp groups along the way. +// It's like searching through a multi-dimensional radix trie. +func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { + nn := n + search := path + + for t, nds := range nn.children { + ntyp := nodeTyp(t) + if len(nds) == 0 { + continue + } + + var xn *node + xsearch := search + + var label byte + if search != "" { + label = search[0] + } + + switch ntyp { + case ntStatic: + xn = nds.findEdge(label) + if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) { + continue + } + xsearch = xsearch[len(xn.prefix):] + + case ntParam, ntRegexp: + // short-circuit and return no matching route for empty param values + if xsearch == "" { + continue + } + + // serially loop through each node grouped by the tail delimiter + for idx := 0; idx < len(nds); idx++ { + xn = nds[idx] + + // label for param nodes is the delimiter byte + p := strings.IndexByte(xsearch, xn.tail) + + if p < 0 { + if xn.tail == '/' { + p = len(xsearch) + } else { + continue + } + } else if ntyp == ntRegexp && p == 0 { + continue + } + + if ntyp == ntRegexp && xn.rex != nil { + if !xn.rex.MatchString(xsearch[:p]) { + continue + } + } else if strings.IndexByte(xsearch[:p], '/') != -1 { + // avoid a match across path segments + continue + } + + prevlen := len(rctx.routeParams.Values) + rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) + xsearch = xsearch[p:] + + if len(xsearch) == 0 { + if xn.isLeaf() { + h := xn.endpoints[method] + if h != nil && h.handler != nil { + rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + return xn + } + + // flag that the routing context found a route, but not a corresponding + // supported method + rctx.methodNotAllowed = true + } + } + + // recursively find the next node on this branch + fin := xn.findRoute(rctx, method, xsearch) + if fin != nil { + return fin + } + + // not found on this branch, reset vars + rctx.routeParams.Values = rctx.routeParams.Values[:prevlen] + xsearch = search + } + + rctx.routeParams.Values = append(rctx.routeParams.Values, "") + + default: + // catch-all nodes + rctx.routeParams.Values = append(rctx.routeParams.Values, search) + xn = nds[0] + xsearch = "" + } + + if xn == nil { + continue + } + + // did we find it yet? + if len(xsearch) == 0 { + if xn.isLeaf() { + h := xn.endpoints[method] + if h != nil && h.handler != nil { + rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + return xn + } + + // flag that the routing context found a route, but not a corresponding + // supported method + rctx.methodNotAllowed = true + } + } + + // recursively find the next node.. + fin := xn.findRoute(rctx, method, xsearch) + if fin != nil { + return fin + } + + // Did not find final handler, let's remove the param here if it was set + if xn.typ > ntStatic { + if len(rctx.routeParams.Values) > 0 { + rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1] + } + } + + } + + return nil +} + +func (n *node) findEdge(ntyp nodeTyp, label byte) *node { + nds := n.children[ntyp] + num := len(nds) + idx := 0 + + switch ntyp { + case ntStatic, ntParam, ntRegexp: + i, j := 0, num-1 + for i <= j { + idx = i + (j-i)/2 + if label > nds[idx].label { + i = idx + 1 + } else if label < nds[idx].label { + j = idx - 1 + } else { + i = num // breaks cond + } + } + if nds[idx].label != label { + return nil + } + return nds[idx] + + default: // catch all + return nds[idx] + } +} + +func (n *node) isLeaf() bool { + return n.endpoints != nil +} + +func (n *node) findPattern(pattern string) bool { + nn := n + for _, nds := range nn.children { + if len(nds) == 0 { + continue + } + + n = nn.findEdge(nds[0].typ, pattern[0]) + if n == nil { + continue + } + + var idx int + var xpattern string + + switch n.typ { + case ntStatic: + idx = longestPrefix(pattern, n.prefix) + if idx < len(n.prefix) { + continue + } + + case ntParam, ntRegexp: + idx = strings.IndexByte(pattern, '}') + 1 + + case ntCatchAll: + idx = longestPrefix(pattern, "*") + + default: + panic("chi: unknown node type") + } + + xpattern = pattern[idx:] + if len(xpattern) == 0 { + return true + } + + return n.findPattern(xpattern) + } + return false +} + +func (n *node) routes() []Route { + rts := []Route{} + + n.walk(func(eps endpoints, subroutes Routes) bool { + if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil { + return false + } + + // Group methodHandlers by unique patterns + pats := make(map[string]endpoints) + + for mt, h := range eps { + if h.pattern == "" { + continue + } + p, ok := pats[h.pattern] + if !ok { + p = endpoints{} + pats[h.pattern] = p + } + p[mt] = h + } + + for p, mh := range pats { + hs := make(map[string]http.Handler) + if mh[mALL] != nil && mh[mALL].handler != nil { + hs["*"] = mh[mALL].handler + } + + for mt, h := range mh { + if h.handler == nil { + continue + } + m := methodTypString(mt) + if m == "" { + continue + } + hs[m] = h.handler + } + + rt := Route{subroutes, hs, p} + rts = append(rts, rt) + } + + return false + }) + + return rts +} + +func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool { + // Visit the leaf values if any + if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) { + return true + } + + // Recurse on the children + for _, ns := range n.children { + for _, cn := range ns { + if cn.walk(fn) { + return true + } + } + } + return false +} + +// patNextSegment returns the next segment details from a pattern: +// node type, param key, regexp string, param tail byte, param starting index, param ending index +func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { + ps := strings.Index(pattern, "{") + ws := strings.Index(pattern, "*") + + if ps < 0 && ws < 0 { + return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing + } + + // Sanity check + if ps >= 0 && ws >= 0 && ws < ps { + panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'") + } + + var tail byte = '/' // Default endpoint tail to / byte + + if ps >= 0 { + // Param/Regexp pattern is next + nt := ntParam + + // Read to closing } taking into account opens and closes in curl count (cc) + cc := 0 + pe := ps + for i, c := range pattern[ps:] { + if c == '{' { + cc++ + } else if c == '}' { + cc-- + if cc == 0 { + pe = ps + i + break + } + } + } + if pe == ps { + panic("chi: route param closing delimiter '}' is missing") + } + + key := pattern[ps+1 : pe] + pe++ // set end to next position + + if pe < len(pattern) { + tail = pattern[pe] + } + + var rexpat string + if idx := strings.Index(key, ":"); idx >= 0 { + nt = ntRegexp + rexpat = key[idx+1:] + key = key[:idx] + } + + if len(rexpat) > 0 { + if rexpat[0] != '^' { + rexpat = "^" + rexpat + } + if rexpat[len(rexpat)-1] != '$' { + rexpat += "$" + } + } + + return nt, key, rexpat, tail, ps, pe + } + + // Wildcard pattern as finale + if ws < len(pattern)-1 { + panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead") + } + return ntCatchAll, "*", "", 0, ws, len(pattern) +} + +func patParamKeys(pattern string) []string { + pat := pattern + paramKeys := []string{} + for { + ptyp, paramKey, _, _, _, e := patNextSegment(pat) + if ptyp == ntStatic { + return paramKeys + } + for i := 0; i < len(paramKeys); i++ { + if paramKeys[i] == paramKey { + panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)) + } + } + paramKeys = append(paramKeys, paramKey) + pat = pat[e:] + } +} + +// longestPrefix finds the length of the shared prefix +// of two strings +func longestPrefix(k1, k2 string) int { + max := len(k1) + if l := len(k2); l < max { + max = l + } + var i int + for i = 0; i < max; i++ { + if k1[i] != k2[i] { + break + } + } + return i +} + +func methodTypString(method methodTyp) string { + for s, t := range methodMap { + if method == t { + return s + } + } + return "" +} + +type nodes []*node + +// Sort the list of nodes by label +func (ns nodes) Sort() { sort.Sort(ns); ns.tailSort() } +func (ns nodes) Len() int { return len(ns) } +func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] } +func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label } + +// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes. +// The list order determines the traversal order. +func (ns nodes) tailSort() { + for i := len(ns) - 1; i >= 0; i-- { + if ns[i].typ > ntStatic && ns[i].tail == '/' { + ns.Swap(i, len(ns)-1) + return + } + } +} + +func (ns nodes) findEdge(label byte) *node { + num := len(ns) + idx := 0 + i, j := 0, num-1 + for i <= j { + idx = i + (j-i)/2 + if label > ns[idx].label { + i = idx + 1 + } else if label < ns[idx].label { + j = idx - 1 + } else { + i = num // breaks cond + } + } + if ns[idx].label != label { + return nil + } + return ns[idx] +} + +// Route describes the details of a routing handler. +// Handlers map key is an HTTP method +type Route struct { + SubRoutes Routes + Handlers map[string]http.Handler + Pattern string +} + +// WalkFunc is the type of the function called for each method and route visited by Walk. +type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error + +// Walk walks any router tree that implements Routes interface. +func Walk(r Routes, walkFn WalkFunc) error { + return walk(r, walkFn, "") +} + +func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error { + for _, route := range r.Routes() { + mws := make([]func(http.Handler) http.Handler, len(parentMw)) + copy(mws, parentMw) + mws = append(mws, r.Middlewares()...) + + if route.SubRoutes != nil { + if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil { + return err + } + continue + } + + for method, handler := range route.Handlers { + if method == "*" { + // Ignore a "catchAll" method, since we pass down all the specific methods for each route. + continue + } + + fullRoute := parentRoute + route.Pattern + fullRoute = strings.Replace(fullRoute, "/*/", "/", -1) + + if chain, ok := handler.(*ChainHandler); ok { + if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil { + return err + } + } else { + if err := walkFn(method, fullRoute, handler, mws...); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/vendor/github.com/go-jose/go-jose/v3/.gitignore b/vendor/github.com/go-jose/go-jose/v3/.gitignore new file mode 100644 index 00000000..eb29ebae --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/.gitignore @@ -0,0 +1,2 @@ +jose-util/jose-util +jose-util.t.err \ No newline at end of file diff --git a/vendor/github.com/go-jose/go-jose/v3/.golangci.yml b/vendor/github.com/go-jose/go-jose/v3/.golangci.yml new file mode 100644 index 00000000..2a577a8f --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/.golangci.yml @@ -0,0 +1,53 @@ +# https://github.com/golangci/golangci-lint + +run: + skip-files: + - doc_test.go + modules-download-mode: readonly + +linters: + enable-all: true + disable: + - gochecknoglobals + - goconst + - lll + - maligned + - nakedret + - scopelint + - unparam + - funlen # added in 1.18 (requires go-jose changes before it can be enabled) + +linters-settings: + gocyclo: + min-complexity: 35 + +issues: + exclude-rules: + - text: "don't use ALL_CAPS in Go names" + linters: + - golint + - text: "hardcoded credentials" + linters: + - gosec + - text: "weak cryptographic primitive" + linters: + - gosec + - path: json/ + linters: + - dupl + - errcheck + - gocritic + - gocyclo + - golint + - govet + - ineffassign + - staticcheck + - structcheck + - stylecheck + - unused + - path: _test\.go + linters: + - scopelint + - path: jwk.go + linters: + - gocyclo diff --git a/vendor/github.com/go-jose/go-jose/v3/.travis.yml b/vendor/github.com/go-jose/go-jose/v3/.travis.yml new file mode 100644 index 00000000..48de631b --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/.travis.yml @@ -0,0 +1,33 @@ +language: go + +matrix: + fast_finish: true + allow_failures: + - go: tip + +go: + - "1.13.x" + - "1.14.x" + - tip + +before_script: + - export PATH=$HOME/.local/bin:$PATH + +before_install: + - go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0 + - pip install cram --user + +script: + - go test -v -covermode=count -coverprofile=profile.cov . + - go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner + - go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher + - go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt + - go test -v ./json # no coverage for forked encoding/json package + - golangci-lint run + - cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util + - cd .. + +after_success: + - gocovmerge *.cov */*.cov > merged.coverprofile + - goveralls -coverprofile merged.coverprofile -service=travis-ci diff --git a/vendor/github.com/go-jose/go-jose/v3/BUG-BOUNTY.md b/vendor/github.com/go-jose/go-jose/v3/BUG-BOUNTY.md new file mode 100644 index 00000000..3305db0f --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/BUG-BOUNTY.md @@ -0,0 +1,10 @@ +Serious about security +====================== + +Square recognizes the important contributions the security research community +can make. We therefore encourage reporting security issues with the code +contained in this repository. + +If you believe you have discovered a security vulnerability, please follow the +guidelines at . + diff --git a/vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md b/vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md new file mode 100644 index 00000000..b63e1f8f --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +If you would like to contribute code to go-jose you can do so through GitHub by +forking the repository and sending a pull request. + +When submitting code, please make every effort to follow existing conventions +and style in order to keep the code as readable as possible. Please also make +sure all tests pass by running `go test`, and format your code with `go fmt`. +We also recommend using `golint` and `errcheck`. + +Before your code can be accepted into the project you must also sign the +Individual Contributor License Agreement. We use [cla-assistant.io][1] and you +will be prompted to sign once a pull request is opened. + +[1]: https://cla-assistant.io/ diff --git a/vendor/github.com/go-jose/go-jose/v3/LICENSE b/vendor/github.com/go-jose/go-jose/v3/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/go-jose/go-jose/v3/README.md b/vendor/github.com/go-jose/go-jose/v3/README.md new file mode 100644 index 00000000..b90c7e5c --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/README.md @@ -0,0 +1,122 @@ +# Go JOSE + +[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2) +[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt) +[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE) +[![build](https://travis-ci.org/go-jose/go-jose.svg?branch=master)](https://travis-ci.org/go-jose/go-jose) +[![coverage](https://coveralls.io/repos/github/go-jose/go-jose/badge.svg?branch=master)](https://coveralls.io/r/go-jose/go-jose) + +Package jose aims to provide an implementation of the Javascript Object Signing +and Encryption set of standards. This includes support for JSON Web Encryption, +JSON Web Signature, and JSON Web Token standards. + +**Disclaimer**: This library contains encryption software that is subject to +the U.S. Export Administration Regulations. You may not export, re-export, +transfer or download this code or any part of it in violation of any United +States law, directive or regulation. In particular this software may not be +exported or re-exported in any form or on any media to Iran, North Sudan, +Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any +US maintained blocked list. + +## Overview + +The implementation follows the +[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516), +[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and +[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications. +Tables of supported algorithms are shown below. The library supports both +the compact and JWS/JWE JSON Serialization formats, and has optional support for +multiple recipients. It also comes with a small command-line utility +([`jose-util`](https://github.com/go-jose/go-jose/tree/master/jose-util)) +for dealing with JOSE messages in a shell. + +**Note**: We use a forked version of the `encoding/json` package from the Go +standard library which uses case-sensitive matching for member names (instead +of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)). +This is to avoid differences in interpretation of messages between go-jose and +libraries in other languages. + +### Versions + +[Version 2](https://gopkg.in/go-jose/go-jose.v2) +([branch](https://github.com/go-jose/go-jose/tree/v2), +[doc](https://godoc.org/gopkg.in/go-jose/go-jose.v2)) is the current stable version: + + import "gopkg.in/go-jose/go-jose.v2" + +[Version 3](https://github.com/go-jose/go-jose) +([branch](https://github.com/go-jose/go-jose/tree/master), +[doc](https://godoc.org/github.com/go-jose/go-jose)) is the under development/unstable version (not released yet): + + import "github.com/go-jose/go-jose/v3" + +All new feature development takes place on the `master` branch, which we are +preparing to release as version 3 soon. Version 2 will continue to receive +critical bug and security fixes. Note that starting with version 3 we are +using Go modules for versioning instead of `gopkg.in` as before. Version 3 also will require Go version 1.13 or higher. + +Version 1 (on the `v1` branch) is frozen and not supported anymore. + +### Supported algorithms + +See below for a table of supported algorithms. Algorithm identifiers match +the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518) +standard where possible. The Godoc reference has a list of constants. + + Key encryption | Algorithm identifier(s) + :------------------------- | :------------------------------ + RSA-PKCS#1v1.5 | RSA1_5 + RSA-OAEP | RSA-OAEP, RSA-OAEP-256 + AES key wrap | A128KW, A192KW, A256KW + AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW + ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW + ECDH-ES (direct) | ECDH-ES1 + Direct encryption | dir1 + +1. Not supported in multi-recipient mode + + Signing / MAC | Algorithm identifier(s) + :------------------------- | :------------------------------ + RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 + RSASSA-PSS | PS256, PS384, PS512 + HMAC | HS256, HS384, HS512 + ECDSA | ES256, ES384, ES512 + Ed25519 | EdDSA2 + +2. Only available in version 2 of the package + + Content encryption | Algorithm identifier(s) + :------------------------- | :------------------------------ + AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 + AES-GCM | A128GCM, A192GCM, A256GCM + + Compression | Algorithm identifiers(s) + :------------------------- | ------------------------------- + DEFLATE (RFC 1951) | DEF + +### Supported key types + +See below for a table of supported key types. These are understood by the +library, and can be passed to corresponding functions such as `NewEncrypter` or +`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which +allows attaching a key id. + + Algorithm(s) | Corresponding types + :------------------------- | ------------------------------- + RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey) + ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey) + EdDSA1 | [ed25519.PublicKey](https://godoc.org/pkg/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/pkg/crypto/ed25519#PrivateKey) + AES, HMAC | []byte + +1. Only available in version 2 or later of the package + +## Examples + +[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2) +[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt) + +Examples can be found in the Godoc +reference for this package. The +[`jose-util`](https://github.com/go-jose/go-jose/tree/master/jose-util) +subdirectory also contains a small command-line utility which might be useful +as an example as well. diff --git a/vendor/github.com/go-jose/go-jose/v3/asymmetric.go b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go new file mode 100644 index 00000000..78abc326 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go @@ -0,0 +1,592 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "crypto" + "crypto/aes" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "errors" + "fmt" + "math/big" + + josecipher "github.com/go-jose/go-jose/v3/cipher" + "github.com/go-jose/go-jose/v3/json" +) + +// A generic RSA-based encrypter/verifier +type rsaEncrypterVerifier struct { + publicKey *rsa.PublicKey +} + +// A generic RSA-based decrypter/signer +type rsaDecrypterSigner struct { + privateKey *rsa.PrivateKey +} + +// A generic EC-based encrypter/verifier +type ecEncrypterVerifier struct { + publicKey *ecdsa.PublicKey +} + +type edEncrypterVerifier struct { + publicKey ed25519.PublicKey +} + +// A key generator for ECDH-ES +type ecKeyGenerator struct { + size int + algID string + publicKey *ecdsa.PublicKey +} + +// A generic EC-based decrypter/signer +type ecDecrypterSigner struct { + privateKey *ecdsa.PrivateKey +} + +type edDecrypterSigner struct { + privateKey ed25519.PrivateKey +} + +// newRSARecipient creates recipientKeyInfo based on the given key. +func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch keyAlg { + case RSA1_5, RSA_OAEP, RSA_OAEP_256: + default: + return recipientKeyInfo{}, ErrUnsupportedAlgorithm + } + + if publicKey == nil { + return recipientKeyInfo{}, errors.New("invalid public key") + } + + return recipientKeyInfo{ + keyAlg: keyAlg, + keyEncrypter: &rsaEncrypterVerifier{ + publicKey: publicKey, + }, + }, nil +} + +// newRSASigner creates a recipientSigInfo based on the given key. +func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch sigAlg { + case RS256, RS384, RS512, PS256, PS384, PS512: + default: + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &rsaDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) { + if sigAlg != EdDSA { + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &edDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +// newECDHRecipient creates recipientKeyInfo based on the given key. +func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch keyAlg { + case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: + default: + return recipientKeyInfo{}, ErrUnsupportedAlgorithm + } + + if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { + return recipientKeyInfo{}, errors.New("invalid public key") + } + + return recipientKeyInfo{ + keyAlg: keyAlg, + keyEncrypter: &ecEncrypterVerifier{ + publicKey: publicKey, + }, + }, nil +} + +// newECDSASigner creates a recipientSigInfo based on the given key. +func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) { + // Verify that key management algorithm is supported by this encrypter + switch sigAlg { + case ES256, ES384, ES512: + default: + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + if privateKey == nil { + return recipientSigInfo{}, errors.New("invalid private key") + } + + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), + signer: &ecDecrypterSigner{ + privateKey: privateKey, + }, + }, nil +} + +// Encrypt the given payload and update the object. +func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { + encryptedKey, err := ctx.encrypt(cek, alg) + if err != nil { + return recipientInfo{}, err + } + + return recipientInfo{ + encryptedKey: encryptedKey, + header: &rawHeader{}, + }, nil +} + +// Encrypt the given payload. Based on the key encryption algorithm, +// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). +func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) { + switch alg { + case RSA1_5: + return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek) + case RSA_OAEP: + return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{}) + case RSA_OAEP_256: + return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{}) + } + + return nil, ErrUnsupportedAlgorithm +} + +// Decrypt the given payload and return the content encryption key. +func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator) +} + +// Decrypt the given payload. Based on the key encryption algorithm, +// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). +func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) { + // Note: The random reader on decrypt operations is only used for blinding, + // so stubbing is meanlingless (hence the direct use of rand.Reader). + switch alg { + case RSA1_5: + defer func() { + // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload + // because of an index out of bounds error, which we want to ignore. + // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover() + // only exists for preventing crashes with unpatched versions. + // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k + // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33 + _ = recover() + }() + + // Perform some input validation. + keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8 + if keyBytes != len(jek) { + // Input size is incorrect, the encrypted payload should always match + // the size of the public modulus (e.g. using a 2048 bit key will + // produce 256 bytes of output). Reject this since it's invalid input. + return nil, ErrCryptoFailure + } + + cek, _, err := generator.genKey() + if err != nil { + return nil, ErrCryptoFailure + } + + // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to + // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing + // the Million Message Attack on Cryptographic Message Syntax". We are + // therefore deliberately ignoring errors here. + _ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek) + + return cek, nil + case RSA_OAEP: + // Use rand.Reader for RSA blinding + return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{}) + case RSA_OAEP_256: + // Use rand.Reader for RSA blinding + return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{}) + } + + return nil, ErrUnsupportedAlgorithm +} + +// Sign the given payload +func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + var hash crypto.Hash + + switch alg { + case RS256, PS256: + hash = crypto.SHA256 + case RS384, PS384: + hash = crypto.SHA384 + case RS512, PS512: + hash = crypto.SHA512 + default: + return Signature{}, ErrUnsupportedAlgorithm + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + var out []byte + var err error + + switch alg { + case RS256, RS384, RS512: + out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed) + case PS256, PS384, PS512: + out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + }) + } + + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: out, + protected: &rawHeader{}, + }, nil +} + +// Verify the given payload +func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + var hash crypto.Hash + + switch alg { + case RS256, PS256: + hash = crypto.SHA256 + case RS384, PS384: + hash = crypto.SHA384 + case RS512, PS512: + hash = crypto.SHA512 + default: + return ErrUnsupportedAlgorithm + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + switch alg { + case RS256, RS384, RS512: + return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature) + case PS256, PS384, PS512: + return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil) + } + + return ErrUnsupportedAlgorithm +} + +// Encrypt the given payload and update the object. +func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { + switch alg { + case ECDH_ES: + // ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key. + return recipientInfo{ + header: &rawHeader{}, + }, nil + case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: + default: + return recipientInfo{}, ErrUnsupportedAlgorithm + } + + generator := ecKeyGenerator{ + algID: string(alg), + publicKey: ctx.publicKey, + } + + switch alg { + case ECDH_ES_A128KW: + generator.size = 16 + case ECDH_ES_A192KW: + generator.size = 24 + case ECDH_ES_A256KW: + generator.size = 32 + } + + kek, header, err := generator.genKey() + if err != nil { + return recipientInfo{}, err + } + + block, err := aes.NewCipher(kek) + if err != nil { + return recipientInfo{}, err + } + + jek, err := josecipher.KeyWrap(block, cek) + if err != nil { + return recipientInfo{}, err + } + + return recipientInfo{ + encryptedKey: jek, + header: &header, + }, nil +} + +// Get key size for EC key generator +func (ctx ecKeyGenerator) keySize() int { + return ctx.size +} + +// Get a content encryption key for ECDH-ES +func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { + priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader) + if err != nil { + return nil, rawHeader{}, err + } + + out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size) + + b, err := json.Marshal(&JSONWebKey{ + Key: &priv.PublicKey, + }) + if err != nil { + return nil, nil, err + } + + headers := rawHeader{ + headerEPK: makeRawMessage(b), + } + + return out, headers, nil +} + +// Decrypt the given payload and return the content encryption key. +func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { + epk, err := headers.getEPK() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid epk header") + } + if epk == nil { + return nil, errors.New("go-jose/go-jose: missing epk header") + } + + publicKey, ok := epk.Key.(*ecdsa.PublicKey) + if publicKey == nil || !ok { + return nil, errors.New("go-jose/go-jose: invalid epk header") + } + + if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { + return nil, errors.New("go-jose/go-jose: invalid public key in epk header") + } + + apuData, err := headers.getAPU() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid apu header") + } + apvData, err := headers.getAPV() + if err != nil { + return nil, errors.New("go-jose/go-jose: invalid apv header") + } + + deriveKey := func(algID string, size int) []byte { + return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size) + } + + var keySize int + + algorithm := headers.getAlgorithm() + switch algorithm { + case ECDH_ES: + // ECDH-ES uses direct key agreement, no key unwrapping necessary. + return deriveKey(string(headers.getEncryption()), generator.keySize()), nil + case ECDH_ES_A128KW: + keySize = 16 + case ECDH_ES_A192KW: + keySize = 24 + case ECDH_ES_A256KW: + keySize = 32 + default: + return nil, ErrUnsupportedAlgorithm + } + + key := deriveKey(string(algorithm), keySize) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + return josecipher.KeyUnwrap(block, recipient.encryptedKey) +} + +func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + if alg != EdDSA { + return Signature{}, ErrUnsupportedAlgorithm + } + + sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0)) + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: sig, + protected: &rawHeader{}, + }, nil +} + +func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + if alg != EdDSA { + return ErrUnsupportedAlgorithm + } + ok := ed25519.Verify(ctx.publicKey, payload, signature) + if !ok { + return errors.New("go-jose/go-jose: ed25519 signature failed to verify") + } + return nil +} + +// Sign the given payload +func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + var expectedBitSize int + var hash crypto.Hash + + switch alg { + case ES256: + expectedBitSize = 256 + hash = crypto.SHA256 + case ES384: + expectedBitSize = 384 + hash = crypto.SHA384 + case ES512: + expectedBitSize = 521 + hash = crypto.SHA512 + } + + curveBits := ctx.privateKey.Curve.Params().BitSize + if expectedBitSize != curveBits { + return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits) + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed) + if err != nil { + return Signature{}, err + } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes++ + } + + // We serialize the outputs (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + + return Signature{ + Signature: out, + protected: &rawHeader{}, + }, nil +} + +// Verify the given payload +func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + var keySize int + var hash crypto.Hash + + switch alg { + case ES256: + keySize = 32 + hash = crypto.SHA256 + case ES384: + keySize = 48 + hash = crypto.SHA384 + case ES512: + keySize = 66 + hash = crypto.SHA512 + default: + return ErrUnsupportedAlgorithm + } + + if len(signature) != 2*keySize { + return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize) + } + + hasher := hash.New() + + // According to documentation, Write() on hash never fails + _, _ = hasher.Write(payload) + hashed := hasher.Sum(nil) + + r := big.NewInt(0).SetBytes(signature[:keySize]) + s := big.NewInt(0).SetBytes(signature[keySize:]) + + match := ecdsa.Verify(ctx.publicKey, hashed, r, s) + if !match { + return errors.New("go-jose/go-jose: ecdsa signature failed to verify") + } + + return nil +} diff --git a/vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go new file mode 100644 index 00000000..af029cec --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go @@ -0,0 +1,196 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "bytes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" + "encoding/binary" + "errors" + "hash" +) + +const ( + nonceBytes = 16 +) + +// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC. +func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) { + keySize := len(key) / 2 + integrityKey := key[:keySize] + encryptionKey := key[keySize:] + + blockCipher, err := newBlockCipher(encryptionKey) + if err != nil { + return nil, err + } + + var hash func() hash.Hash + switch keySize { + case 16: + hash = sha256.New + case 24: + hash = sha512.New384 + case 32: + hash = sha512.New + } + + return &cbcAEAD{ + hash: hash, + blockCipher: blockCipher, + authtagBytes: keySize, + integrityKey: integrityKey, + }, nil +} + +// An AEAD based on CBC+HMAC +type cbcAEAD struct { + hash func() hash.Hash + authtagBytes int + integrityKey []byte + blockCipher cipher.Block +} + +func (ctx *cbcAEAD) NonceSize() int { + return nonceBytes +} + +func (ctx *cbcAEAD) Overhead() int { + // Maximum overhead is block size (for padding) plus auth tag length, where + // the length of the auth tag is equivalent to the key size. + return ctx.blockCipher.BlockSize() + ctx.authtagBytes +} + +// Seal encrypts and authenticates the plaintext. +func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte { + // Output buffer -- must take care not to mangle plaintext input. + ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)] + copy(ciphertext, plaintext) + ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize()) + + cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce) + + cbc.CryptBlocks(ciphertext, ciphertext) + authtag := ctx.computeAuthTag(data, nonce, ciphertext) + + ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag))) + copy(out, ciphertext) + copy(out[len(ciphertext):], authtag) + + return ret +} + +// Open decrypts and authenticates the ciphertext. +func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + if len(ciphertext) < ctx.authtagBytes { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)") + } + + offset := len(ciphertext) - ctx.authtagBytes + expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset]) + match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:]) + if match != 1 { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)") + } + + cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce) + + // Make copy of ciphertext buffer, don't want to modify in place + buffer := append([]byte{}, ciphertext[:offset]...) + + if len(buffer)%ctx.blockCipher.BlockSize() > 0 { + return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)") + } + + cbc.CryptBlocks(buffer, buffer) + + // Remove padding + plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize()) + if err != nil { + return nil, err + } + + ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext))) + copy(out, plaintext) + + return ret, nil +} + +// Compute an authentication tag +func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte { + buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8) + n := 0 + n += copy(buffer, aad) + n += copy(buffer[n:], nonce) + n += copy(buffer[n:], ciphertext) + binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8) + + // According to documentation, Write() on hash.Hash never fails. + hmac := hmac.New(ctx.hash, ctx.integrityKey) + _, _ = hmac.Write(buffer) + + return hmac.Sum(nil)[:ctx.authtagBytes] +} + +// resize ensures that the given slice has a capacity of at least n bytes. +// If the capacity of the slice is less than n, a new slice is allocated +// and the existing data will be copied. +func resize(in []byte, n uint64) (head, tail []byte) { + if uint64(cap(in)) >= n { + head = in[:n] + } else { + head = make([]byte, n) + copy(head, in) + } + + tail = head[len(in):] + return +} + +// Apply padding +func padBuffer(buffer []byte, blockSize int) []byte { + missing := blockSize - (len(buffer) % blockSize) + ret, out := resize(buffer, uint64(len(buffer))+uint64(missing)) + padding := bytes.Repeat([]byte{byte(missing)}, missing) + copy(out, padding) + return ret +} + +// Remove padding +func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) { + if len(buffer)%blockSize != 0 { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + last := buffer[len(buffer)-1] + count := int(last) + + if count == 0 || count > blockSize || count > len(buffer) { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + padding := bytes.Repeat([]byte{last}, count) + if !bytes.HasSuffix(buffer, padding) { + return nil, errors.New("go-jose/go-jose: invalid padding") + } + + return buffer[:len(buffer)-count], nil +} diff --git a/vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go b/vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go new file mode 100644 index 00000000..f62c3bdb --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go @@ -0,0 +1,75 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "crypto" + "encoding/binary" + "hash" + "io" +) + +type concatKDF struct { + z, info []byte + i uint32 + cache []byte + hasher hash.Hash +} + +// NewConcatKDF builds a KDF reader based on the given inputs. +func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader { + buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo))) + n := 0 + n += copy(buffer, algID) + n += copy(buffer[n:], ptyUInfo) + n += copy(buffer[n:], ptyVInfo) + n += copy(buffer[n:], supPubInfo) + copy(buffer[n:], supPrivInfo) + + hasher := hash.New() + + return &concatKDF{ + z: z, + info: buffer, + hasher: hasher, + cache: []byte{}, + i: 1, + } +} + +func (ctx *concatKDF) Read(out []byte) (int, error) { + copied := copy(out, ctx.cache) + ctx.cache = ctx.cache[copied:] + + for copied < len(out) { + ctx.hasher.Reset() + + // Write on a hash.Hash never fails + _ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i) + _, _ = ctx.hasher.Write(ctx.z) + _, _ = ctx.hasher.Write(ctx.info) + + hash := ctx.hasher.Sum(nil) + chunkCopied := copy(out[copied:], hash) + copied += chunkCopied + ctx.cache = hash[chunkCopied:] + + ctx.i++ + } + + return copied, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go b/vendor/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go new file mode 100644 index 00000000..093c6467 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go @@ -0,0 +1,86 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/binary" +) + +// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA. +// It is an error to call this function with a private/public key that are not on the same +// curve. Callers must ensure that the keys are valid before calling this function. Output +// size may be at most 1<<16 bytes (64 KiB). +func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte { + if size > 1<<16 { + panic("ECDH-ES output size too large, must be less than or equal to 1<<16") + } + + // algId, partyUInfo, partyVInfo inputs must be prefixed with the length + algID := lengthPrefixed([]byte(alg)) + ptyUInfo := lengthPrefixed(apuData) + ptyVInfo := lengthPrefixed(apvData) + + // suppPubInfo is the encoded length of the output size in bits + supPubInfo := make([]byte, 4) + binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8) + + if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) { + panic("public key not on same curve as private key") + } + + z, _ := priv.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes()) + zBytes := z.Bytes() + + // Note that calling z.Bytes() on a big.Int may strip leading zero bytes from + // the returned byte array. This can lead to a problem where zBytes will be + // shorter than expected which breaks the key derivation. Therefore we must pad + // to the full length of the expected coordinate here before calling the KDF. + octSize := dSize(priv.Curve) + if len(zBytes) != octSize { + zBytes = append(bytes.Repeat([]byte{0}, octSize-len(zBytes)), zBytes...) + } + + reader := NewConcatKDF(crypto.SHA256, zBytes, algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{}) + key := make([]byte, size) + + // Read on the KDF will never fail + _, _ = reader.Read(key) + + return key +} + +// dSize returns the size in octets for a coordinate on a elliptic curve. +func dSize(curve elliptic.Curve) int { + order := curve.Params().P + bitLen := order.BitLen() + size := bitLen / 8 + if bitLen%8 != 0 { + size++ + } + return size +} + +func lengthPrefixed(data []byte) []byte { + out := make([]byte, len(data)+4) + binary.BigEndian.PutUint32(out, uint32(len(data))) + copy(out[4:], data) + return out +} diff --git a/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go new file mode 100644 index 00000000..b9effbca --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go @@ -0,0 +1,109 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package josecipher + +import ( + "crypto/cipher" + "crypto/subtle" + "encoding/binary" + "errors" +) + +var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} + +// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher. +func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) { + if len(cek)%8 != 0 { + return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") + } + + n := len(cek) / 8 + r := make([][]byte, n) + + for i := range r { + r[i] = make([]byte, 8) + copy(r[i], cek[i*8:]) + } + + buffer := make([]byte, 16) + tBytes := make([]byte, 8) + copy(buffer, defaultIV) + + for t := 0; t < 6*n; t++ { + copy(buffer[8:], r[t%n]) + + block.Encrypt(buffer, buffer) + + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < 8; i++ { + buffer[i] ^= tBytes[i] + } + copy(r[t%n], buffer[8:]) + } + + out := make([]byte, (n+1)*8) + copy(out, buffer[:8]) + for i := range r { + copy(out[(i+1)*8:], r[i]) + } + + return out, nil +} + +// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher. +func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) { + if len(ciphertext)%8 != 0 { + return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") + } + + n := (len(ciphertext) / 8) - 1 + r := make([][]byte, n) + + for i := range r { + r[i] = make([]byte, 8) + copy(r[i], ciphertext[(i+1)*8:]) + } + + buffer := make([]byte, 16) + tBytes := make([]byte, 8) + copy(buffer[:8], ciphertext[:8]) + + for t := 6*n - 1; t >= 0; t-- { + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < 8; i++ { + buffer[i] ^= tBytes[i] + } + copy(buffer[8:], r[t%n]) + + block.Decrypt(buffer, buffer) + + copy(r[t%n], buffer[8:]) + } + + if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 { + return nil, errors.New("go-jose/go-jose: failed to unwrap key") + } + + out := make([]byte, n*8) + for i := range r { + copy(out[i*8:], r[i]) + } + + return out, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v3/crypter.go b/vendor/github.com/go-jose/go-jose/v3/crypter.go new file mode 100644 index 00000000..6901137e --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/crypter.go @@ -0,0 +1,544 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "crypto/ecdsa" + "crypto/rsa" + "errors" + "fmt" + "reflect" + + "github.com/go-jose/go-jose/v3/json" +) + +// Encrypter represents an encrypter which produces an encrypted JWE object. +type Encrypter interface { + Encrypt(plaintext []byte) (*JSONWebEncryption, error) + EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error) + Options() EncrypterOptions +} + +// A generic content cipher +type contentCipher interface { + keySize() int + encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error) + decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error) +} + +// A key generator (for generating/getting a CEK) +type keyGenerator interface { + keySize() int + genKey() ([]byte, rawHeader, error) +} + +// A generic key encrypter +type keyEncrypter interface { + encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key +} + +// A generic key decrypter +type keyDecrypter interface { + decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key +} + +// A generic encrypter based on the given key encrypter and content cipher. +type genericEncrypter struct { + contentAlg ContentEncryption + compressionAlg CompressionAlgorithm + cipher contentCipher + recipients []recipientKeyInfo + keyGenerator keyGenerator + extraHeaders map[HeaderKey]interface{} +} + +type recipientKeyInfo struct { + keyID string + keyAlg KeyAlgorithm + keyEncrypter keyEncrypter +} + +// EncrypterOptions represents options that can be set on new encrypters. +type EncrypterOptions struct { + Compression CompressionAlgorithm + + // Optional map of additional keys to be inserted into the protected header + // of a JWS object. Some specifications which make use of JWS like to insert + // additional values here. All values must be JSON-serializable. + ExtraHeaders map[HeaderKey]interface{} +} + +// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it +// if necessary. It returns itself and so can be used in a fluent style. +func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions { + if eo.ExtraHeaders == nil { + eo.ExtraHeaders = map[HeaderKey]interface{}{} + } + eo.ExtraHeaders[k] = v + return eo +} + +// WithContentType adds a content type ("cty") header and returns the updated +// EncrypterOptions. +func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions { + return eo.WithHeader(HeaderContentType, contentType) +} + +// WithType adds a type ("typ") header and returns the updated EncrypterOptions. +func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions { + return eo.WithHeader(HeaderType, typ) +} + +// Recipient represents an algorithm/key to encrypt messages to. +// +// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used +// on the password-based encryption algorithms PBES2-HS256+A128KW, +// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe +// default of 100000 will be used for the count and a 128-bit random salt will +// be generated. +type Recipient struct { + Algorithm KeyAlgorithm + Key interface{} + KeyID string + PBES2Count int + PBES2Salt []byte +} + +// NewEncrypter creates an appropriate encrypter based on the key type +func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) { + encrypter := &genericEncrypter{ + contentAlg: enc, + recipients: []recipientKeyInfo{}, + cipher: getContentCipher(enc), + } + if opts != nil { + encrypter.compressionAlg = opts.Compression + encrypter.extraHeaders = opts.ExtraHeaders + } + + if encrypter.cipher == nil { + return nil, ErrUnsupportedAlgorithm + } + + var keyID string + var rawKey interface{} + switch encryptionKey := rcpt.Key.(type) { + case JSONWebKey: + keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key + case *JSONWebKey: + keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key + case OpaqueKeyEncrypter: + keyID, rawKey = encryptionKey.KeyID(), encryptionKey + default: + rawKey = encryptionKey + } + + switch rcpt.Algorithm { + case DIRECT: + // Direct encryption mode must be treated differently + if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) { + return nil, ErrUnsupportedKeyType + } + if encrypter.cipher.keySize() != len(rawKey.([]byte)) { + return nil, ErrInvalidKeySize + } + encrypter.keyGenerator = staticKeyGenerator{ + key: rawKey.([]byte), + } + recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte)) + recipientInfo.keyID = keyID + if rcpt.KeyID != "" { + recipientInfo.keyID = rcpt.KeyID + } + encrypter.recipients = []recipientKeyInfo{recipientInfo} + return encrypter, nil + case ECDH_ES: + // ECDH-ES (w/o key wrapping) is similar to DIRECT mode + typeOf := reflect.TypeOf(rawKey) + if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) { + return nil, ErrUnsupportedKeyType + } + encrypter.keyGenerator = ecKeyGenerator{ + size: encrypter.cipher.keySize(), + algID: string(enc), + publicKey: rawKey.(*ecdsa.PublicKey), + } + recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey)) + recipientInfo.keyID = keyID + if rcpt.KeyID != "" { + recipientInfo.keyID = rcpt.KeyID + } + encrypter.recipients = []recipientKeyInfo{recipientInfo} + return encrypter, nil + default: + // Can just add a standard recipient + encrypter.keyGenerator = randomKeyGenerator{ + size: encrypter.cipher.keySize(), + } + err := encrypter.addRecipient(rcpt) + return encrypter, err + } +} + +// NewMultiEncrypter creates a multi-encrypter based on the given parameters +func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) { + cipher := getContentCipher(enc) + + if cipher == nil { + return nil, ErrUnsupportedAlgorithm + } + if len(rcpts) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty") + } + + encrypter := &genericEncrypter{ + contentAlg: enc, + recipients: []recipientKeyInfo{}, + cipher: cipher, + keyGenerator: randomKeyGenerator{ + size: cipher.keySize(), + }, + } + + if opts != nil { + encrypter.compressionAlg = opts.Compression + encrypter.extraHeaders = opts.ExtraHeaders + } + + for _, recipient := range rcpts { + err := encrypter.addRecipient(recipient) + if err != nil { + return nil, err + } + } + + return encrypter, nil +} + +func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) { + var recipientInfo recipientKeyInfo + + switch recipient.Algorithm { + case DIRECT, ECDH_ES: + return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm) + } + + recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key) + if recipient.KeyID != "" { + recipientInfo.keyID = recipient.KeyID + } + + switch recipient.Algorithm { + case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: + if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok { + sr.p2c = recipient.PBES2Count + sr.p2s = recipient.PBES2Salt + } + } + + if err == nil { + ctx.recipients = append(ctx.recipients, recipientInfo) + } + return err +} + +func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) { + switch encryptionKey := encryptionKey.(type) { + case *rsa.PublicKey: + return newRSARecipient(alg, encryptionKey) + case *ecdsa.PublicKey: + return newECDHRecipient(alg, encryptionKey) + case []byte: + return newSymmetricRecipient(alg, encryptionKey) + case string: + return newSymmetricRecipient(alg, []byte(encryptionKey)) + case *JSONWebKey: + recipient, err := makeJWERecipient(alg, encryptionKey.Key) + recipient.keyID = encryptionKey.KeyID + return recipient, err + } + if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok { + return newOpaqueKeyEncrypter(alg, encrypter) + } + return recipientKeyInfo{}, ErrUnsupportedKeyType +} + +// newDecrypter creates an appropriate decrypter based on the key type +func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) { + switch decryptionKey := decryptionKey.(type) { + case *rsa.PrivateKey: + return &rsaDecrypterSigner{ + privateKey: decryptionKey, + }, nil + case *ecdsa.PrivateKey: + return &ecDecrypterSigner{ + privateKey: decryptionKey, + }, nil + case []byte: + return &symmetricKeyCipher{ + key: decryptionKey, + }, nil + case string: + return &symmetricKeyCipher{ + key: []byte(decryptionKey), + }, nil + case JSONWebKey: + return newDecrypter(decryptionKey.Key) + case *JSONWebKey: + return newDecrypter(decryptionKey.Key) + } + if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok { + return &opaqueKeyDecrypter{decrypter: okd}, nil + } + return nil, ErrUnsupportedKeyType +} + +// Implementation of encrypt method producing a JWE object. +func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) { + return ctx.EncryptWithAuthData(plaintext, nil) +} + +// Implementation of encrypt method producing a JWE object. +func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) { + obj := &JSONWebEncryption{} + obj.aad = aad + + obj.protected = &rawHeader{} + err := obj.protected.set(headerEncryption, ctx.contentAlg) + if err != nil { + return nil, err + } + + obj.recipients = make([]recipientInfo, len(ctx.recipients)) + + if len(ctx.recipients) == 0 { + return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to") + } + + cek, headers, err := ctx.keyGenerator.genKey() + if err != nil { + return nil, err + } + + obj.protected.merge(&headers) + + for i, info := range ctx.recipients { + recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg) + if err != nil { + return nil, err + } + + err = recipient.header.set(headerAlgorithm, info.keyAlg) + if err != nil { + return nil, err + } + + if info.keyID != "" { + err = recipient.header.set(headerKeyID, info.keyID) + if err != nil { + return nil, err + } + } + obj.recipients[i] = recipient + } + + if len(ctx.recipients) == 1 { + // Move per-recipient headers into main protected header if there's + // only a single recipient. + obj.protected.merge(obj.recipients[0].header) + obj.recipients[0].header = nil + } + + if ctx.compressionAlg != NONE { + plaintext, err = compress(ctx.compressionAlg, plaintext) + if err != nil { + return nil, err + } + + err = obj.protected.set(headerCompression, ctx.compressionAlg) + if err != nil { + return nil, err + } + } + + for k, v := range ctx.extraHeaders { + b, err := json.Marshal(v) + if err != nil { + return nil, err + } + (*obj.protected)[k] = makeRawMessage(b) + } + + authData := obj.computeAuthData() + parts, err := ctx.cipher.encrypt(cek, authData, plaintext) + if err != nil { + return nil, err + } + + obj.iv = parts.iv + obj.ciphertext = parts.ciphertext + obj.tag = parts.tag + + return obj, nil +} + +func (ctx *genericEncrypter) Options() EncrypterOptions { + return EncrypterOptions{ + Compression: ctx.compressionAlg, + ExtraHeaders: ctx.extraHeaders, + } +} + +// Decrypt and validate the object and return the plaintext. Note that this +// function does not support multi-recipient, if you desire multi-recipient +// decryption use DecryptMulti instead. +func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) { + headers := obj.mergedHeaders(nil) + + if len(obj.recipients) > 1 { + return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one") + } + + critical, err := headers.getCritical() + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: invalid crit header") + } + + if len(critical) > 0 { + return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + } + + key := tryJWKS(decryptionKey, obj.Header) + decrypter, err := newDecrypter(key) + if err != nil { + return nil, err + } + + cipher := getContentCipher(headers.getEncryption()) + if cipher == nil { + return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption())) + } + + generator := randomKeyGenerator{ + size: cipher.keySize(), + } + + parts := &aeadParts{ + iv: obj.iv, + ciphertext: obj.ciphertext, + tag: obj.tag, + } + + authData := obj.computeAuthData() + + var plaintext []byte + recipient := obj.recipients[0] + recipientHeaders := obj.mergedHeaders(&recipient) + + cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) + if err == nil { + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + } + + if plaintext == nil { + return nil, ErrCryptoFailure + } + + // The "zip" header parameter may only be present in the protected header. + if comp := obj.protected.getCompression(); comp != "" { + plaintext, err = decompress(comp, plaintext) + } + + return plaintext, err +} + +// DecryptMulti decrypts and validates the object and returns the plaintexts, +// with support for multiple recipients. It returns the index of the recipient +// for which the decryption was successful, the merged headers for that recipient, +// and the plaintext. +func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) { + globalHeaders := obj.mergedHeaders(nil) + + critical, err := globalHeaders.getCritical() + if err != nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header") + } + + if len(critical) > 0 { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header") + } + + key := tryJWKS(decryptionKey, obj.Header) + decrypter, err := newDecrypter(key) + if err != nil { + return -1, Header{}, nil, err + } + + encryption := globalHeaders.getEncryption() + cipher := getContentCipher(encryption) + if cipher == nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption)) + } + + generator := randomKeyGenerator{ + size: cipher.keySize(), + } + + parts := &aeadParts{ + iv: obj.iv, + ciphertext: obj.ciphertext, + tag: obj.tag, + } + + authData := obj.computeAuthData() + + index := -1 + var plaintext []byte + var headers rawHeader + + for i, recipient := range obj.recipients { + recipientHeaders := obj.mergedHeaders(&recipient) + + cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) + if err == nil { + // Found a valid CEK -- let's try to decrypt. + plaintext, err = cipher.decrypt(cek, authData, parts) + if err == nil { + index = i + headers = recipientHeaders + break + } + } + } + + if plaintext == nil { + return -1, Header{}, nil, ErrCryptoFailure + } + + // The "zip" header parameter may only be present in the protected header. + if comp := obj.protected.getCompression(); comp != "" { + plaintext, _ = decompress(comp, plaintext) + } + + sanitized, err := headers.sanitized() + if err != nil { + return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err) + } + + return index, sanitized, plaintext, err +} diff --git a/vendor/github.com/go-jose/go-jose/v3/doc.go b/vendor/github.com/go-jose/go-jose/v3/doc.go new file mode 100644 index 00000000..71ec1c41 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/doc.go @@ -0,0 +1,27 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + +Package jose aims to provide an implementation of the Javascript Object Signing +and Encryption set of standards. It implements encryption and signing based on +the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web +Token support available in a sub-package. The library supports both the compact +and JWS/JWE JSON Serialization formats, and has optional support for multiple +recipients. + +*/ +package jose diff --git a/vendor/github.com/go-jose/go-jose/v3/encoding.go b/vendor/github.com/go-jose/go-jose/v3/encoding.go new file mode 100644 index 00000000..968a4249 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/encoding.go @@ -0,0 +1,191 @@ +/*- + * Copyright 2014 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "bytes" + "compress/flate" + "encoding/base64" + "encoding/binary" + "io" + "math/big" + "strings" + "unicode" + + "github.com/go-jose/go-jose/v3/json" +) + +// Helper function to serialize known-good objects. +// Precondition: value is not a nil pointer. +func mustSerializeJSON(value interface{}) []byte { + out, err := json.Marshal(value) + if err != nil { + panic(err) + } + // We never want to serialize the top-level value "null," since it's not a + // valid JOSE message. But if a caller passes in a nil pointer to this method, + // MarshalJSON will happily serialize it as the top-level value "null". If + // that value is then embedded in another operation, for instance by being + // base64-encoded and fed as input to a signing algorithm + // (https://github.com/go-jose/go-jose/issues/22), the result will be + // incorrect. Because this method is intended for known-good objects, and a nil + // pointer is not a known-good object, we are free to panic in this case. + // Note: It's not possible to directly check whether the data pointed at by an + // interface is a nil pointer, so we do this hacky workaround. + // https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I + if string(out) == "null" { + panic("Tried to serialize a nil pointer.") + } + return out +} + +// Strip all newlines and whitespace +func stripWhitespace(data string) string { + buf := strings.Builder{} + buf.Grow(len(data)) + for _, r := range data { + if !unicode.IsSpace(r) { + buf.WriteRune(r) + } + } + return buf.String() +} + +// Perform compression based on algorithm +func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { + switch algorithm { + case DEFLATE: + return deflate(input) + default: + return nil, ErrUnsupportedAlgorithm + } +} + +// Perform decompression based on algorithm +func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { + switch algorithm { + case DEFLATE: + return inflate(input) + default: + return nil, ErrUnsupportedAlgorithm + } +} + +// Compress with DEFLATE +func deflate(input []byte) ([]byte, error) { + output := new(bytes.Buffer) + + // Writing to byte buffer, err is always nil + writer, _ := flate.NewWriter(output, 1) + _, _ = io.Copy(writer, bytes.NewBuffer(input)) + + err := writer.Close() + return output.Bytes(), err +} + +// Decompress with DEFLATE +func inflate(input []byte) ([]byte, error) { + output := new(bytes.Buffer) + reader := flate.NewReader(bytes.NewBuffer(input)) + + _, err := io.Copy(output, reader) + if err != nil { + return nil, err + } + + err = reader.Close() + return output.Bytes(), err +} + +// byteBuffer represents a slice of bytes that can be serialized to url-safe base64. +type byteBuffer struct { + data []byte +} + +func newBuffer(data []byte) *byteBuffer { + if data == nil { + return nil + } + return &byteBuffer{ + data: data, + } +} + +func newFixedSizeBuffer(data []byte, length int) *byteBuffer { + if len(data) > length { + panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)") + } + pad := make([]byte, length-len(data)) + return newBuffer(append(pad, data...)) +} + +func newBufferFromInt(num uint64) *byteBuffer { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, num) + return newBuffer(bytes.TrimLeft(data, "\x00")) +} + +func (b *byteBuffer) MarshalJSON() ([]byte, error) { + return json.Marshal(b.base64()) +} + +func (b *byteBuffer) UnmarshalJSON(data []byte) error { + var encoded string + err := json.Unmarshal(data, &encoded) + if err != nil { + return err + } + + if encoded == "" { + return nil + } + + decoded, err := base64URLDecode(encoded) + if err != nil { + return err + } + + *b = *newBuffer(decoded) + + return nil +} + +func (b *byteBuffer) base64() string { + return base64.RawURLEncoding.EncodeToString(b.data) +} + +func (b *byteBuffer) bytes() []byte { + // Handling nil here allows us to transparently handle nil slices when serializing. + if b == nil { + return nil + } + return b.data +} + +func (b byteBuffer) bigInt() *big.Int { + return new(big.Int).SetBytes(b.data) +} + +func (b byteBuffer) toInt() int { + return int(b.bigInt().Int64()) +} + +// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C +func base64URLDecode(value string) ([]byte, error) { + value = strings.TrimRight(value, "=") + return base64.RawURLEncoding.DecodeString(value) +} diff --git a/vendor/github.com/gorilla/mux/LICENSE b/vendor/github.com/go-jose/go-jose/v3/json/LICENSE similarity index 83% rename from vendor/github.com/gorilla/mux/LICENSE rename to vendor/github.com/go-jose/go-jose/v3/json/LICENSE index 6903df63..74487567 100644 --- a/vendor/github.com/gorilla/mux/LICENSE +++ b/vendor/github.com/go-jose/go-jose/v3/json/LICENSE @@ -1,16 +1,16 @@ -Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/github.com/go-jose/go-jose/v3/json/README.md b/vendor/github.com/go-jose/go-jose/v3/json/README.md new file mode 100644 index 00000000..86de5e55 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/json/README.md @@ -0,0 +1,13 @@ +# Safe JSON + +This repository contains a fork of the `encoding/json` package from Go 1.6. + +The following changes were made: + +* Object deserialization uses case-sensitive member name matching instead of + [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html). + This is to avoid differences in the interpretation of JOSE messages between + go-jose and libraries written in other languages. +* When deserializing a JSON object, we check for duplicate keys and reject the + input whenever we detect a duplicate. Rather than trying to work with malformed + data, we prefer to reject it right away. diff --git a/vendor/github.com/go-jose/go-jose/v3/json/decode.go b/vendor/github.com/go-jose/go-jose/v3/json/decode.go new file mode 100644 index 00000000..4dbc4146 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/json/decode.go @@ -0,0 +1,1217 @@ +// Copyright 2010 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. + +// Represents JSON data structure using native Go types: booleans, floats, +// strings, arrays, and maps. + +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "errors" + "fmt" + "math" + "reflect" + "runtime" + "strconv" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// Unmarshal parses the JSON-encoded data and stores the result +// in the value pointed to by v. +// +// Unmarshal uses the inverse of the encodings that +// Marshal uses, allocating maps, slices, and pointers as necessary, +// with the following additional rules: +// +// To unmarshal JSON into a pointer, Unmarshal first handles the case of +// the JSON being the JSON literal null. In that case, Unmarshal sets +// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into +// the value pointed at by the pointer. If the pointer is nil, Unmarshal +// allocates a new value for it to point to. +// +// To unmarshal JSON into a struct, Unmarshal matches incoming object +// keys to the keys used by Marshal (either the struct field name or its tag), +// preferring an exact match but also accepting a case-insensitive match. +// Unmarshal will only set exported fields of the struct. +// +// To unmarshal JSON into an interface value, +// Unmarshal stores one of these in the interface value: +// +// bool, for JSON booleans +// float64, for JSON numbers +// string, for JSON strings +// []interface{}, for JSON arrays +// map[string]interface{}, for JSON objects +// nil for JSON null +// +// To unmarshal a JSON array into a slice, Unmarshal resets the slice length +// to zero and then appends each element to the slice. +// As a special case, to unmarshal an empty JSON array into a slice, +// Unmarshal replaces the slice with a new empty slice. +// +// To unmarshal a JSON array into a Go array, Unmarshal decodes +// JSON array elements into corresponding Go array elements. +// If the Go array is smaller than the JSON array, +// the additional JSON array elements are discarded. +// If the JSON array is smaller than the Go array, +// the additional Go array elements are set to zero values. +// +// To unmarshal a JSON object into a string-keyed map, Unmarshal first +// establishes a map to use, If the map is nil, Unmarshal allocates a new map. +// Otherwise Unmarshal reuses the existing map, keeping existing entries. +// Unmarshal then stores key-value pairs from the JSON object into the map. +// +// If a JSON value is not appropriate for a given target type, +// or if a JSON number overflows the target type, Unmarshal +// skips that field and completes the unmarshaling as best it can. +// If no more serious errors are encountered, Unmarshal returns +// an UnmarshalTypeError describing the earliest such error. +// +// The JSON null value unmarshals into an interface, map, pointer, or slice +// by setting that Go value to nil. Because null is often used in JSON to mean +// ``not present,'' unmarshaling a JSON null into any other Go type has no effect +// on the value and produces no error. +// +// When unmarshaling quoted strings, invalid UTF-8 or +// invalid UTF-16 surrogate pairs are not treated as an error. +// Instead, they are replaced by the Unicode replacement +// character U+FFFD. +// +func Unmarshal(data []byte, v interface{}) error { + // Check for well-formedness. + // Avoids filling out half a data structure + // before discovering a JSON syntax error. + var d decodeState + err := checkValid(data, &d.scan) + if err != nil { + return err + } + + d.init(data) + return d.unmarshal(v) +} + +// Unmarshaler is the interface implemented by objects +// that can unmarshal a JSON description of themselves. +// The input can be assumed to be a valid encoding of +// a JSON value. UnmarshalJSON must copy the JSON data +// if it wishes to retain the data after returning. +type Unmarshaler interface { + UnmarshalJSON([]byte) error +} + +// An UnmarshalTypeError describes a JSON value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // description of JSON value - "bool", "array", "number -5" + Type reflect.Type // type of Go value it could not be assigned to + Offset int64 // error occurred after reading Offset bytes +} + +func (e *UnmarshalTypeError) Error() string { + return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() +} + +// An UnmarshalFieldError describes a JSON object key that +// led to an unexported (and therefore unwritable) struct field. +// (No longer used; kept for compatibility.) +type UnmarshalFieldError struct { + Key string + Type reflect.Type + Field reflect.StructField +} + +func (e *UnmarshalFieldError) Error() string { + return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() +} + +// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "json: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "json: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "json: Unmarshal(nil " + e.Type.String() + ")" +} + +func (d *decodeState) unmarshal(v interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + d.scan.reset() + // We decode rv not rv.Elem because the Unmarshaler interface + // test must be applied at the top level of the value. + d.value(rv) + return d.savedError +} + +// A Number represents a JSON number literal. +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { + return strconv.ParseFloat(string(n), 64) +} + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { + return strconv.ParseInt(string(n), 10, 64) +} + +// isValidNumber reports whether s is a valid JSON number literal. +func isValidNumber(s string) bool { + // This function implements the JSON numbers grammar. + // See https://tools.ietf.org/html/rfc7159#section-6 + // and http://json.org/number.gif + + if s == "" { + return false + } + + // Optional - + if s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + + // Digits + switch { + default: + return false + + case s[0] == '0': + s = s[1:] + + case '1' <= s[0] && s[0] <= '9': + s = s[1:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // . followed by 1 or more digits. + if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { + s = s[2:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // e or E followed by an optional - or + and + // 1 or more digits. + if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { + s = s[1:] + if s[0] == '+' || s[0] == '-' { + s = s[1:] + if s == "" { + return false + } + } + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + } + } + + // Make sure we are at the end. + return s == "" +} + +type NumberUnmarshalType int + +const ( + // unmarshal a JSON number into an interface{} as a float64 + UnmarshalFloat NumberUnmarshalType = iota + // unmarshal a JSON number into an interface{} as a `json.Number` + UnmarshalJSONNumber + // unmarshal a JSON number into an interface{} as a int64 + // if value is an integer otherwise float64 + UnmarshalIntOrFloat +) + +// decodeState represents the state while decoding a JSON value. +type decodeState struct { + data []byte + off int // read offset in data + scan scanner + nextscan scanner // for calls to nextValue + savedError error + numberType NumberUnmarshalType +} + +// errPhase is used for errors that should not happen unless +// there is a bug in the JSON decoder or something is editing +// the data slice while the decoder executes. +var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?") + +func (d *decodeState) init(data []byte) *decodeState { + d.data = data + d.off = 0 + d.savedError = nil + return d +} + +// error aborts the decoding by panicking with err. +func (d *decodeState) error(err error) { + panic(err) +} + +// saveError saves the first err it is called with, +// for reporting at the end of the unmarshal. +func (d *decodeState) saveError(err error) { + if d.savedError == nil { + d.savedError = err + } +} + +// next cuts off and returns the next full JSON value in d.data[d.off:]. +// The next value is known to be an object or array, not a literal. +func (d *decodeState) next() []byte { + c := d.data[d.off] + item, rest, err := nextValue(d.data[d.off:], &d.nextscan) + if err != nil { + d.error(err) + } + d.off = len(d.data) - len(rest) + + // Our scanner has seen the opening brace/bracket + // and thinks we're still in the middle of the object. + // invent a closing brace/bracket to get it out. + if c == '{' { + d.scan.step(&d.scan, '}') + } else { + d.scan.step(&d.scan, ']') + } + + return item +} + +// scanWhile processes bytes in d.data[d.off:] until it +// receives a scan code not equal to op. +// It updates d.off and returns the new scan code. +func (d *decodeState) scanWhile(op int) int { + var newOp int + for { + if d.off >= len(d.data) { + newOp = d.scan.eof() + d.off = len(d.data) + 1 // mark processed EOF with len+1 + } else { + c := d.data[d.off] + d.off++ + newOp = d.scan.step(&d.scan, c) + } + if newOp != op { + break + } + } + return newOp +} + +// value decodes a JSON value from d.data[d.off:] into the value. +// it updates d.off to point past the decoded value. +func (d *decodeState) value(v reflect.Value) { + if !v.IsValid() { + _, rest, err := nextValue(d.data[d.off:], &d.nextscan) + if err != nil { + d.error(err) + } + d.off = len(d.data) - len(rest) + + // d.scan thinks we're still at the beginning of the item. + // Feed in an empty string - the shortest, simplest value - + // so that it knows we got to the end of the value. + if d.scan.redo { + // rewind. + d.scan.redo = false + d.scan.step = stateBeginValue + } + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '"') + + n := len(d.scan.parseState) + if n > 0 && d.scan.parseState[n-1] == parseObjectKey { + // d.scan thinks we just read an object key; finish the object + d.scan.step(&d.scan, ':') + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '"') + d.scan.step(&d.scan, '}') + } + + return + } + + switch op := d.scanWhile(scanSkipSpace); op { + default: + d.error(errPhase) + + case scanBeginArray: + d.array(v) + + case scanBeginObject: + d.object(v) + + case scanBeginLiteral: + d.literal(v) + } +} + +type unquotedValue struct{} + +// valueQuoted is like value but decodes a +// quoted string literal or literal null into an interface value. +// If it finds anything other than a quoted string literal or null, +// valueQuoted returns unquotedValue{}. +func (d *decodeState) valueQuoted() interface{} { + switch op := d.scanWhile(scanSkipSpace); op { + default: + d.error(errPhase) + + case scanBeginArray: + d.array(reflect.Value{}) + + case scanBeginObject: + d.object(reflect.Value{}) + + case scanBeginLiteral: + switch v := d.literalInterface().(type) { + case nil, string: + return v + } + } + return unquotedValue{} +} + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// array consumes an array from d.data[d.off-1:], decoding into the value v. +// the first byte of the array ('[') has been read already. +func (d *decodeState) array(v reflect.Value) { + // Check for unmarshaler. + u, ut, pv := d.indirect(v, false) + if u != nil { + d.off-- + err := u.UnmarshalJSON(d.next()) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) + d.off-- + d.next() + return + } + + v = pv + + // Check type of target. + switch v.Kind() { + case reflect.Interface: + if v.NumMethod() == 0 { + // Decoding into nil interface? Switch to non-reflect code. + v.Set(reflect.ValueOf(d.arrayInterface())) + return + } + // Otherwise it's invalid. + fallthrough + default: + d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) + d.off-- + d.next() + return + case reflect.Array: + case reflect.Slice: + break + } + + i := 0 + for { + // Look ahead for ] - can only happen on first iteration. + op := d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + + // Back up so d.value can have the byte we just read. + d.off-- + d.scan.undo(op) + + // Get element of array, growing if necessary. + if v.Kind() == reflect.Slice { + // Grow slice if necessary + if i >= v.Cap() { + newcap := v.Cap() + v.Cap()/2 + if newcap < 4 { + newcap = 4 + } + newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) + reflect.Copy(newv, v) + v.Set(newv) + } + if i >= v.Len() { + v.SetLen(i + 1) + } + } + + if i < v.Len() { + // Decode into element. + d.value(v.Index(i)) + } else { + // Ran out of fixed array: skip. + d.value(reflect.Value{}) + } + i++ + + // Next token must be , or ]. + op = d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + if op != scanArrayValue { + d.error(errPhase) + } + } + + if i < v.Len() { + if v.Kind() == reflect.Array { + // Array. Zero the rest. + z := reflect.Zero(v.Type().Elem()) + for ; i < v.Len(); i++ { + v.Index(i).Set(z) + } + } else { + v.SetLen(i) + } + } + if i == 0 && v.Kind() == reflect.Slice { + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) + } +} + +var nullLiteral = []byte("null") + +// object consumes an object from d.data[d.off-1:], decoding into the value v. +// the first byte ('{') of the object has been read already. +func (d *decodeState) object(v reflect.Value) { + // Check for unmarshaler. + u, ut, pv := d.indirect(v, false) + if u != nil { + d.off-- + err := u.UnmarshalJSON(d.next()) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + v = pv + + // Decoding into nil interface? Switch to non-reflect code. + if v.Kind() == reflect.Interface && v.NumMethod() == 0 { + v.Set(reflect.ValueOf(d.objectInterface())) + return + } + + // Check type of target: struct or map[string]T + switch v.Kind() { + case reflect.Map: + // map must have string kind + t := v.Type() + if t.Key().Kind() != reflect.String { + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + if v.IsNil() { + v.Set(reflect.MakeMap(t)) + } + case reflect.Struct: + + default: + d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) + d.off-- + d.next() // skip over { } in input + return + } + + var mapElem reflect.Value + keys := map[string]bool{} + + for { + // Read opening " of string key or closing }. + op := d.scanWhile(scanSkipSpace) + if op == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if op != scanBeginLiteral { + d.error(errPhase) + } + + // Read key. + start := d.off - 1 + op = d.scanWhile(scanContinue) + item := d.data[start : d.off-1] + key, ok := unquote(item) + if !ok { + d.error(errPhase) + } + + // Check for duplicate keys. + _, ok = keys[key] + if !ok { + keys[key] = true + } else { + d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) + } + + // Figure out field corresponding to key. + var subv reflect.Value + destring := false // whether the value is wrapped in a string to be decoded first + + if v.Kind() == reflect.Map { + elemType := v.Type().Elem() + if !mapElem.IsValid() { + mapElem = reflect.New(elemType).Elem() + } else { + mapElem.Set(reflect.Zero(elemType)) + } + subv = mapElem + } else { + var f *field + fields := cachedTypeFields(v.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, []byte(key)) { + f = ff + break + } + } + if f != nil { + subv = v + destring = f.quoted + for _, i := range f.index { + if subv.Kind() == reflect.Ptr { + if subv.IsNil() { + subv.Set(reflect.New(subv.Type().Elem())) + } + subv = subv.Elem() + } + subv = subv.Field(i) + } + } + } + + // Read : before value. + if op == scanSkipSpace { + op = d.scanWhile(scanSkipSpace) + } + if op != scanObjectKey { + d.error(errPhase) + } + + // Read value. + if destring { + switch qv := d.valueQuoted().(type) { + case nil: + d.literalStore(nullLiteral, subv, false) + case string: + d.literalStore([]byte(qv), subv, true) + default: + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) + } + } else { + d.value(subv) + } + + // Write value back to map; + // if using struct, subv points into struct already. + if v.Kind() == reflect.Map { + kv := reflect.ValueOf(key).Convert(v.Type().Key()) + v.SetMapIndex(kv, subv) + } + + // Next token must be , or }. + op = d.scanWhile(scanSkipSpace) + if op == scanEndObject { + break + } + if op != scanObjectValue { + d.error(errPhase) + } + } +} + +// literal consumes a literal from d.data[d.off-1:], decoding into the value v. +// The first byte of the literal has been read already +// (that's how the caller knows it's a literal). +func (d *decodeState) literal(v reflect.Value) { + // All bytes inside literal return scanContinue op code. + start := d.off - 1 + op := d.scanWhile(scanContinue) + + // Scan read one byte too far; back up. + d.off-- + d.scan.undo(op) + + d.literalStore(d.data[start:d.off], v, false) +} + +// convertNumber converts the number literal s to a float64, int64 or a Number +// depending on d.numberDecodeType. +func (d *decodeState) convertNumber(s string) (interface{}, error) { + switch d.numberType { + + case UnmarshalJSONNumber: + return Number(s), nil + case UnmarshalIntOrFloat: + v, err := strconv.ParseInt(s, 10, 64) + if err == nil { + return v, nil + } + + // tries to parse integer number in scientific notation + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} + } + + // if it has no decimal value use int64 + if fi, fd := math.Modf(f); fd == 0.0 { + return int64(fi), nil + } + return f, nil + default: + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} + } + return f, nil + } + +} + +var numberType = reflect.TypeOf(Number("")) + +// literalStore decodes a literal stored in item into v. +// +// fromQuoted indicates whether this literal came from unwrapping a +// string from the ",string" struct tag option. this is used only to +// produce more helpful error messages. +func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) { + // Check for unmarshaler. + if len(item) == 0 { + //Empty string given + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + return + } + wantptr := item[0] == 'n' // null + u, ut, pv := d.indirect(v, wantptr) + if u != nil { + err := u.UnmarshalJSON(item) + if err != nil { + d.error(err) + } + return + } + if ut != nil { + if item[0] != '"' { + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + } + return + } + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + err := ut.UnmarshalText(s) + if err != nil { + d.error(err) + } + return + } + + v = pv + + switch c := item[0]; c { + case 'n': // null + switch v.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + v.Set(reflect.Zero(v.Type())) + // otherwise, ignore null for primitives/string + } + case 't', 'f': // true, false + value := c == 't' + switch v.Kind() { + default: + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) + } + case reflect.Bool: + v.SetBool(value) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(value)) + } else { + d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) + } + } + + case '"': // string + s, ok := unquoteBytes(item) + if !ok { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + switch v.Kind() { + default: + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + case reflect.Slice: + if v.Type().Elem().Kind() != reflect.Uint8 { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + break + } + b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) + n, err := base64.StdEncoding.Decode(b, s) + if err != nil { + d.saveError(err) + break + } + v.SetBytes(b[:n]) + case reflect.String: + v.SetString(string(s)) + case reflect.Interface: + if v.NumMethod() == 0 { + v.Set(reflect.ValueOf(string(s))) + } else { + d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) + } + } + + default: // number + if c != '-' && (c < '0' || c > '9') { + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } + } + s := string(item) + switch v.Kind() { + default: + if v.Kind() == reflect.String && v.Type() == numberType { + v.SetString(s) + if !isValidNumber(s) { + d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item)) + } + break + } + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) + } + case reflect.Interface: + n, err := d.convertNumber(s) + if err != nil { + d.saveError(err) + break + } + if v.NumMethod() != 0 { + d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) + break + } + v.Set(reflect.ValueOf(n)) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || v.OverflowInt(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetInt(n) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || v.OverflowUint(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetUint(n) + + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(s, v.Type().Bits()) + if err != nil || v.OverflowFloat(n) { + d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) + break + } + v.SetFloat(n) + } + } +} + +// The xxxInterface routines build up a value to be stored +// in an empty interface. They are not strictly necessary, +// but they avoid the weight of reflection in this common case. + +// valueInterface is like value but returns interface{} +func (d *decodeState) valueInterface() interface{} { + switch d.scanWhile(scanSkipSpace) { + default: + d.error(errPhase) + panic("unreachable") + case scanBeginArray: + return d.arrayInterface() + case scanBeginObject: + return d.objectInterface() + case scanBeginLiteral: + return d.literalInterface() + } +} + +// arrayInterface is like array but returns []interface{}. +func (d *decodeState) arrayInterface() []interface{} { + var v = make([]interface{}, 0) + for { + // Look ahead for ] - can only happen on first iteration. + op := d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + + // Back up so d.value can have the byte we just read. + d.off-- + d.scan.undo(op) + + v = append(v, d.valueInterface()) + + // Next token must be , or ]. + op = d.scanWhile(scanSkipSpace) + if op == scanEndArray { + break + } + if op != scanArrayValue { + d.error(errPhase) + } + } + return v +} + +// objectInterface is like object but returns map[string]interface{}. +func (d *decodeState) objectInterface() map[string]interface{} { + m := make(map[string]interface{}) + keys := map[string]bool{} + + for { + // Read opening " of string key or closing }. + op := d.scanWhile(scanSkipSpace) + if op == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if op != scanBeginLiteral { + d.error(errPhase) + } + + // Read string key. + start := d.off - 1 + op = d.scanWhile(scanContinue) + item := d.data[start : d.off-1] + key, ok := unquote(item) + if !ok { + d.error(errPhase) + } + + // Check for duplicate keys. + _, ok = keys[key] + if !ok { + keys[key] = true + } else { + d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) + } + + // Read : before value. + if op == scanSkipSpace { + op = d.scanWhile(scanSkipSpace) + } + if op != scanObjectKey { + d.error(errPhase) + } + + // Read value. + m[key] = d.valueInterface() + + // Next token must be , or }. + op = d.scanWhile(scanSkipSpace) + if op == scanEndObject { + break + } + if op != scanObjectValue { + d.error(errPhase) + } + } + return m +} + +// literalInterface is like literal but returns an interface value. +func (d *decodeState) literalInterface() interface{} { + // All bytes inside literal return scanContinue op code. + start := d.off - 1 + op := d.scanWhile(scanContinue) + + // Scan read one byte too far; back up. + d.off-- + d.scan.undo(op) + item := d.data[start:d.off] + + switch c := item[0]; c { + case 'n': // null + return nil + + case 't', 'f': // true, false + return c == 't' + + case '"': // string + s, ok := unquote(item) + if !ok { + d.error(errPhase) + } + return s + + default: // number + if c != '-' && (c < '0' || c > '9') { + d.error(errPhase) + } + n, err := d.convertNumber(string(item)) + if err != nil { + d.saveError(err) + } + return n + } +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + r, err := strconv.ParseUint(string(s[2:6]), 16, 64) + if err != nil { + return -1 + } + return rune(r) +} + +// unquote converts a quoted JSON string literal s into an actual string t. +// The rules are different than for Go, so cannot use strconv.Unquote. +func unquote(s []byte) (t string, ok bool) { + s, ok = unquoteBytes(s) + t = string(s) + return +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/vendor/github.com/go-jose/go-jose/v3/json/encode.go b/vendor/github.com/go-jose/go-jose/v3/json/encode.go new file mode 100644 index 00000000..ea0a1361 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v3/json/encode.go @@ -0,0 +1,1197 @@ +// Copyright 2010 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. + +// Package json implements encoding and decoding of JSON objects as defined in +// RFC 4627. The mapping between JSON objects and Go values is described +// in the documentation for the Marshal and Unmarshal functions. +// +// See "JSON and Go" for an introduction to this package: +// https://golang.org/doc/articles/json_and_go.html +package json + +import ( + "bytes" + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// Marshal returns the JSON encoding of v. +// +// Marshal traverses the value v recursively. +// If an encountered value implements the Marshaler interface +// and is not a nil pointer, Marshal calls its MarshalJSON method +// to produce JSON. If no MarshalJSON method is present but the +// value implements encoding.TextMarshaler instead, Marshal calls +// its MarshalText method. +// The nil pointer exception is not strictly necessary +// but mimics a similar, necessary exception in the behavior of +// UnmarshalJSON. +// +// Otherwise, Marshal uses the following type-dependent default encodings: +// +// Boolean values encode as JSON booleans. +// +// Floating point, integer, and Number values encode as JSON numbers. +// +// String values encode as JSON strings coerced to valid UTF-8, +// replacing invalid bytes with the Unicode replacement rune. +// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" +// to keep some browsers from misinterpreting JSON output as HTML. +// Ampersand "&" is also escaped to "\u0026" for the same reason. +// +// Array and slice values encode as JSON arrays, except that +// []byte encodes as a base64-encoded string, and a nil slice +// encodes as the null JSON object. +// +// Struct values encode as JSON objects. Each exported struct field +// becomes a member of the object unless +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option. +// The empty values are false, 0, any +// nil pointer or interface value, and any array, slice, map, or string of +// length zero. The object's default key string is the struct field name +// but can be specified in the struct field's tag value. The "json" key in +// the struct field's tag value is the key name, followed by an optional comma +// and options. Examples: +// +// // Field is ignored by this package. +// Field int `json:"-"` +// +// // Field appears in JSON as key "myName". +// Field int `json:"myName"` +// +// // Field appears in JSON as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `json:"myName,omitempty"` +// +// // Field appears in JSON as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `json:",omitempty"` +// +// The "string" option signals that a field is stored as JSON inside a +// JSON-encoded string. It applies only to fields of string, floating point, +// integer, or boolean types. This extra level of encoding is sometimes used +// when communicating with JavaScript programs: +// +// Int64String int64 `json:",string"` +// +// The key name will be used if it's a non-empty string consisting of +// only Unicode letters, digits, dollar signs, percent signs, hyphens, +// underscores and slashes. +// +// Anonymous struct fields are usually marshaled as if their inner exported fields +// were fields in the outer struct, subject to the usual Go visibility rules amended +// as described in the next paragraph. +// An anonymous struct field with a name given in its JSON tag is treated as +// having that name, rather than being anonymous. +// An anonymous struct field of interface type is treated the same as having +// that type as its name, rather than being anonymous. +// +// The Go visibility rules for struct fields are amended for JSON when +// deciding which field to marshal or unmarshal. If there are +// multiple fields at the same level, and that level is the least +// nested (and would therefore be the nesting level selected by the +// usual Go rules), the following extra rules apply: +// +// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, +// even if there are multiple untagged fields that would otherwise conflict. +// 2) If there is exactly one field (tagged or not according to the first rule), that is selected. +// 3) Otherwise there are multiple fields, and all are ignored; no error occurs. +// +// Handling of anonymous struct fields is new in Go 1.1. +// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of +// an anonymous struct field in both current and earlier versions, give the field +// a JSON tag of "-". +// +// Map values encode as JSON objects. +// The map's key type must be string; the map keys are used as JSON object +// keys, subject to the UTF-8 coercion described for string values above. +// +// Pointer values encode as the value pointed to. +// A nil pointer encodes as the null JSON object. +// +// Interface values encode as the value contained in the interface. +// A nil interface value encodes as the null JSON object. +// +// Channel, complex, and function values cannot be encoded in JSON. +// Attempting to encode such a value causes Marshal to return +// an UnsupportedTypeError. +// +// JSON cannot represent cyclic data structures and Marshal does not +// handle them. Passing cyclic structures to Marshal will result in +// an infinite recursion. +// +func Marshal(v interface{}) ([]byte, error) { + e := &encodeState{} + err := e.marshal(v) + if err != nil { + return nil, err + } + return e.Bytes(), nil +} + +// MarshalIndent is like Marshal but applies Indent to format the output. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + b, err := Marshal(v) + if err != nil { + return nil, err + } + var buf bytes.Buffer + err = Indent(&buf, b, prefix, indent) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 +// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 +// so that the JSON will be safe to embed inside HTML