Compare commits
74 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
0d2a7a0385 | |
|
|
789a9b110d | |
|
|
31f45fb505 | |
|
|
17533b124c | |
|
|
9ce16c5aac | |
|
|
29e8d936f2 | |
|
|
4cfebb8319 | |
|
|
eedbcf46d4 | |
|
|
a8fdbb83d0 | |
|
|
70658b863b | |
|
|
334300bae7 | |
|
|
e9f0628555 | |
|
|
0caf31c543 | |
|
|
4faa03dfed | |
|
|
58519d1268 | |
|
|
1367b967b3 | |
|
|
114683f49e | |
|
|
2b456b9a79 | |
|
|
3a71c1bcd8 | |
|
|
95642486c6 | |
|
|
691550a6f2 | |
|
|
12c2a8e144 | |
|
|
d943602d21 | |
|
|
e10e072599 | |
|
|
686347cf91 | |
|
|
f45b3a1baf | |
|
|
1ac6c45dad | |
|
|
d78e64c8cc | |
|
|
7987d01a6e | |
|
|
e1dacbcea8 | |
|
|
1cc15c6ffa | |
|
|
51c5ef726c | |
|
|
1fb466941a | |
|
|
fff1fc7390 | |
|
|
9551f2a381 | |
|
|
71448c1f7f | |
|
|
80b1634515 | |
|
|
4ac0c1f2d7 | |
|
|
4dafc15f22 | |
|
|
92ef55650f | |
|
|
9e94122d2b | |
|
|
173396be90 | |
|
|
d9e13ab2ab | |
|
|
9e6d58aaea | |
|
|
f9c2bd51ae | |
|
|
41dffd7f3c | |
|
|
8825ceecb5 | |
|
|
50104548cf | |
|
|
08efe4c103 | |
|
|
6c3df26b3c | |
|
|
1cedefa1c2 | |
|
|
ddf4e6d854 | |
|
|
8e7955ae89 | |
|
|
ae197908be | |
|
|
6ec699509d | |
|
|
242fccefa4 | |
|
|
d0a6318334 | |
|
|
398da8860f | |
|
|
70ed7ffc5f | |
|
|
9ca8b41cf7 | |
|
|
b4a98b13fe | |
|
|
64fdc52855 | |
|
|
a65da54933 | |
|
|
43a3ba347b | |
|
|
47085ee0c9 | |
|
|
a408612f26 | |
|
|
f8d12c9d39 | |
|
|
96ce66bd30 | |
|
|
e144eac2af | |
|
|
a62d63d49d | |
|
|
3bf9217de5 | |
|
|
02705c44b2 | |
|
|
ce27840573 | |
|
|
40dc601e9d |
|
|
@ -0,0 +1,151 @@
|
||||||
|
.register_inputs: ®ister_inputs
|
||||||
|
stage: release-internal
|
||||||
|
runOnBranches: "^master$"
|
||||||
|
COMPONENT: "common"
|
||||||
|
|
||||||
|
.register_inputs_stable_bookworm: ®ister_inputs_stable_bookworm
|
||||||
|
<<: *register_inputs
|
||||||
|
runOnChangesTo: ['RELEASE_NOTES']
|
||||||
|
FLAVOR: "bookworm"
|
||||||
|
SERIES: "stable"
|
||||||
|
|
||||||
|
.register_inputs_stable_trixie: ®ister_inputs_stable_trixie
|
||||||
|
<<: *register_inputs
|
||||||
|
runOnChangesTo: ['RELEASE_NOTES']
|
||||||
|
FLAVOR: "trixie"
|
||||||
|
SERIES: "stable"
|
||||||
|
|
||||||
|
.register_inputs_next_bookworm: ®ister_inputs_next_bookworm
|
||||||
|
<<: *register_inputs
|
||||||
|
FLAVOR: "bookworm"
|
||||||
|
SERIES: next
|
||||||
|
|
||||||
|
.register_inputs_next_trixie: ®ister_inputs_next_trixie
|
||||||
|
<<: *register_inputs
|
||||||
|
FLAVOR: "trixie"
|
||||||
|
SERIES: next
|
||||||
|
|
||||||
|
################################################
|
||||||
|
### Generate Debian Package for Internal APT ###
|
||||||
|
################################################
|
||||||
|
.cloudflared-apt-build: &cloudflared_apt_build
|
||||||
|
stage: package
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-packaging # For consistency, we only run this job after we knew we could build the packages for external delivery
|
||||||
|
image: $BUILD_IMAGE
|
||||||
|
cache: {}
|
||||||
|
script:
|
||||||
|
- make cloudflared-deb
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- cloudflared*.deb
|
||||||
|
|
||||||
|
##############
|
||||||
|
### Stable ###
|
||||||
|
##############
|
||||||
|
cloudflared-amd64-stable:
|
||||||
|
<<: *cloudflared_apt_build
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-release]
|
||||||
|
variables: &amd64-stable-vars
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: amd64
|
||||||
|
FIPS: true
|
||||||
|
ORIGINAL_NAME: true
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
|
||||||
|
cloudflared-arm64-stable:
|
||||||
|
<<: *cloudflared_apt_build
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-release]
|
||||||
|
variables: &arm64-stable-vars
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: arm64
|
||||||
|
FIPS: false # TUN-7595
|
||||||
|
ORIGINAL_NAME: true
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
|
||||||
|
############
|
||||||
|
### Next ###
|
||||||
|
############
|
||||||
|
cloudflared-amd64-next:
|
||||||
|
<<: *cloudflared_apt_build
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
variables:
|
||||||
|
<<: *amd64-stable-vars
|
||||||
|
NIGHTLY: true
|
||||||
|
|
||||||
|
cloudflared-arm64-next:
|
||||||
|
<<: *cloudflared_apt_build
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
variables:
|
||||||
|
<<: *arm64-stable-vars
|
||||||
|
NIGHTLY: true
|
||||||
|
|
||||||
|
include:
|
||||||
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
### Publish Packages to Internal Repos ###
|
||||||
|
##########################################
|
||||||
|
# Bookworm AMD64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_stable_bookworm
|
||||||
|
jobPrefix: cloudflared-bookworm-amd64
|
||||||
|
needs: &amd64-stable ["cloudflared-amd64-stable"]
|
||||||
|
|
||||||
|
# Bookworm ARM64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_stable_bookworm
|
||||||
|
jobPrefix: cloudflared-bookworm-arm64
|
||||||
|
needs: &arm64-stable ["cloudflared-arm64-stable"]
|
||||||
|
|
||||||
|
# Trixie AMD64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_stable_trixie
|
||||||
|
jobPrefix: cloudflared-trixie-amd64
|
||||||
|
needs: *amd64-stable
|
||||||
|
|
||||||
|
# Trixie ARM64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_stable_trixie
|
||||||
|
jobPrefix: cloudflared-trixie-arm64
|
||||||
|
needs: *arm64-stable
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
### Publish Nightly Packages to Internal Repos ###
|
||||||
|
##################################################
|
||||||
|
# Bookworm AMD64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_next_bookworm
|
||||||
|
jobPrefix: cloudflared-nightly-bookworm-amd64
|
||||||
|
needs: &amd64-next ['cloudflared-amd64-next']
|
||||||
|
|
||||||
|
# Bookworm ARM64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_next_bookworm
|
||||||
|
jobPrefix: cloudflared-nightly-bookworm-arm64
|
||||||
|
needs: &arm64-next ['cloudflared-arm64-next']
|
||||||
|
|
||||||
|
# Trixie AMD64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_next_trixie
|
||||||
|
jobPrefix: cloudflared-nightly-trixie-amd64
|
||||||
|
needs: *amd64-next
|
||||||
|
|
||||||
|
# Trixie ARM64
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/apt-register/register@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *register_inputs_next_trixie
|
||||||
|
jobPrefix: cloudflared-nightly-trixie-arm64
|
||||||
|
needs: *arm64-next
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Builds a custom CI Image when necessary
|
||||||
|
|
||||||
|
include:
|
||||||
|
#####################################################
|
||||||
|
############## Build and Push CI Image ##############
|
||||||
|
#####################################################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/docker-image/build-push-image@~latest
|
||||||
|
inputs:
|
||||||
|
stage: pre-build
|
||||||
|
jobPrefix: ci-image
|
||||||
|
runOnChangesTo: [".ci/image/**"]
|
||||||
|
runOnMR: true
|
||||||
|
runOnBranches: '^master$'
|
||||||
|
commentImageRefs: false
|
||||||
|
runner: vm-linux-x86-4cpu-8gb
|
||||||
|
EXTRA_DIB_ARGS: "--manifest=.ci/image/.docker-images"
|
||||||
|
|
||||||
|
#####################################################
|
||||||
|
## Resolve the image reference for downstream jobs ##
|
||||||
|
#####################################################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/docker-image/get-image-ref@~latest
|
||||||
|
inputs:
|
||||||
|
stage: pre-build
|
||||||
|
jobPrefix: ci-image
|
||||||
|
runOnMR: true
|
||||||
|
runOnBranches: '^master$'
|
||||||
|
IMAGE_PATH: "$REGISTRY_HOST/stash/tun/cloudflared/ci-image/master"
|
||||||
|
VARIABLE_NAME: BUILD_IMAGE
|
||||||
|
needs:
|
||||||
|
- job: ci-image-build-push-image
|
||||||
|
optional: true
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
## A set of predefined rules to use on the different jobs
|
||||||
|
.default-rules:
|
||||||
|
# Rules to run the job only on the master branch
|
||||||
|
run-on-master:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
|
# Rules to run the job only on merge requests
|
||||||
|
run-on-mr:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
when: never
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
|
# Rules to run the job on merge_requests and master branch
|
||||||
|
run-always:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
when: never
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
- if: $CI_COMMIT_BRANCH != null && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
|
# Rules to run the job only when a release happens
|
||||||
|
run-on-release:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- 'RELEASE_NOTES'
|
||||||
|
when: on_success
|
||||||
|
- when: never
|
||||||
|
|
||||||
|
.component-tests:
|
||||||
|
image: $BUILD_IMAGE
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-always]
|
||||||
|
variables:
|
||||||
|
COMPONENT_TESTS_CONFIG: component-test-config.yaml
|
||||||
|
COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiBjbG91ZGZsYXJlZC5leGUKY3JlZGVudGlhbHNfZmlsZTogY3JlZC5qc29uCm9yaWdpbmNlcnQ6IGNlcnQucGVtCnpvbmVfZG9tYWluOiBhcmdvdHVubmVsdGVzdC5jb20Kem9uZV90YWc6IDQ4Nzk2ZjFlNzBiYjc2NjljMjliYjUxYmEyODJiZjY1
|
||||||
|
secrets:
|
||||||
|
DNS_API_TOKEN:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/_terraform_atlantis/component_tests_token/data@kv
|
||||||
|
file: false
|
||||||
|
COMPONENT_TESTS_ORIGINCERT:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/component_tests_cert_pem/data@kv
|
||||||
|
file: false
|
||||||
|
cache: {}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
include:
|
||||||
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
|
|
||||||
|
######################################
|
||||||
|
### Sync master branch with Github ###
|
||||||
|
######################################
|
||||||
|
push-github:
|
||||||
|
stage: sync
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/github-push.sh
|
||||||
|
secrets:
|
||||||
|
CLOUDFLARED_DEPLOY_SSH_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cloudflared_github_ssh/data@kv
|
||||||
|
file: false
|
||||||
|
cache: {}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
images:
|
||||||
|
- name: ci-image
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
ARG CLOUDFLARE_DOCKER_REGISTRY_HOST
|
||||||
|
|
||||||
|
FROM ${CLOUDFLARE_DOCKER_REGISTRY_HOST:-registry.cfdata.org}/stash/cf/debian-images/bookworm/main:2025.7.0@sha256:6350da2f7e728dae2c1420f6dafc38e23cacc0b399d3d5b2f40fe48d9c8ff1ca
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get upgrade -y && \
|
||||||
|
apt-get install --no-install-recommends --allow-downgrades -y \
|
||||||
|
build-essential \
|
||||||
|
git \
|
||||||
|
go-boring=1.24.9-1 \
|
||||||
|
libffi-dev \
|
||||||
|
procps \
|
||||||
|
python3-dev \
|
||||||
|
python3-pip \
|
||||||
|
python3-setuptools \
|
||||||
|
python3-venv \
|
||||||
|
# libmsi and libgcab are libraries the wixl binary depends on.
|
||||||
|
libmsi-dev \
|
||||||
|
libgcab-dev \
|
||||||
|
# deb and rpm build tools
|
||||||
|
rubygem-fpm \
|
||||||
|
rpm \
|
||||||
|
# create deb and rpm repository files
|
||||||
|
reprepro \
|
||||||
|
createrepo-c \
|
||||||
|
# gcc for cross architecture compilation in arm
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
# Install wixl
|
||||||
|
curl -o /usr/local/bin/wixl -L https://pkg.cloudflare.com/binaries/wixl && \
|
||||||
|
chmod a+x /usr/local/bin/wixl && \
|
||||||
|
mkdir -p opt
|
||||||
|
|
||||||
|
WORKDIR /opt
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
.golang-inputs: &golang_inputs
|
||||||
|
runOnMR: true
|
||||||
|
runOnBranches: '^master$'
|
||||||
|
outputDir: artifacts
|
||||||
|
runner: linux-x86-8cpu-16gb
|
||||||
|
stage: build
|
||||||
|
golangVersion: "boring-1.24"
|
||||||
|
imageVersion: "3371-f5539bd6f83d@sha256:a2a68f580070f9411d0d3155959ed63b700ef319b5fcc62db340e92227bbc628"
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
|
||||||
|
.default-packaging-job: &packaging-job-defaults
|
||||||
|
stage: package
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
image: $BUILD_IMAGE
|
||||||
|
cache: {}
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts/*
|
||||||
|
|
||||||
|
include:
|
||||||
|
###################
|
||||||
|
### Linux Build ###
|
||||||
|
###################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *golang_inputs
|
||||||
|
jobPrefix: linux-build
|
||||||
|
GOLANG_MAKE_TARGET: ci-build
|
||||||
|
|
||||||
|
########################
|
||||||
|
### Linux FIPS Build ###
|
||||||
|
########################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *golang_inputs
|
||||||
|
jobPrefix: linux-fips-build
|
||||||
|
GOLANG_MAKE_TARGET: ci-fips-build
|
||||||
|
|
||||||
|
#################
|
||||||
|
### Unit Tests ##
|
||||||
|
#################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *golang_inputs
|
||||||
|
stage: test
|
||||||
|
jobPrefix: test
|
||||||
|
GOLANG_MAKE_TARGET: ci-test
|
||||||
|
|
||||||
|
######################
|
||||||
|
### Unit Tests FIPS ##
|
||||||
|
######################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *golang_inputs
|
||||||
|
stage: test
|
||||||
|
jobPrefix: test-fips
|
||||||
|
GOLANG_MAKE_TARGET: ci-fips-test
|
||||||
|
|
||||||
|
#################
|
||||||
|
### Vuln Check ##
|
||||||
|
#################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/golang/boring-make@~latest
|
||||||
|
inputs:
|
||||||
|
<<: *golang_inputs
|
||||||
|
runOnBranches: '^$'
|
||||||
|
stage: validate
|
||||||
|
jobPrefix: vulncheck
|
||||||
|
GOLANG_MAKE_TARGET: vulncheck
|
||||||
|
|
||||||
|
#################################
|
||||||
|
### Run Linux Component Tests ###
|
||||||
|
#################################
|
||||||
|
linux-component-tests: &linux-component-tests
|
||||||
|
stage: test
|
||||||
|
extends: .component-tests
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-build-boring-make
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/component-tests.sh
|
||||||
|
variables: &component-tests-variables
|
||||||
|
CI: 1
|
||||||
|
COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiAuL2Nsb3VkZmxhcmVkCmNyZWRlbnRpYWxzX2ZpbGU6IGNyZWQuanNvbgpvcmlnaW5jZXJ0OiBjZXJ0LnBlbQp6b25lX2RvbWFpbjogYXJnb3R1bm5lbHRlc3QuY29tCnpvbmVfdGFnOiA0ODc5NmYxZTcwYmI3NjY5YzI5YmI1MWJhMjgyYmY2NQ==
|
||||||
|
tags:
|
||||||
|
- linux-x86-8cpu-16gb
|
||||||
|
artifacts:
|
||||||
|
reports:
|
||||||
|
junit: report.xml
|
||||||
|
|
||||||
|
######################################
|
||||||
|
### Run Linux FIPS Component Tests ###
|
||||||
|
######################################
|
||||||
|
linux-component-tests-fips:
|
||||||
|
<<: *linux-component-tests
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-fips-build-boring-make
|
||||||
|
variables:
|
||||||
|
<<: *component-tests-variables
|
||||||
|
COMPONENT_TESTS_FIPS: 1
|
||||||
|
|
||||||
|
################################
|
||||||
|
####### Linux Packaging ########
|
||||||
|
################################
|
||||||
|
linux-packaging:
|
||||||
|
<<: *packaging-job-defaults
|
||||||
|
parallel:
|
||||||
|
matrix:
|
||||||
|
- ARCH: ["386", "amd64", "arm", "armhf", "arm64"]
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/linux/build-packages.sh ${ARCH}
|
||||||
|
|
||||||
|
################################
|
||||||
|
##### Linux FIPS Packaging #####
|
||||||
|
################################
|
||||||
|
linux-packaging-fips:
|
||||||
|
<<: *packaging-job-defaults
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/linux/build-packages-fips.sh
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
include:
|
||||||
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
|
|
||||||
|
###############################
|
||||||
|
### Defaults for Mac Builds ###
|
||||||
|
###############################
|
||||||
|
.mac-build-defaults: &mac-build-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-mr]
|
||||||
|
tags:
|
||||||
|
- "macstadium-${RUNNER_ARCH}"
|
||||||
|
parallel:
|
||||||
|
matrix:
|
||||||
|
- RUNNER_ARCH: [arm, intel]
|
||||||
|
cache: {}
|
||||||
|
|
||||||
|
######################################
|
||||||
|
### Build Cloudflared Mac Binaries ###
|
||||||
|
######################################
|
||||||
|
macos-build-cloudflared: &mac-build
|
||||||
|
<<: *mac-build-defaults
|
||||||
|
stage: build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts/*
|
||||||
|
script:
|
||||||
|
- '[ "${RUNNER_ARCH}" = "arm" ] && export TARGET_ARCH=arm64'
|
||||||
|
- '[ "${RUNNER_ARCH}" = "intel" ] && export TARGET_ARCH=amd64'
|
||||||
|
- ARCH=$(uname -m)
|
||||||
|
- echo ARCH=$ARCH - TARGET_ARCH=$TARGET_ARCH
|
||||||
|
- ./.ci/scripts/mac/install-go.sh
|
||||||
|
- BUILD_SCRIPT=.ci/scripts/mac/build.sh
|
||||||
|
- if [[ ! -x ${BUILD_SCRIPT} ]] ; then exit ; fi
|
||||||
|
- set -euo pipefail
|
||||||
|
- echo "Executing ${BUILD_SCRIPT}"
|
||||||
|
- exec ${BUILD_SCRIPT}
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
### Build and Sign Cloudflared Mac Binaries ###
|
||||||
|
###############################################
|
||||||
|
macos-build-and-sign-cloudflared:
|
||||||
|
<<: *mac-build
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
secrets:
|
||||||
|
APPLE_DEV_CA_CERT:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/apple_dev_ca_cert_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_CODE_SIGN_CERT:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_cert_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_CODE_SIGN_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_key_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_CODE_SIGN_PASS:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_pass_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_INSTALLER_CERT:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_cert_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_INSTALLER_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_key_v2/data@kv
|
||||||
|
file: false
|
||||||
|
CFD_INSTALLER_PASS:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_pass_v2/data@kv
|
||||||
|
file: false
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
include:
|
||||||
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
|
|
||||||
|
######################################
|
||||||
|
### Build and Push DockerHub Image ###
|
||||||
|
######################################
|
||||||
|
- component: $CI_SERVER_FQDN/cloudflare/ci/docker-image/build-push-image@~latest
|
||||||
|
inputs:
|
||||||
|
stage: release
|
||||||
|
jobPrefix: docker-hub
|
||||||
|
runOnMR: false
|
||||||
|
runOnBranches: '^master$'
|
||||||
|
runOnChangesTo: ['RELEASE_NOTES']
|
||||||
|
needs:
|
||||||
|
- generate-version-file
|
||||||
|
- release-cloudflared-to-r2
|
||||||
|
commentImageRefs: false
|
||||||
|
runner: vm-linux-x86-4cpu-8gb
|
||||||
|
# Based on if the CI reference is protected or not the CI component will
|
||||||
|
# either use _BRANCH or _PROD, therefore, to prevent the pipelines from failing
|
||||||
|
# we simply set both to the same value.
|
||||||
|
DOCKER_USER_BRANCH: &docker-hub-user svcgithubdockerhubcloudflar045
|
||||||
|
DOCKER_PASSWORD_BRANCH: &docker-hub-password gitlab/cloudflare/tun/cloudflared/_dev/dockerhub/svc_password/data
|
||||||
|
DOCKER_USER_PROD: *docker-hub-user
|
||||||
|
DOCKER_PASSWORD_PROD: *docker-hub-password
|
||||||
|
EXTRA_DIB_ARGS: --overwrite
|
||||||
|
|
||||||
|
.default-release-job: &release-job-defaults
|
||||||
|
stage: release
|
||||||
|
image: $BUILD_IMAGE
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- .cache/pip
|
||||||
|
variables: &release-job-variables
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||||
|
# KV Vars
|
||||||
|
KV_NAMESPACE: 380e19aa04314648949b6ad841417ebe
|
||||||
|
KV_ACCOUNT: &cf-account 5ab4e9dfbd435d24068829fda0077963
|
||||||
|
# R2 Vars
|
||||||
|
R2_BUCKET: cloudflared-pkgs
|
||||||
|
R2_ACCOUNT_ID: *cf-account
|
||||||
|
# APT and RPM Repository Vars
|
||||||
|
GPG_PUBLIC_KEY_URL: "https://pkg.cloudflare.com/cloudflare-ascii-pubkey.gpg"
|
||||||
|
PKG_URL: "https://pkg.cloudflare.com/cloudflared"
|
||||||
|
BINARY_NAME: cloudflared
|
||||||
|
secrets:
|
||||||
|
KV_API_TOKEN:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_kv_api_token/data@kv
|
||||||
|
file: false
|
||||||
|
API_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_github_api_key/data@kv
|
||||||
|
file: false
|
||||||
|
R2_CLIENT_ID:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/_terraform_atlantis/r2_api_token/client_id@kv
|
||||||
|
file: false
|
||||||
|
R2_CLIENT_SECRET:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/_terraform_atlantis/r2_api_token/client_secret@kv
|
||||||
|
file: false
|
||||||
|
LINUX_SIGNING_PUBLIC_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/gpg_v1/public_key@kv
|
||||||
|
file: false
|
||||||
|
LINUX_SIGNING_PRIVATE_KEY:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/gpg_v1/private_key@kv
|
||||||
|
file: false
|
||||||
|
LINUX_SIGNING_PUBLIC_KEY_2:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/gpg_v2/public_key@kv
|
||||||
|
file: false
|
||||||
|
LINUX_SIGNING_PRIVATE_KEY_2:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/gpg_v2/private_key@kv
|
||||||
|
file: false
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
### Push Cloudflared Binaries to Github ###
|
||||||
|
###########################################
|
||||||
|
release-cloudflared-to-github:
|
||||||
|
<<: *release-job-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-release]
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-packaging
|
||||||
|
- linux-packaging-fips
|
||||||
|
- macos-build-and-sign-cloudflared
|
||||||
|
- windows-package-sign
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/release-target.sh github-release
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
### Upload Cloudflared Binaries to R2 ###
|
||||||
|
#########################################
|
||||||
|
release-cloudflared-to-r2:
|
||||||
|
<<: *release-job-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-release]
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-packaging # We only release non-FIPS binaries to R2
|
||||||
|
- release-cloudflared-to-github
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/release-target.sh r2-linux-release
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
### Upload Cloudflared Nightly Binaries to R2 ###
|
||||||
|
#################################################
|
||||||
|
release-cloudflared-nightly-to-r2:
|
||||||
|
<<: *release-job-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
variables:
|
||||||
|
<<: *release-job-variables
|
||||||
|
R2_BUCKET: cloudflared-pkgs-next
|
||||||
|
GPG_PUBLIC_KEY_URL: "https://next.pkg.cloudflare.com/cloudflare-ascii-pubkey.gpg"
|
||||||
|
PKG_URL: "https://next.pkg.cloudflare.com/cloudflared"
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- linux-packaging # We only release non-FIPS binaries to R2
|
||||||
|
script:
|
||||||
|
- ./.ci/scripts/release-target.sh r2-linux-release
|
||||||
|
|
||||||
|
#############################
|
||||||
|
### Generate Version File ###
|
||||||
|
#############################
|
||||||
|
generate-version-file:
|
||||||
|
<<: *release-job-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-release]
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
script:
|
||||||
|
- make generate-docker-version
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- versions
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
# Fetch cloudflared from the artifacts folder
|
||||||
|
mv ./artifacts/cloudflared ./cloudflared
|
||||||
|
|
||||||
|
python3 -m venv env
|
||||||
|
. env/bin/activate
|
||||||
|
|
||||||
|
pip install --upgrade -r component-tests/requirements.txt
|
||||||
|
|
||||||
|
# Creates and routes a Named Tunnel for this build. Also constructs
|
||||||
|
# config file from env vars.
|
||||||
|
python3 component-tests/setup.py --type create
|
||||||
|
|
||||||
|
# Define the cleanup function
|
||||||
|
cleanup() {
|
||||||
|
# The Named Tunnel is deleted and its route unprovisioned here.
|
||||||
|
python3 component-tests/setup.py --type cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
# The trap will call the cleanup function on script exit
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
pytest component-tests -o log_cli=true --log-cli-level=INFO --junit-xml=report.xml
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
|
|
||||||
OUTPUT=$(goimports -l -d -local github.com/cloudflare/cloudflared $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc))
|
OUTPUT=$(go run -mod=readonly golang.org/x/tools/cmd/goimports@v0.30.0 -l -d -local github.com/cloudflare/cloudflared $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc))
|
||||||
|
|
||||||
if [ -n "$OUTPUT" ] ; then
|
if [ -n "$OUTPUT" ] ; then
|
||||||
PAGER=$(which colordiff || echo cat)
|
PAGER=$(which colordiff || echo cat)
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
BRANCH="master"
|
||||||
|
TMP_PATH="$PWD/tmp"
|
||||||
|
PRIVATE_KEY_PATH="$TMP_PATH/github-deploy-key"
|
||||||
|
PUBLIC_KEY_GITHUB_PATH="$TMP_PATH/github.pub"
|
||||||
|
|
||||||
|
mkdir -p $TMP_PATH
|
||||||
|
|
||||||
|
# Setup Private Key
|
||||||
|
echo "$CLOUDFLARED_DEPLOY_SSH_KEY" > $PRIVATE_KEY_PATH
|
||||||
|
chmod 400 $PRIVATE_KEY_PATH
|
||||||
|
|
||||||
|
# Download GitHub Public Key for KnownHostsFile
|
||||||
|
ssh-keyscan -t ed25519 github.com > $PUBLIC_KEY_GITHUB_PATH
|
||||||
|
|
||||||
|
# Setup git ssh command with the right configurations
|
||||||
|
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=$PUBLIC_KEY_GITHUB_PATH -o IdentitiesOnly=yes -i $PRIVATE_KEY_PATH"
|
||||||
|
|
||||||
|
# Add GitHub as a new remote
|
||||||
|
git remote add github git@github.com:cloudflare/cloudflared.git || true
|
||||||
|
|
||||||
|
# GitLab doesn't pull branch references, instead it creates a new one on each pipeline.
|
||||||
|
# Therefore, we need to manually fetch the reference to then push it to GitHub.
|
||||||
|
git fetch origin $BRANCH:$BRANCH
|
||||||
|
git push -u github $BRANCH
|
||||||
|
|
||||||
|
if TAG="$(git describe --tags --exact-match 2>/dev/null)"; then
|
||||||
|
git push -u github "$TAG"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if architecture argument is provided
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Error: Architecture argument is required"
|
||||||
|
echo "Usage: $0 <architecture>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
arch=$1
|
||||||
|
|
||||||
|
# Get Version
|
||||||
|
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
|
||||||
|
echo $VERSION
|
||||||
|
|
||||||
|
# Disable FIPS module in go-boring
|
||||||
|
export GOEXPERIMENT=noboringcrypto
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
# This controls the directory the built artifacts go into
|
||||||
|
export ARTIFACT_DIR=artifacts/
|
||||||
|
mkdir -p $ARTIFACT_DIR
|
||||||
|
|
||||||
|
export TARGET_OS=linux
|
||||||
|
|
||||||
|
unset TARGET_ARM
|
||||||
|
export TARGET_ARCH=$arch
|
||||||
|
|
||||||
|
## Support for arm platforms without hardware FPU enabled
|
||||||
|
if [[ $arch == arm ]] ; then
|
||||||
|
export TARGET_ARCH=arm
|
||||||
|
export TARGET_ARM=5
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Support for armhf builds
|
||||||
|
if [[ $arch == armhf ]] ; then
|
||||||
|
export TARGET_ARCH=arm
|
||||||
|
export TARGET_ARM=7
|
||||||
|
fi
|
||||||
|
|
||||||
|
make cloudflared-deb
|
||||||
|
mv cloudflared\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-linux-$arch.deb
|
||||||
|
|
||||||
|
# rpm packages invert the - and _ and use x86_64 instead of amd64.
|
||||||
|
RPMVERSION=$(echo $VERSION|sed -r 's/-/_/g')
|
||||||
|
RPMARCH=$arch
|
||||||
|
if [ $arch == "amd64" ];then
|
||||||
|
RPMARCH="x86_64"
|
||||||
|
fi
|
||||||
|
if [ $arch == "arm64" ]; then
|
||||||
|
RPMARCH="aarch64"
|
||||||
|
fi
|
||||||
|
make cloudflared-rpm
|
||||||
|
mv cloudflared-$RPMVERSION-1.$RPMARCH.rpm $ARTIFACT_DIR/cloudflared-linux-$RPMARCH.rpm
|
||||||
|
|
||||||
|
# finally move the linux binary as well.
|
||||||
|
mv ./cloudflared $ARTIFACT_DIR/cloudflared-linux-$arch
|
||||||
|
|
||||||
|
|
@ -2,9 +2,9 @@ rm -rf /tmp/go
|
||||||
export GOCACHE=/tmp/gocache
|
export GOCACHE=/tmp/gocache
|
||||||
rm -rf $GOCACHE
|
rm -rf $GOCACHE
|
||||||
|
|
||||||
./.teamcity/install-cloudflare-go.sh
|
brew install go@1.24
|
||||||
|
|
||||||
export PATH="/tmp/go/bin:$PATH"
|
|
||||||
go version
|
go version
|
||||||
which go
|
which go
|
||||||
go env
|
go env
|
||||||
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
python3 -m venv env
|
||||||
|
. env/bin/activate
|
||||||
|
pip install pynacl==1.4.0 pygithub==1.55
|
||||||
|
|
||||||
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
|
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
|
||||||
echo $VERSION
|
echo $VERSION
|
||||||
|
|
||||||
export TARGET_OS=windows
|
export TARGET_OS=windows
|
||||||
# This controls the directory the built artifacts go into
|
# This controls the directory the built artifacts go into
|
||||||
export BUILT_ARTIFACT_DIR=built_artifacts/
|
export BUILT_ARTIFACT_DIR=artifacts/
|
||||||
export FINAL_ARTIFACT_DIR=artifacts/
|
export FINAL_ARTIFACT_DIR=artifacts/
|
||||||
mkdir -p $BUILT_ARTIFACT_DIR
|
mkdir -p $BUILT_ARTIFACT_DIR
|
||||||
mkdir -p $FINAL_ARTIFACT_DIR
|
mkdir -p $FINAL_ARTIFACT_DIR
|
||||||
windowsArchs=("amd64" "386")
|
windowsArchs=("amd64" "386")
|
||||||
for arch in ${windowsArchs[@]}; do
|
for arch in ${windowsArchs[@]}; do
|
||||||
export TARGET_ARCH=$arch
|
export TARGET_ARCH=$arch
|
||||||
# Copy exe into final directory
|
# Copy .exe from artifacts directory
|
||||||
cp $BUILT_ARTIFACT_DIR/cloudflared-windows-$arch.exe ./cloudflared.exe
|
cp $BUILT_ARTIFACT_DIR/cloudflared-windows-$arch.exe ./cloudflared.exe
|
||||||
make cloudflared-msi
|
make cloudflared-msi
|
||||||
# Copy msi into final directory
|
# Copy msi into final directory
|
||||||
mv cloudflared-$VERSION-$arch.msi $FINAL_ARTIFACT_DIR/cloudflared-windows-$arch.msi
|
mv cloudflared-$VERSION-$arch.msi $FINAL_ARTIFACT_DIR/cloudflared-windows-$arch.msi
|
||||||
cp $BUILT_ARTIFACT_DIR/cloudflared-windows-$arch.exe $FINAL_ARTIFACT_DIR/cloudflared-windows-$arch.exe
|
|
||||||
done
|
done
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
# Check if a make target is provided as an argument
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Error: Make target argument is required"
|
||||||
|
echo "Usage: $0 <make-target>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAKE_TARGET=$1
|
||||||
|
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# Our release scripts are written in python, so we should install their dependecies here.
|
||||||
|
pip install pynacl==1.4.0 pygithub==1.55 boto3==1.22.9 python-gnupg==0.4.9
|
||||||
|
make $MAKE_TARGET
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Define the file to store the list of vulnerabilities to ignore.
|
||||||
|
IGNORE_FILE=".vulnignore"
|
||||||
|
|
||||||
|
# Check if the ignored vulnerabilities file exists. If not, create an empty one.
|
||||||
|
if [ ! -f "$IGNORE_FILE" ]; then
|
||||||
|
touch "$IGNORE_FILE"
|
||||||
|
echo "Created an empty file to store ignored vulnerabilities: $IGNORE_FILE"
|
||||||
|
echo "# Add vulnerability IDs (e.g., GO-2022-0450) to ignore, one per line." >> "$IGNORE_FILE"
|
||||||
|
echo "# You can also add comments on the same line after the ID." >> "$IGNORE_FILE"
|
||||||
|
echo "" >> "$IGNORE_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run govulncheck and capture its output.
|
||||||
|
VULN_OUTPUT=$(go run -mod=readonly golang.org/x/vuln/cmd/govulncheck@latest ./... || true)
|
||||||
|
|
||||||
|
# Print the govuln output
|
||||||
|
echo "====================================="
|
||||||
|
echo "Full Output of govulncheck:"
|
||||||
|
echo "====================================="
|
||||||
|
echo "$VULN_OUTPUT"
|
||||||
|
echo "====================================="
|
||||||
|
echo "End of govulncheck Output"
|
||||||
|
echo "====================================="
|
||||||
|
|
||||||
|
# Process the ignore file to remove comments and empty lines.
|
||||||
|
# The 'cut' command gets the vulnerability ID and removes anything after the '#'.
|
||||||
|
# The 'grep' command filters out empty lines and lines starting with '#'.
|
||||||
|
CLEAN_IGNORES=$(grep -v '^\s*#' "$IGNORE_FILE" | cut -d'#' -f1 | sed 's/ //g' | sort -u || true)
|
||||||
|
|
||||||
|
# Filter out the ignored vulnerabilities.
|
||||||
|
UNIGNORED_VULNS=$(echo "$VULN_OUTPUT" | grep 'Vulnerability')
|
||||||
|
|
||||||
|
# If the list of ignored vulnerabilities is not empty, filter them out.
|
||||||
|
if [ -n "$CLEAN_IGNORES" ]; then
|
||||||
|
UNIGNORED_VULNS=$(echo "$UNIGNORED_VULNS" | grep -vFf <(echo "$CLEAN_IGNORES") || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If there are any vulnerabilities that were not in our ignore list, print them and exit with an error.
|
||||||
|
if [ -n "$UNIGNORED_VULNS" ]; then
|
||||||
|
echo "🚨 Found new, unignored vulnerabilities:"
|
||||||
|
echo "-------------------------------------"
|
||||||
|
echo "$UNIGNORED_VULNS"
|
||||||
|
echo "-------------------------------------"
|
||||||
|
echo "Exiting with an error. ❌"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "🎉 No new vulnerabilities found. All clear! ✨"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
$env:TARGET_OS = "windows"
|
||||||
|
$env:LOCAL_OS = "windows"
|
||||||
|
$TIMESTAMP_RFC3161 = "http://timestamp.digicert.com"
|
||||||
|
|
||||||
|
New-Item -Path ".\artifacts" -ItemType Directory
|
||||||
|
|
||||||
|
Write-Output "Building for amd64"
|
||||||
|
$env:TARGET_ARCH = "amd64"
|
||||||
|
$env:LOCAL_ARCH = "amd64"
|
||||||
|
$env:CGO_ENABLED = 1
|
||||||
|
& make cloudflared
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared for amd64" }
|
||||||
|
# Sign build
|
||||||
|
azuresigntool.exe sign -kvu $env:KEY_VAULT_URL -kvi "$env:KEY_VAULT_CLIENT_ID" -kvs "$env:KEY_VAULT_SECRET" -kvc "$env:KEY_VAULT_CERTIFICATE" -kvt "$env:KEY_VAULT_TENANT_ID" -tr "$TIMESTAMP_RFC3161" -d "Cloudflare Tunnel Daemon" .\cloudflared.exe
|
||||||
|
copy .\cloudflared.exe .\artifacts\cloudflared-windows-amd64.exe
|
||||||
|
|
||||||
|
Write-Output "Building for 386"
|
||||||
|
$env:TARGET_ARCH = "386"
|
||||||
|
$env:LOCAL_ARCH = "386"
|
||||||
|
$env:CGO_ENABLED = 0
|
||||||
|
& make cloudflared
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared for 386" }
|
||||||
|
## Sign build
|
||||||
|
azuresigntool.exe sign -kvu $env:KEY_VAULT_URL -kvi "$env:KEY_VAULT_CLIENT_ID" -kvs "$env:KEY_VAULT_SECRET" -kvc "$env:KEY_VAULT_CERTIFICATE" -kvt "$env:KEY_VAULT_TENANT_ID" -tr "$TIMESTAMP_RFC3161" -d "Cloudflare Tunnel Daemon" .\cloudflared.exe
|
||||||
|
copy .\cloudflared.exe .\artifacts\cloudflared-windows-386.exe
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
$env:TARGET_OS = "windows"
|
||||||
|
$env:LOCAL_OS = "windows"
|
||||||
|
$env:TARGET_ARCH = "amd64"
|
||||||
|
$env:LOCAL_ARCH = "amd64"
|
||||||
|
$env:CGO_ENABLED = 1
|
||||||
|
|
||||||
|
python --version
|
||||||
|
python -m pip --version
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Building cloudflared"
|
||||||
|
& make cloudflared
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared" }
|
||||||
|
|
||||||
|
|
||||||
|
Write-Host "Running unit tests"
|
||||||
|
# Not testing with race detector because of https://github.com/golang/go/issues/61058
|
||||||
|
# We already test it on other platforms
|
||||||
|
go test -failfast -v -mod=vendor ./...
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Failed unit tests" }
|
||||||
|
|
||||||
|
|
||||||
|
# On Gitlab runners we need to add all of this addresses to the NO_PROXY list in order for the tests to run.
|
||||||
|
$env:NO_PROXY = "pypi.org,files.pythonhosted.org,api.cloudflare.com,argotunneltest.com,argotunnel.com,trycloudflare.com,${env:NO_PROXY}"
|
||||||
|
Write-Host "No Proxy: ${env:NO_PROXY}"
|
||||||
|
Write-Host "Running component tests"
|
||||||
|
try {
|
||||||
|
python -m pip --disable-pip-version-check install --upgrade -r component-tests/requirements.txt --use-pep517
|
||||||
|
python component-tests/setup.py --type create
|
||||||
|
python -m pytest component-tests -o log_cli=true --log-cli-level=INFO --junit-xml=report.xml
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Failed component tests"
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
python component-tests/setup.py --type cleanup
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
Param(
|
||||||
|
[string]$GoVersion,
|
||||||
|
[string]$ScriptToExecute
|
||||||
|
)
|
||||||
|
|
||||||
|
# The script is a wrapper that downloads a specific version
|
||||||
|
# of go, adds it to the PATH and executes a script with that go
|
||||||
|
# version in the path.
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# Get the path to the system's temporary directory.
|
||||||
|
$tempPath = [System.IO.Path]::GetTempPath()
|
||||||
|
|
||||||
|
# Create a unique name for the new temporary folder.
|
||||||
|
$folderName = "go_" + (Get-Random)
|
||||||
|
|
||||||
|
# Join the temp path and the new folder name to create the full path.
|
||||||
|
$fullPath = Join-Path -Path $tempPath -ChildPath $folderName
|
||||||
|
|
||||||
|
# Store the current value of PATH environment variable.
|
||||||
|
$oldPath = $env:Path
|
||||||
|
|
||||||
|
# Use a try...finally block to ensure the temporrary folder and PATH are cleaned up.
|
||||||
|
try {
|
||||||
|
# Create the temporary folder.
|
||||||
|
Write-Host "Creating temporary folder at: $fullPath"
|
||||||
|
$newTempFolder = New-Item -ItemType Directory -Path $fullPath -Force
|
||||||
|
|
||||||
|
# Download go
|
||||||
|
$url = "https://go.dev/dl/$GoVersion.windows-amd64.zip"
|
||||||
|
$destinationFile = Join-Path -Path $newTempFolder.FullName -ChildPath "go$GoVersion.windows-amd64.zip"
|
||||||
|
Write-Host "Downloading go from: $url"
|
||||||
|
Invoke-WebRequest -Uri $url -OutFile $destinationFile
|
||||||
|
Write-Host "File downloaded to: $destinationFile"
|
||||||
|
|
||||||
|
# Unzip the downloaded file.
|
||||||
|
Write-Host "Unzipping the file..."
|
||||||
|
Expand-Archive -Path $destinationFile -DestinationPath $newTempFolder.FullName -Force
|
||||||
|
Write-Host "File unzipped successfully."
|
||||||
|
|
||||||
|
# Define the go/bin path wich is inside the temporary folder
|
||||||
|
$goBinPath = Join-Path -Path $fullPath -ChildPath "go\bin"
|
||||||
|
|
||||||
|
# Add the go/bin path to the PATH environment variable.
|
||||||
|
$env:Path = "$goBinPath;$($env:Path)"
|
||||||
|
Write-Host "Added $goBinPath to the environment PATH."
|
||||||
|
|
||||||
|
go env
|
||||||
|
go version
|
||||||
|
|
||||||
|
& $ScriptToExecute
|
||||||
|
} finally {
|
||||||
|
# Cleanup: Remove the path from the environment variable and then the temporary folder.
|
||||||
|
Write-Host "Starting cleanup..."
|
||||||
|
|
||||||
|
$env:Path = $oldPath
|
||||||
|
Write-Host "Reverted changes in the environment PATH."
|
||||||
|
|
||||||
|
# Remove the temporary folder and its contents.
|
||||||
|
if (Test-Path -Path $fullPath) {
|
||||||
|
Remove-Item -Path $fullPath -Recurse -Force
|
||||||
|
Write-Host "Temporary folder and its contents have been removed."
|
||||||
|
} else {
|
||||||
|
Write-Host "Temporary folder does not exist, no cleanup needed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Sign Windows artifacts using azuretool
|
||||||
|
# This script processes MSI files from the artifacts directory
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Define paths
|
||||||
|
$ARTIFACT_DIR = "artifacts"
|
||||||
|
$TIMESTAMP_RFC3161 = "http://timestamp.digicert.com"
|
||||||
|
|
||||||
|
Write-Host "Looking for Windows artifacts to sign in $ARTIFACT_DIR..."
|
||||||
|
|
||||||
|
# Find all Windows MSI files
|
||||||
|
$msiFiles = Get-ChildItem -Path $ARTIFACT_DIR -Filter "cloudflared-windows-*.msi" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($msiFiles.Count -eq 0) {
|
||||||
|
Write-Host "No Windows MSI files found in $ARTIFACT_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Found $($msiFiles.Count) file(s) to sign:"
|
||||||
|
foreach ($file in $msiFiles) {
|
||||||
|
Write-Host "Running azuretool sign for $($file.Name)"
|
||||||
|
azuresigntool.exe sign -kvu $env:KEY_VAULT_URL -kvi "$env:KEY_VAULT_CLIENT_ID" -kvs "$env:KEY_VAULT_SECRET" -kvc "$env:KEY_VAULT_CERTIFICATE" -kvt "$env:KEY_VAULT_TENANT_ID" -tr "$TIMESTAMP_RFC3161" -d "Cloudflare Tunnel Daemon" .\\$ARTIFACT_DIR\\$($file.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Signing process completed"
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
include:
|
||||||
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
|
|
||||||
|
###################################
|
||||||
|
### Defaults for Windows Builds ###
|
||||||
|
###################################
|
||||||
|
.windows-build-defaults: &windows-build-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-always]
|
||||||
|
tags:
|
||||||
|
- windows-x86
|
||||||
|
cache: {}
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
### Build Cloudflared Windows Binaries ###
|
||||||
|
##########################################
|
||||||
|
windows-build-cloudflared:
|
||||||
|
<<: *windows-build-defaults
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- powershell -ExecutionPolicy Bypass -File ".\.ci\scripts\windows\go-wrapper.ps1" "${GO_VERSION}" ".\.ci\scripts\windows\builds.ps1"
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts/*
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
### Load Environment Variables for Component Tests ###
|
||||||
|
######################################################
|
||||||
|
windows-load-env-variables:
|
||||||
|
stage: pre-build
|
||||||
|
extends: .component-tests
|
||||||
|
script:
|
||||||
|
- echo "COMPONENT_TESTS_CONFIG=$COMPONENT_TESTS_CONFIG" >> windows.env
|
||||||
|
- echo "COMPONENT_TESTS_CONFIG_CONTENT=$COMPONENT_TESTS_CONFIG_CONTENT" >> windows.env
|
||||||
|
- echo "DNS_API_TOKEN=$DNS_API_TOKEN" >> windows.env
|
||||||
|
# We have to encode the `COMPONENT_TESTS_ORIGINCERT` secret, because it content is a file, otherwise we can't export it using gitlab
|
||||||
|
- echo "COMPONENT_TESTS_ORIGINCERT=$(echo "$COMPONENT_TESTS_ORIGINCERT" | base64 -w0)" >> windows.env
|
||||||
|
- echo "KEY_VAULT_URL=$KEY_VAULT_URL" >> windows.env
|
||||||
|
- echo "KEY_VAULT_CLIENT_ID=$KEY_VAULT_CLIENT_ID" >> windows.env
|
||||||
|
- echo "KEY_VAULT_TENANT_ID=$KEY_VAULT_TENANT_ID" >> windows.env
|
||||||
|
- echo "KEY_VAULT_SECRET=$KEY_VAULT_SECRET" >> windows.env
|
||||||
|
- echo "KEY_VAULT_CERTIFICATE=$KEY_VAULT_CERTIFICATE" >> windows.env
|
||||||
|
variables:
|
||||||
|
COMPONENT_TESTS_CONFIG_CONTENT: Y2xvdWRmbGFyZWRfYmluYXJ5OiAuL2Nsb3VkZmxhcmVkLmV4ZQpjcmVkZW50aWFsc19maWxlOiBjcmVkLmpzb24Kb3JpZ2luY2VydDogY2VydC5wZW0Kem9uZV9kb21haW46IGFyZ290dW5uZWx0ZXN0LmNvbQp6b25lX3RhZzogNDg3OTZmMWU3MGJiNzY2OWMyOWJiNTFiYTI4MmJmNjU=
|
||||||
|
secrets:
|
||||||
|
KEY_VAULT_URL:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/azure_vault/app_info/key_vault_url@kv
|
||||||
|
file: false
|
||||||
|
KEY_VAULT_CLIENT_ID:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/azure_vault/app_info/key_vault_client_id@kv
|
||||||
|
file: false
|
||||||
|
KEY_VAULT_TENANT_ID:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/azure_vault/app_info/key_vault_tenant_id@kv
|
||||||
|
file: false
|
||||||
|
KEY_VAULT_SECRET:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/azure_vault/secret/key_vault_secret@kv
|
||||||
|
file: false
|
||||||
|
KEY_VAULT_CERTIFICATE:
|
||||||
|
vault: gitlab/cloudflare/tun/cloudflared/_dev/azure_vault/certificate_v2/key_vault_certificate@kv
|
||||||
|
file: false
|
||||||
|
artifacts:
|
||||||
|
access: 'none'
|
||||||
|
reports:
|
||||||
|
dotenv: windows.env
|
||||||
|
|
||||||
|
###################################
|
||||||
|
### Run Windows Component Tests ###
|
||||||
|
###################################
|
||||||
|
windows-component-tests-cloudflared:
|
||||||
|
<<: *windows-build-defaults
|
||||||
|
stage: test
|
||||||
|
needs: ["windows-load-env-variables"]
|
||||||
|
script:
|
||||||
|
# We have to decode the secret we encoded on the `windows-load-env-variables` job
|
||||||
|
- $env:COMPONENT_TESTS_ORIGINCERT = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:COMPONENT_TESTS_ORIGINCERT))
|
||||||
|
- powershell -ExecutionPolicy Bypass -File ".\.ci\scripts\windows\go-wrapper.ps1" "${GO_VERSION}" ".\.ci\scripts\windows\component-test.ps1"
|
||||||
|
artifacts:
|
||||||
|
reports:
|
||||||
|
junit: report.xml
|
||||||
|
|
||||||
|
################################
|
||||||
|
### Package Windows Binaries ###
|
||||||
|
################################
|
||||||
|
windows-package:
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
stage: package
|
||||||
|
needs:
|
||||||
|
- ci-image-get-image-ref
|
||||||
|
- windows-build-cloudflared
|
||||||
|
image: $BUILD_IMAGE
|
||||||
|
script:
|
||||||
|
- .ci/scripts/package-windows.sh
|
||||||
|
cache: {}
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts/*
|
||||||
|
|
||||||
|
#############################
|
||||||
|
### Sign Windows Binaries ###
|
||||||
|
#############################
|
||||||
|
windows-package-sign:
|
||||||
|
<<: *windows-build-defaults
|
||||||
|
rules:
|
||||||
|
- !reference [.default-rules, run-on-master]
|
||||||
|
stage: package
|
||||||
|
needs:
|
||||||
|
- windows-package
|
||||||
|
- windows-load-env-variables
|
||||||
|
script:
|
||||||
|
- powershell -ExecutionPolicy Bypass -File ".\.ci\scripts\windows\sign-msi.ps1"
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts/*
|
||||||
169
.gitlab-ci.yml
169
.gitlab-ci.yml
|
|
@ -1,131 +1,58 @@
|
||||||
stages: [build, release]
|
variables:
|
||||||
|
GO_VERSION: "go1.24.9"
|
||||||
|
GIT_DEPTH: "0"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
id_tokens:
|
id_tokens:
|
||||||
VAULT_ID_TOKEN:
|
VAULT_ID_TOKEN:
|
||||||
aud: https://vault.cfdata.org
|
aud: https://vault.cfdata.org
|
||||||
|
|
||||||
# This before_script is injected into every job that runs on master meaning that if there is no tag the step
|
stages: [sync, pre-build, build, validate, test, package, release, release-internal, review]
|
||||||
# will succeed but only write "No tag present - Skipping" to the console.
|
|
||||||
.check_tag:
|
|
||||||
before_script:
|
|
||||||
- |
|
|
||||||
# Check if there is a Git tag pointing to HEAD
|
|
||||||
echo "Tag found: $(git tag --points-at HEAD | grep .)"
|
|
||||||
if git tag --points-at HEAD | grep .; then
|
|
||||||
echo "Tag found: $(git tag --points-at HEAD | grep .)"
|
|
||||||
export "VERSION=$(git tag --points-at HEAD | grep .)"
|
|
||||||
else
|
|
||||||
echo "No tag present — skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
## A set of predefined rules to use on the different jobs
|
include:
|
||||||
.default_rules:
|
#####################################################
|
||||||
# Rules to run the job only on the master branch
|
########## Import Commons Configurations ############
|
||||||
run_on_master:
|
#####################################################
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- local: .ci/commons.gitlab-ci.yml
|
||||||
when: always
|
|
||||||
- when: never
|
|
||||||
# Rules to run the job only on branches that are not master. This is needed because for now
|
|
||||||
# we need to keep a similar behavior due to the integration with teamcity, which requires us
|
|
||||||
# to not trigger pipelines on tags and/or merge requests.
|
|
||||||
run_on_branch:
|
|
||||||
- if: $CI_COMMIT_TAG
|
|
||||||
when: never
|
|
||||||
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
|
||||||
when: always
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
# Stage 1: Build on every PR
|
########### Sync Repository with Github #############
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
build_cloudflared_macos: &build
|
- local: .ci/github.gitlab-ci.yml
|
||||||
stage: build
|
|
||||||
rules:
|
|
||||||
- !reference [.default_rules, run_on_branch]
|
|
||||||
tags:
|
|
||||||
- "macstadium-${RUNNER_ARCH}"
|
|
||||||
parallel:
|
|
||||||
matrix:
|
|
||||||
- RUNNER_ARCH: [arm, intel]
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- artifacts/*
|
|
||||||
script:
|
|
||||||
- '[ "${RUNNER_ARCH}" = "arm" ] && export TARGET_ARCH=arm64'
|
|
||||||
- '[ "${RUNNER_ARCH}" = "intel" ] && export TARGET_ARCH=amd64'
|
|
||||||
- ARCH=$(uname -m)
|
|
||||||
- echo ARCH=$ARCH - TARGET_ARCH=$TARGET_ARCH
|
|
||||||
- ./.teamcity/mac/install-cloudflare-go.sh
|
|
||||||
- export PATH="/tmp/go/bin:$PATH"
|
|
||||||
- BUILD_SCRIPT=.teamcity/mac/build.sh
|
|
||||||
- if [[ ! -x ${BUILD_SCRIPT} ]] ; then exit ; fi
|
|
||||||
- set -euo pipefail
|
|
||||||
- echo "Executing ${BUILD_SCRIPT}"
|
|
||||||
- exec ${BUILD_SCRIPT}
|
|
||||||
|
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
# Stage 1: Build and sign only on releases
|
############# Build or Fetch CI Image ###############
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
build_and_sign_cloudflared_macos:
|
- local: .ci/ci-image.gitlab-ci.yml
|
||||||
<<: *build
|
|
||||||
rules:
|
|
||||||
- !reference [.default_rules, run_on_master]
|
|
||||||
secrets:
|
|
||||||
APPLE_DEV_CA_CERT:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/apple_dev_ca_cert_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_CODE_SIGN_CERT:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_cert_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_CODE_SIGN_KEY:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_key_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_CODE_SIGN_PASS:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_code_sign_pass_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_INSTALLER_CERT:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_cert_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_INSTALLER_KEY:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_key_v2/data@kv
|
|
||||||
file: false
|
|
||||||
CFD_INSTALLER_PASS:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_branch/master/cfd_installer_pass_v2/data@kv
|
|
||||||
file: false
|
|
||||||
|
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
# Stage 2: Release to Github after building and signing
|
################## Linux Builds ###################
|
||||||
# -----------------------------------------------
|
#####################################################
|
||||||
release_cloudflared_macos_to_github:
|
- local: .ci/linux.gitlab-ci.yml
|
||||||
stage: release
|
|
||||||
image: docker-registry.cfdata.org/stash/tun/docker-images/cloudflared-ci/main:6-8616fe631b76-amd64@sha256:96f4fd05e66cec03e0864c1bcf09324c130d4728eef45ee994716da499183614
|
#####################################################
|
||||||
extends: .check_tag
|
################## Windows Builds ###################
|
||||||
dependencies:
|
#####################################################
|
||||||
- build_and_sign_cloudflared_macos
|
- local: .ci/windows.gitlab-ci.yml
|
||||||
rules:
|
|
||||||
- !reference [.default_rules, run_on_master]
|
#####################################################
|
||||||
cache:
|
################### macOS Builds ####################
|
||||||
paths:
|
#####################################################
|
||||||
- .cache/pip
|
- local: .ci/mac.gitlab-ci.yml
|
||||||
variables:
|
|
||||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
#####################################################
|
||||||
KV_NAMESPACE: 380e19aa04314648949b6ad841417ebe
|
################# Release Packages ##################
|
||||||
KV_ACCOUNT: 5ab4e9dfbd435d24068829fda0077963
|
#####################################################
|
||||||
secrets:
|
- local: .ci/release.gitlab-ci.yml
|
||||||
KV_API_TOKEN:
|
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_kv_api_token/data@kv
|
#####################################################
|
||||||
file: false
|
########## Release Packages Internally ##############
|
||||||
API_KEY:
|
#####################################################
|
||||||
vault: gitlab/cloudflare/tun/cloudflared/_dev/cfd_github_api_key/data@kv
|
- local: .ci/apt-internal.gitlab-ci.yml
|
||||||
file: false
|
|
||||||
script:
|
#####################################################
|
||||||
- python3 --version ; pip --version # For debugging
|
############## Manual Claude Review #################
|
||||||
- python3 -m venv venv
|
#####################################################
|
||||||
- source venv/bin/activate
|
- component: $CI_SERVER_FQDN/cloudflare/ci/ai/review@~latest
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55
|
inputs:
|
||||||
- echo $VERSION
|
whenToRun: "manual"
|
||||||
- echo $TAG_EXISTS
|
|
||||||
- echo "Running release because tag exists."
|
|
||||||
- make macos-release
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ linters:
|
||||||
- sloglint # Ensure consistent code style when using log/slog.
|
- sloglint # Ensure consistent code style when using log/slog.
|
||||||
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed.
|
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed.
|
||||||
- staticcheck # It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary.
|
- staticcheck # It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary.
|
||||||
- tenv # Tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17.
|
- usetesting # Reports uses of functions with replacement inside the testing package.
|
||||||
- testableexamples # Linter checks if examples are testable (have an expected output).
|
- testableexamples # Linter checks if examples are testable (have an expected output).
|
||||||
- testifylint # Checks usage of github.com/stretchr/testify.
|
- testifylint # Checks usage of github.com/stretchr/testify.
|
||||||
- tparallel # Tparallel detects inappropriate usage of t.Parallel() method in your Go test codes.
|
- tparallel # Tparallel detects inappropriate usage of t.Parallel() method in your Go test codes.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# !/usr/bin/env bash
|
|
||||||
|
|
||||||
cd /tmp
|
|
||||||
git clone -q https://github.com/cloudflare/go
|
|
||||||
cd go/src
|
|
||||||
# https://github.com/cloudflare/go/tree/af19da5605ca11f85776ef7af3384a02a315a52b is version go1.22.5-devel-cf
|
|
||||||
git checkout -q af19da5605ca11f85776ef7af3384a02a315a52b
|
|
||||||
./make.bash
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
|
|
||||||
# Relative path to working directory
|
|
||||||
$CloudflaredDirectory = "go\src\github.com\cloudflare\cloudflared"
|
|
||||||
|
|
||||||
cd $CloudflaredDirectory
|
|
||||||
|
|
||||||
Write-Output "Building for amd64"
|
|
||||||
$env:TARGET_OS = "windows"
|
|
||||||
$env:CGO_ENABLED = 1
|
|
||||||
$env:TARGET_ARCH = "amd64"
|
|
||||||
$env:Path = "$Env:Temp\go\bin;$($env:Path)"
|
|
||||||
|
|
||||||
go env
|
|
||||||
go version
|
|
||||||
|
|
||||||
& make cloudflared
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared for amd64" }
|
|
||||||
copy .\cloudflared.exe .\cloudflared-windows-amd64.exe
|
|
||||||
|
|
||||||
Write-Output "Building for 386"
|
|
||||||
$env:CGO_ENABLED = 0
|
|
||||||
$env:TARGET_ARCH = "386"
|
|
||||||
make cloudflared
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared for 386" }
|
|
||||||
copy .\cloudflared.exe .\cloudflared-windows-386.exe
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
|
|
||||||
$WorkingDirectory = Get-Location
|
|
||||||
$CloudflaredDirectory = "$WorkingDirectory\go\src\github.com\cloudflare\cloudflared"
|
|
||||||
|
|
||||||
go env
|
|
||||||
go version
|
|
||||||
|
|
||||||
$env:TARGET_OS = "windows"
|
|
||||||
$env:CGO_ENABLED = 1
|
|
||||||
$env:TARGET_ARCH = "amd64"
|
|
||||||
$env:Path = "$Env:Temp\go\bin;$($env:Path)"
|
|
||||||
|
|
||||||
python --version
|
|
||||||
python -m pip --version
|
|
||||||
|
|
||||||
cd $CloudflaredDirectory
|
|
||||||
|
|
||||||
go env
|
|
||||||
go version
|
|
||||||
|
|
||||||
Write-Output "Building cloudflared"
|
|
||||||
|
|
||||||
& make cloudflared
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Failed to build cloudflared" }
|
|
||||||
|
|
||||||
echo $LASTEXITCODE
|
|
||||||
|
|
||||||
Write-Output "Running unit tests"
|
|
||||||
|
|
||||||
# Not testing with race detector because of https://github.com/golang/go/issues/61058
|
|
||||||
# We already test it on other platforms
|
|
||||||
& go test -failfast -mod=vendor ./...
|
|
||||||
if ($LASTEXITCODE -ne 0) { throw "Failed unit tests" }
|
|
||||||
|
|
||||||
Write-Output "Running component tests"
|
|
||||||
|
|
||||||
python -m pip --disable-pip-version-check install --upgrade -r component-tests/requirements.txt --use-pep517
|
|
||||||
python component-tests/setup.py --type create
|
|
||||||
python -m pytest component-tests -o log_cli=true --log-cli-level=INFO
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
python component-tests/setup.py --type cleanup
|
|
||||||
throw "Failed component tests"
|
|
||||||
}
|
|
||||||
python component-tests/setup.py --type cleanup
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
|
|
||||||
Write-Output "Downloading cloudflare go..."
|
|
||||||
|
|
||||||
Set-Location "$Env:Temp"
|
|
||||||
|
|
||||||
git clone -q https://github.com/cloudflare/go
|
|
||||||
Write-Output "Building go..."
|
|
||||||
cd go/src
|
|
||||||
# https://github.com/cloudflare/go/tree/af19da5605ca11f85776ef7af3384a02a315a52b is version go1.22.5-devel-cf
|
|
||||||
git checkout -q af19da5605ca11f85776ef7af3384a02a315a52b
|
|
||||||
& ./make.bat
|
|
||||||
|
|
||||||
Write-Output "Installed"
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
$GoMsiVersion = "go1.22.5.windows-amd64.msi"
|
|
||||||
|
|
||||||
Write-Output "Downloading go installer..."
|
|
||||||
|
|
||||||
Set-Location "$Env:Temp"
|
|
||||||
|
|
||||||
(New-Object System.Net.WebClient).DownloadFile(
|
|
||||||
"https://go.dev/dl/$GoMsiVersion",
|
|
||||||
"$Env:Temp\$GoMsiVersion"
|
|
||||||
)
|
|
||||||
|
|
||||||
Write-Output "Installing go..."
|
|
||||||
Install-Package "$Env:Temp\$GoMsiVersion" -Force
|
|
||||||
|
|
||||||
# Go installer updates global $PATH
|
|
||||||
go env
|
|
||||||
|
|
||||||
Write-Output "Installed"
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Add vulnerability IDs (e.g., GO-2022-0450) to ignore, one per line.
|
||||||
|
# You can also add comments on the same line after the ID.
|
||||||
|
GO-2025-3942 # Ignore core-dns vulnerability since we will be removing the proxy-dns feature in the near future
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
## 2025.7.1
|
||||||
|
### Notices
|
||||||
|
- `cloudflared` will no longer officially support Debian and Ubuntu distros that reached end-of-life: `buster`, `bullseye`, `impish`, `trusty`.
|
||||||
|
|
||||||
## 2025.1.1
|
## 2025.1.1
|
||||||
### New Features
|
### New Features
|
||||||
- This release introduces the use of new Post Quantum curves and the ability to use Post Quantum curves when running tunnels with the QUIC protocol this applies to non-FIPS and FIPS builds.
|
- This release introduces the use of new Post Quantum curves and the ability to use Post Quantum curves when running tunnels with the QUIC protocol this applies to non-FIPS and FIPS builds.
|
||||||
|
|
|
||||||
13
Dockerfile
13
Dockerfile
|
|
@ -1,7 +1,7 @@
|
||||||
# use a builder image for building cloudflare
|
# use a builder image for building cloudflare
|
||||||
ARG TARGET_GOOS
|
ARG TARGET_GOOS
|
||||||
ARG TARGET_GOARCH
|
ARG TARGET_GOARCH
|
||||||
FROM golang:1.22.10 as builder
|
FROM golang:1.24.9 AS builder
|
||||||
ENV GO111MODULE=on \
|
ENV GO111MODULE=on \
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
TARGET_GOOS=${TARGET_GOOS} \
|
TARGET_GOOS=${TARGET_GOOS} \
|
||||||
|
|
@ -16,10 +16,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||||
# copy our sources into the builder image
|
# copy our sources into the builder image
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN .teamcity/install-cloudflare-go.sh
|
|
||||||
|
|
||||||
# compile cloudflared
|
# compile cloudflared
|
||||||
RUN PATH="/tmp/go/bin:$PATH" make cloudflared
|
RUN make cloudflared
|
||||||
|
|
||||||
# use a distroless base image with glibc
|
# use a distroless base image with glibc
|
||||||
FROM gcr.io/distroless/base-debian12:nonroot
|
FROM gcr.io/distroless/base-debian12:nonroot
|
||||||
|
|
@ -29,8 +27,11 @@ LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared
|
||||||
# copy our compiled binary
|
# copy our compiled binary
|
||||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||||
|
|
||||||
# run as non-privileged user
|
# run as nonroot user
|
||||||
USER nonroot
|
# We need to use numeric user id's because Kubernetes doesn't support strings:
|
||||||
|
# https://github.com/kubernetes/kubernetes/blob/v1.33.2/pkg/kubelet/kuberuntime/security_context_others.go#L49
|
||||||
|
# The `nonroot` user maps to `65532`, from: https://github.com/GoogleContainerTools/distroless/blob/main/common/variables.bzl#L18
|
||||||
|
USER 65532:65532
|
||||||
|
|
||||||
# command / entrypoint of container
|
# command / entrypoint of container
|
||||||
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# use a builder image for building cloudflare
|
# use a builder image for building cloudflare
|
||||||
FROM golang:1.22.10 as builder
|
FROM golang:1.24.9 AS builder
|
||||||
ENV GO111MODULE=on \
|
ENV GO111MODULE=on \
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
||||||
|
|
@ -11,10 +11,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||||
# copy our sources into the builder image
|
# copy our sources into the builder image
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN .teamcity/install-cloudflare-go.sh
|
|
||||||
|
|
||||||
# compile cloudflared
|
# compile cloudflared
|
||||||
RUN GOOS=linux GOARCH=amd64 PATH="/tmp/go/bin:$PATH" make cloudflared
|
RUN GOOS=linux GOARCH=amd64 make cloudflared
|
||||||
|
|
||||||
# use a distroless base image with glibc
|
# use a distroless base image with glibc
|
||||||
FROM gcr.io/distroless/base-debian12:nonroot
|
FROM gcr.io/distroless/base-debian12:nonroot
|
||||||
|
|
@ -24,8 +22,11 @@ LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared
|
||||||
# copy our compiled binary
|
# copy our compiled binary
|
||||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||||
|
|
||||||
# run as non-privileged user
|
# run as nonroot user
|
||||||
USER nonroot
|
# We need to use numeric user id's because Kubernetes doesn't support strings:
|
||||||
|
# https://github.com/kubernetes/kubernetes/blob/v1.33.2/pkg/kubelet/kuberuntime/security_context_others.go#L49
|
||||||
|
# The `nonroot` user maps to `65532`, from: https://github.com/GoogleContainerTools/distroless/blob/main/common/variables.bzl#L18
|
||||||
|
USER 65532:65532
|
||||||
|
|
||||||
# command / entrypoint of container
|
# command / entrypoint of container
|
||||||
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# use a builder image for building cloudflare
|
# use a builder image for building cloudflare
|
||||||
FROM golang:1.22.10 as builder
|
FROM golang:1.24.9 AS builder
|
||||||
ENV GO111MODULE=on \
|
ENV GO111MODULE=on \
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
# the CONTAINER_BUILD envvar is used set github.com/cloudflare/cloudflared/metrics.Runtime=virtual
|
||||||
|
|
@ -11,10 +11,8 @@ WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||||
# copy our sources into the builder image
|
# copy our sources into the builder image
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN .teamcity/install-cloudflare-go.sh
|
|
||||||
|
|
||||||
# compile cloudflared
|
# compile cloudflared
|
||||||
RUN GOOS=linux GOARCH=arm64 PATH="/tmp/go/bin:$PATH" make cloudflared
|
RUN GOOS=linux GOARCH=arm64 make cloudflared
|
||||||
|
|
||||||
# use a distroless base image with glibc
|
# use a distroless base image with glibc
|
||||||
FROM gcr.io/distroless/base-debian12:nonroot-arm64
|
FROM gcr.io/distroless/base-debian12:nonroot-arm64
|
||||||
|
|
@ -24,8 +22,11 @@ LABEL org.opencontainers.image.source="https://github.com/cloudflare/cloudflared
|
||||||
# copy our compiled binary
|
# copy our compiled binary
|
||||||
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
COPY --from=builder --chown=nonroot /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
||||||
|
|
||||||
# run as non-privileged user
|
# run as nonroot user
|
||||||
USER nonroot
|
# We need to use numeric user id's because Kubernetes doesn't support strings:
|
||||||
|
# https://github.com/kubernetes/kubernetes/blob/v1.33.2/pkg/kubelet/kuberuntime/security_context_others.go#L49
|
||||||
|
# The `nonroot` user maps to `65532`, from: https://github.com/GoogleContainerTools/distroless/blob/main/common/variables.bzl#L18
|
||||||
|
USER 65532:65532
|
||||||
|
|
||||||
# command / entrypoint of container
|
# command / entrypoint of container
|
||||||
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
ENTRYPOINT ["cloudflared", "--no-autoupdate"]
|
||||||
|
|
|
||||||
78
Makefile
78
Makefile
|
|
@ -24,7 +24,13 @@ else
|
||||||
DEB_PACKAGE_NAME := $(BINARY_NAME)
|
DEB_PACKAGE_NAME := $(BINARY_NAME)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
DATE := $(shell date -u -r RELEASE_NOTES '+%Y-%m-%d-%H%M UTC')
|
# Use git in windows since we don't have access to the `date` tool
|
||||||
|
ifeq ($(TARGET_OS), windows)
|
||||||
|
DATE := $(shell git log -1 --format="%ad" --date=format-local:'%Y-%m-%dT%H:%M UTC' -- RELEASE_NOTES)
|
||||||
|
else
|
||||||
|
DATE := $(shell date -u -r RELEASE_NOTES '+%Y-%m-%d-%H:%M UTC')
|
||||||
|
endif
|
||||||
|
|
||||||
VERSION_FLAGS := -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"
|
VERSION_FLAGS := -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"
|
||||||
ifdef PACKAGE_MANAGER
|
ifdef PACKAGE_MANAGER
|
||||||
VERSION_FLAGS := $(VERSION_FLAGS) -X "github.com/cloudflare/cloudflared/cmd/cloudflared/updater.BuiltForPackageManager=$(PACKAGE_MANAGER)"
|
VERSION_FLAGS := $(VERSION_FLAGS) -X "github.com/cloudflare/cloudflared/cmd/cloudflared/updater.BuiltForPackageManager=$(PACKAGE_MANAGER)"
|
||||||
|
|
@ -56,8 +62,6 @@ PACKAGE_DIR := $(CURDIR)/packaging
|
||||||
PREFIX := /usr
|
PREFIX := /usr
|
||||||
INSTALL_BINDIR := $(PREFIX)/bin/
|
INSTALL_BINDIR := $(PREFIX)/bin/
|
||||||
INSTALL_MANDIR := $(PREFIX)/share/man/man1/
|
INSTALL_MANDIR := $(PREFIX)/share/man/man1/
|
||||||
CF_GO_PATH := /tmp/go
|
|
||||||
PATH := $(CF_GO_PATH)/bin:$(PATH)
|
|
||||||
|
|
||||||
LOCAL_ARCH ?= $(shell uname -m)
|
LOCAL_ARCH ?= $(shell uname -m)
|
||||||
ifneq ($(GOARCH),)
|
ifneq ($(GOARCH),)
|
||||||
|
|
@ -66,6 +70,8 @@ else ifeq ($(LOCAL_ARCH),x86_64)
|
||||||
TARGET_ARCH ?= amd64
|
TARGET_ARCH ?= amd64
|
||||||
else ifeq ($(LOCAL_ARCH),amd64)
|
else ifeq ($(LOCAL_ARCH),amd64)
|
||||||
TARGET_ARCH ?= amd64
|
TARGET_ARCH ?= amd64
|
||||||
|
else ifeq ($(LOCAL_ARCH),386)
|
||||||
|
TARGET_ARCH ?= 386
|
||||||
else ifeq ($(LOCAL_ARCH),i686)
|
else ifeq ($(LOCAL_ARCH),i686)
|
||||||
TARGET_ARCH ?= amd64
|
TARGET_ARCH ?= amd64
|
||||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
|
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
|
||||||
|
|
@ -122,6 +128,8 @@ endif
|
||||||
#for FIPS compliance, FPM defaults to MD5.
|
#for FIPS compliance, FPM defaults to MD5.
|
||||||
RPM_DIGEST := --rpm-digest sha256
|
RPM_DIGEST := --rpm-digest sha256
|
||||||
|
|
||||||
|
GO_TEST_LOG_OUTPUT = /tmp/gotest.log
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: cloudflared test
|
all: cloudflared test
|
||||||
|
|
||||||
|
|
@ -129,6 +137,10 @@ all: cloudflared test
|
||||||
clean:
|
clean:
|
||||||
go clean
|
go clean
|
||||||
|
|
||||||
|
.PHONY: vulncheck
|
||||||
|
vulncheck:
|
||||||
|
@./.ci/scripts/vuln-check.sh
|
||||||
|
|
||||||
.PHONY: cloudflared
|
.PHONY: cloudflared
|
||||||
cloudflared:
|
cloudflared:
|
||||||
ifeq ($(FIPS), true)
|
ifeq ($(FIPS), true)
|
||||||
|
|
@ -150,11 +162,9 @@ generate-docker-version:
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: vet
|
test: vet
|
||||||
ifndef CI
|
$Q go test -json -v -mod=vendor -race $(LDFLAGS) ./... 2>&1 | tee $(GO_TEST_LOG_OUTPUT)
|
||||||
go test -v -mod=vendor -race $(LDFLAGS) ./...
|
ifneq ($(FIPS), true)
|
||||||
else
|
@go run -mod=readonly github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest -input $(GO_TEST_LOG_OUTPUT)
|
||||||
@mkdir -p .cover
|
|
||||||
go test -v -mod=vendor -race $(LDFLAGS) -coverprofile=".cover/c.out" ./...
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: cover
|
.PHONY: cover
|
||||||
|
|
@ -172,26 +182,17 @@ fuzz:
|
||||||
@go test -fuzz=FuzzIPDecoder -fuzztime=600s ./packet
|
@go test -fuzz=FuzzIPDecoder -fuzztime=600s ./packet
|
||||||
@go test -fuzz=FuzzICMPDecoder -fuzztime=600s ./packet
|
@go test -fuzz=FuzzICMPDecoder -fuzztime=600s ./packet
|
||||||
@go test -fuzz=FuzzSessionWrite -fuzztime=600s ./quic/v3
|
@go test -fuzz=FuzzSessionWrite -fuzztime=600s ./quic/v3
|
||||||
@go test -fuzz=FuzzSessionServe -fuzztime=600s ./quic/v3
|
@go test -fuzz=FuzzSessionRead -fuzztime=600s ./quic/v3
|
||||||
@go test -fuzz=FuzzRegistrationDatagram -fuzztime=600s ./quic/v3
|
@go test -fuzz=FuzzRegistrationDatagram -fuzztime=600s ./quic/v3
|
||||||
@go test -fuzz=FuzzPayloadDatagram -fuzztime=600s ./quic/v3
|
@go test -fuzz=FuzzPayloadDatagram -fuzztime=600s ./quic/v3
|
||||||
@go test -fuzz=FuzzRegistrationResponseDatagram -fuzztime=600s ./quic/v3
|
@go test -fuzz=FuzzRegistrationResponseDatagram -fuzztime=600s ./quic/v3
|
||||||
@go test -fuzz=FuzzNewIdentity -fuzztime=600s ./tracing
|
@go test -fuzz=FuzzNewIdentity -fuzztime=600s ./tracing
|
||||||
@go test -fuzz=FuzzNewAccessValidator -fuzztime=600s ./validation
|
@go test -fuzz=FuzzNewAccessValidator -fuzztime=600s ./validation
|
||||||
|
|
||||||
.PHONY: install-go
|
|
||||||
install-go:
|
|
||||||
rm -rf ${CF_GO_PATH}
|
|
||||||
./.teamcity/install-cloudflare-go.sh
|
|
||||||
|
|
||||||
.PHONY: cleanup-go
|
|
||||||
cleanup-go:
|
|
||||||
rm -rf ${CF_GO_PATH}
|
|
||||||
|
|
||||||
cloudflared.1: cloudflared_man_template
|
cloudflared.1: cloudflared_man_template
|
||||||
sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' cloudflared_man_template > cloudflared.1
|
sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' cloudflared_man_template > cloudflared.1
|
||||||
|
|
||||||
install: install-go cloudflared cloudflared.1 cleanup-go
|
install: cloudflared cloudflared.1
|
||||||
mkdir -p $(DESTDIR)$(INSTALL_BINDIR) $(DESTDIR)$(INSTALL_MANDIR)
|
mkdir -p $(DESTDIR)$(INSTALL_BINDIR) $(DESTDIR)$(INSTALL_MANDIR)
|
||||||
install -m755 cloudflared $(DESTDIR)$(INSTALL_BINDIR)/cloudflared
|
install -m755 cloudflared $(DESTDIR)$(INSTALL_BINDIR)/cloudflared
|
||||||
install -m644 cloudflared.1 $(DESTDIR)$(INSTALL_MANDIR)/cloudflared.1
|
install -m644 cloudflared.1 $(DESTDIR)$(INSTALL_MANDIR)/cloudflared.1
|
||||||
|
|
@ -220,10 +221,6 @@ cloudflared-deb: cloudflared cloudflared.1
|
||||||
cloudflared-rpm: cloudflared cloudflared.1
|
cloudflared-rpm: cloudflared cloudflared.1
|
||||||
$(call build_package,rpm)
|
$(call build_package,rpm)
|
||||||
|
|
||||||
.PHONY: cloudflared-pkg
|
|
||||||
cloudflared-pkg: cloudflared cloudflared.1
|
|
||||||
$(call build_package,osxpkg)
|
|
||||||
|
|
||||||
.PHONY: cloudflared-msi
|
.PHONY: cloudflared-msi
|
||||||
cloudflared-msi:
|
cloudflared-msi:
|
||||||
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
|
wixl --define Version=$(VERSION) --define Path=$(EXECUTABLE_PATH) --output cloudflared-$(VERSION)-$(TARGET_ARCH).msi cloudflared.wxs
|
||||||
|
|
@ -234,17 +231,18 @@ github-release-dryrun:
|
||||||
|
|
||||||
.PHONY: github-release
|
.PHONY: github-release
|
||||||
github-release:
|
github-release:
|
||||||
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
|
|
||||||
python3 github_message.py --release-version $(VERSION)
|
|
||||||
|
|
||||||
.PHONY: macos-release
|
|
||||||
macos-release:
|
|
||||||
python3 github_release.py --path $(PWD)/artifacts/ --release-version $(VERSION)
|
python3 github_release.py --path $(PWD)/artifacts/ --release-version $(VERSION)
|
||||||
|
python3 github_message.py --release-version $(VERSION)
|
||||||
|
|
||||||
.PHONY: r2-linux-release
|
.PHONY: r2-linux-release
|
||||||
r2-linux-release:
|
r2-linux-release:
|
||||||
python3 ./release_pkgs.py
|
python3 ./release_pkgs.py
|
||||||
|
|
||||||
|
.PHONY: r2-next-linux-release
|
||||||
|
# Publishes to a separate R2 repository during GPG key rollover, using dual-key signing.
|
||||||
|
r2-next-linux-release:
|
||||||
|
python3 ./release_pkgs.py --upload-repo-file
|
||||||
|
|
||||||
.PHONY: capnp
|
.PHONY: capnp
|
||||||
capnp:
|
capnp:
|
||||||
which capnp # https://capnproto.org/install.html
|
which capnp # https://capnproto.org/install.html
|
||||||
|
|
@ -253,7 +251,7 @@ capnp:
|
||||||
|
|
||||||
.PHONY: vet
|
.PHONY: vet
|
||||||
vet:
|
vet:
|
||||||
go vet -mod=vendor github.com/cloudflare/cloudflared/...
|
$Q go vet -mod=vendor github.com/cloudflare/cloudflared/...
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
|
|
@ -262,7 +260,7 @@ fmt:
|
||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@./fmt-check.sh
|
@./.ci/scripts/fmt-check.sh
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
|
|
@ -271,3 +269,23 @@ lint:
|
||||||
.PHONY: mocks
|
.PHONY: mocks
|
||||||
mocks:
|
mocks:
|
||||||
go generate mocks/mockgen.go
|
go generate mocks/mockgen.go
|
||||||
|
|
||||||
|
.PHONY: ci-build
|
||||||
|
ci-build:
|
||||||
|
@GOOS=linux GOARCH=amd64 $(MAKE) cloudflared
|
||||||
|
@mkdir -p artifacts
|
||||||
|
@mv cloudflared artifacts/cloudflared
|
||||||
|
|
||||||
|
.PHONY: ci-fips-build
|
||||||
|
ci-fips-build:
|
||||||
|
@FIPS=true GOOS=linux GOARCH=amd64 $(MAKE) cloudflared
|
||||||
|
@mkdir -p artifacts
|
||||||
|
@mv cloudflared artifacts/cloudflared
|
||||||
|
|
||||||
|
.PHONY: ci-test
|
||||||
|
ci-test: fmt-check lint test
|
||||||
|
@go run -mod=readonly github.com/jstemmer/go-junit-report/v2@latest -in $(GO_TEST_LOG_OUTPUT) -parser gojson -out report.xml -set-exit-code
|
||||||
|
|
||||||
|
.PHONY: ci-fips-test
|
||||||
|
ci-fips-test:
|
||||||
|
@FIPS=true $(MAKE) ci-test
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -3,14 +3,14 @@
|
||||||
Contains the command-line client for Cloudflare Tunnel, a tunneling daemon that proxies traffic from the Cloudflare network to your origins.
|
Contains the command-line client for Cloudflare Tunnel, a tunneling daemon that proxies traffic from the Cloudflare network to your origins.
|
||||||
This daemon sits between Cloudflare network and your origin (e.g. a webserver). Cloudflare attracts client requests and sends them to you
|
This daemon sits between Cloudflare network and your origin (e.g. a webserver). Cloudflare attracts client requests and sends them to you
|
||||||
via this daemon, without requiring you to poke holes on your firewall --- your origin can remain as closed as possible.
|
via this daemon, without requiring you to poke holes on your firewall --- your origin can remain as closed as possible.
|
||||||
Extensive documentation can be found in the [Cloudflare Tunnel section](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps) of the Cloudflare Docs.
|
Extensive documentation can be found in the [Cloudflare Tunnel section](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel) of the Cloudflare Docs.
|
||||||
All usages related with proxying to your origins are available under `cloudflared tunnel help`.
|
All usages related with proxying to your origins are available under `cloudflared tunnel help`.
|
||||||
|
|
||||||
You can also use `cloudflared` to access Tunnel origins (that are protected with `cloudflared tunnel`) for TCP traffic
|
You can also use `cloudflared` to access Tunnel origins (that are protected with `cloudflared tunnel`) for TCP traffic
|
||||||
at Layer 4 (i.e., not HTTP/websocket), which is relevant for use cases such as SSH, RDP, etc.
|
at Layer 4 (i.e., not HTTP/websocket), which is relevant for use cases such as SSH, RDP, etc.
|
||||||
Such usages are available under `cloudflared access help`.
|
Such usages are available under `cloudflared access help`.
|
||||||
|
|
||||||
You can instead use [WARP client](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/private-networks)
|
You can instead use [WARP client](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/warp/)
|
||||||
to access private origins behind Tunnels for Layer 4 traffic without requiring `cloudflared access` commands on the client side.
|
to access private origins behind Tunnels for Layer 4 traffic without requiring `cloudflared access` commands on the client side.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,41 +19,41 @@ to access private origins behind Tunnels for Layer 4 traffic without requiring `
|
||||||
Before you use Cloudflare Tunnel, you'll need to complete a few steps in the Cloudflare dashboard: you need to add a
|
Before you use Cloudflare Tunnel, you'll need to complete a few steps in the Cloudflare dashboard: you need to add a
|
||||||
website to your Cloudflare account. Note that today it is possible to use Tunnel without a website (e.g. for private
|
website to your Cloudflare account. Note that today it is possible to use Tunnel without a website (e.g. for private
|
||||||
routing), but for legacy reasons this requirement is still necessary:
|
routing), but for legacy reasons this requirement is still necessary:
|
||||||
1. [Add a website to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/201720164-Creating-a-Cloudflare-account-and-adding-a-website)
|
1. [Add a website to Cloudflare](https://developers.cloudflare.com/fundamentals/manage-domains/add-site/)
|
||||||
2. [Change your domain nameservers to Cloudflare](https://support.cloudflare.com/hc/en-us/articles/205195708)
|
2. [Change your domain nameservers to Cloudflare](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/)
|
||||||
|
|
||||||
|
|
||||||
## Installing `cloudflared`
|
## Installing `cloudflared`
|
||||||
|
|
||||||
Downloads are available as standalone binaries, a Docker image, and Debian, RPM, and Homebrew packages. You can also find releases [here](https://github.com/cloudflare/cloudflared/releases) on the `cloudflared` GitHub repository.
|
Downloads are available as standalone binaries, a Docker image, and Debian, RPM, and Homebrew packages. You can also find releases [here](https://github.com/cloudflare/cloudflared/releases) on the `cloudflared` GitHub repository.
|
||||||
|
|
||||||
* You can [install on macOS](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#macos) via Homebrew or by downloading the [latest Darwin amd64 release](https://github.com/cloudflare/cloudflared/releases)
|
* You can [install on macOS](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/#macos) via Homebrew or by downloading the [latest Darwin amd64 release](https://github.com/cloudflare/cloudflared/releases)
|
||||||
* Binaries, Debian, and RPM packages for Linux [can be found here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#linux)
|
* Binaries, Debian, and RPM packages for Linux [can be found here](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/#linux)
|
||||||
* A Docker image of `cloudflared` is [available on DockerHub](https://hub.docker.com/r/cloudflare/cloudflared)
|
* A Docker image of `cloudflared` is [available on DockerHub](https://hub.docker.com/r/cloudflare/cloudflared)
|
||||||
* You can install on Windows machines with the [steps here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation#windows)
|
* You can install on Windows machines with the [steps here](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/#windows)
|
||||||
* To build from source, first you need to download the go toolchain by running `./.teamcity/install-cloudflare-go.sh` and follow the output. Then you can run `make cloudflared`
|
* To build from source, install the required version of go, mentioned in the [Development](#development) section below. Then you can run `make cloudflared`.
|
||||||
|
|
||||||
User documentation for Cloudflare Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
|
User documentation for Cloudflare Tunnel can be found at https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/
|
||||||
|
|
||||||
|
|
||||||
## Creating Tunnels and routing traffic
|
## Creating Tunnels and routing traffic
|
||||||
|
|
||||||
Once installed, you can authenticate `cloudflared` into your Cloudflare account and begin creating Tunnels to serve traffic to your origins.
|
Once installed, you can authenticate `cloudflared` into your Cloudflare account and begin creating Tunnels to serve traffic to your origins.
|
||||||
|
|
||||||
* Create a Tunnel with [these instructions](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/)
|
* Create a Tunnel with [these instructions](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/)
|
||||||
* Route traffic to that Tunnel:
|
* Route traffic to that Tunnel:
|
||||||
* Via public [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns)
|
* Via public [DNS records in Cloudflare](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/routing-to-tunnel/dns/)
|
||||||
* Or via a public hostname guided by a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb)
|
* Or via a public hostname guided by a [Cloudflare Load Balancer](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/routing-to-tunnel/public-load-balancers/)
|
||||||
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/)
|
* Or from [WARP client private traffic](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/private-net/)
|
||||||
|
|
||||||
|
|
||||||
## TryCloudflare
|
## TryCloudflare
|
||||||
|
|
||||||
Want to test Cloudflare Tunnel before adding a website to Cloudflare? You can do so with TryCloudflare using the documentation [available here](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/).
|
Want to test Cloudflare Tunnel before adding a website to Cloudflare? You can do so with TryCloudflare using the documentation [available here](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/).
|
||||||
|
|
||||||
## Deprecated versions
|
## Deprecated versions
|
||||||
|
|
||||||
Cloudflare currently supports versions of cloudflared that are **within one year** of the most recent release. Breaking changes unrelated to feature availability may be introduced that will impact versions released more than one year ago. You can read more about upgrading cloudflared in our [developer documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/#updating-cloudflared).
|
Cloudflare currently supports versions of cloudflared that are **within one year** of the most recent release. Breaking changes unrelated to feature availability may be introduced that will impact versions released more than one year ago. You can read more about upgrading cloudflared in our [developer documentation](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/update-cloudflared/).
|
||||||
|
|
||||||
For example, as of January 2023 Cloudflare will support cloudflared version 2023.1.1 to cloudflared 2022.1.1.
|
For example, as of January 2023 Cloudflare will support cloudflared version 2023.1.1 to cloudflared 2022.1.1.
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ For example, as of January 2023 Cloudflare will support cloudflared version 2023
|
||||||
### Requirements
|
### Requirements
|
||||||
- [GNU Make](https://www.gnu.org/software/make/)
|
- [GNU Make](https://www.gnu.org/software/make/)
|
||||||
- [capnp](https://capnproto.org/install.html)
|
- [capnp](https://capnproto.org/install.html)
|
||||||
- [cloudflare go toolchain](https://github.com/cloudflare/go)
|
- [go >= 1.24](https://go.dev/doc/install)
|
||||||
- Optional tools:
|
- Optional tools:
|
||||||
- [capnpc-go](https://pkg.go.dev/zombiezen.com/go/capnproto2/capnpc-go)
|
- [capnpc-go](https://pkg.go.dev/zombiezen.com/go/capnproto2/capnpc-go)
|
||||||
- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports)
|
- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,86 @@
|
||||||
2025.4.1
|
2025.11.1
|
||||||
|
- 2025-11-07 TUN-9800: Fix docker hub push step
|
||||||
|
|
||||||
|
2025.11.0
|
||||||
|
- 2025-11-06 TUN-9863: Introduce Code Signing for Windows Builds
|
||||||
|
- 2025-11-06 TUN-9800: Prefix gitlab steps with operating system
|
||||||
|
- 2025-11-04 chore: Update cloudflared signing key name in index.html
|
||||||
|
- 2025-10-31 chore: add claude review
|
||||||
|
- 2025-10-31 Chore: Update documentation links in README
|
||||||
|
- 2025-10-31 TUN-9800: Add pipelines for linux packaging
|
||||||
|
|
||||||
|
2025.10.1
|
||||||
|
- 2025-10-30 chore: Update ci image to use goboring 1.24.9
|
||||||
|
- 2025-10-28 TUN-9849: Add cf-proxy-* to control response headers
|
||||||
|
- 2025-10-24 TUN-9961: Add pkg.cloudflared.com index.html to git repo
|
||||||
|
- 2025-10-23 TUN-9954: Update from go1.24.6 to go1.24.9
|
||||||
|
- 2025-10-23 Fix systemd service installation hanging
|
||||||
|
- 2025-10-21 TUN-9941: Use new GPG key for RPM builds
|
||||||
|
- 2025-10-21 TUN-9941: Fix typo causing r2-release-next deployment to fail
|
||||||
|
- 2025-10-21 TUN-9941: Lookup correct key for RPM signature
|
||||||
|
- 2025-10-15 TUN-9919: Make RPM postinstall scriplet idempotent
|
||||||
|
- 2025-10-14 TUN-9916: Fix the cloudflared binary path used in the component test
|
||||||
|
|
||||||
|
2025.10.0
|
||||||
|
- 2025-10-14 chore: Fix upload of RPM repo file during double signing
|
||||||
|
- 2025-10-13 TUN-9882: Bump datagram v3 write channel capacity
|
||||||
|
- 2025-10-10 chore: Fix import of GPG keys when two keys are provided
|
||||||
|
- 2025-10-10 chore: Fix parameter order when uploading RPM .repo file to R2
|
||||||
|
- 2025-10-10 TUN-9883: Add new datagram v3 feature flag
|
||||||
|
- 2025-10-09 chore: Force usage of go-boring 1.24
|
||||||
|
- 2025-10-08 TUN-9882: Improve metrics for datagram v3
|
||||||
|
- 2025-10-07 GRC-16749: Add fedramp tags to catalog
|
||||||
|
- 2025-10-07 TUN-9882: Add buffers for UDP and ICMP datagrams in datagram v3
|
||||||
|
- 2025-10-07 TUN-9882: Add write deadline for UDP origin writes
|
||||||
|
- 2025-09-29 TUN-9776: Support signing Debian packages with two keys for rollover
|
||||||
|
- 2025-09-22 TUN-9800: Add pipeline to sync between gitlab and github repos
|
||||||
|
|
||||||
|
2025.9.1
|
||||||
|
- 2025-09-22 TUN-9855: Create script to ignore vulnerabilities from govuln check
|
||||||
|
- 2025-09-19 TUN-9852: Remove fmt.Println from cloudflared access command
|
||||||
|
|
||||||
|
2025.9.0
|
||||||
|
- 2025-09-15 TUN-9820: Add support for FedRAMP in originRequest Access config
|
||||||
|
- 2025-09-11 TUN-9800: Migrate cloudflared-ci pipelines to Gitlab CI
|
||||||
|
- 2025-09-04 TUN-9803: Add windows builds to gitlab-ci
|
||||||
|
- 2025-08-27 TUN-9755: Set endpoint in tunnel credentials when generating locally managed tunnel with a Fed token
|
||||||
|
|
||||||
|
2025.8.1
|
||||||
|
- 2025-08-19 AUTH-7480 update fed callback url for login helper
|
||||||
|
- 2025-08-19 CUSTESC-53681: Correct QUIC connection management for datagram handlers
|
||||||
|
- 2025-08-12 AUTH-7260: Add support for login interstitial auto closure
|
||||||
|
|
||||||
|
2025.8.0
|
||||||
|
- 2025-08-07 vuln: Fix GO-2025-3770 vulnerability
|
||||||
|
- 2025-07-23 TUN-9583: set proper url and hostname for cloudflared tail command
|
||||||
|
- 2025-07-07 TUN-9542: Remove unsupported Debian-based releases
|
||||||
|
|
||||||
|
2025.7.0
|
||||||
|
- 2025-07-03 TUN-9540: Use numeric user id for Dockerfiles
|
||||||
|
- 2025-07-01 TUN-9161: Remove P256Kyber768Draft00PQKex curve from nonFips curve preferences
|
||||||
|
- 2025-07-01 TUN-9531: Bump go-boring from 1.24.2 to 1.24.4
|
||||||
|
- 2025-07-01 TUN-9511: Add metrics for virtual DNS origin
|
||||||
|
- 2025-06-30 TUN-9470: Add OriginDialerService to include TCP
|
||||||
|
- 2025-06-30 TUN-9473: Add --dns-resolver-addrs flag
|
||||||
|
- 2025-06-27 TUN-9472: Add virtual DNS service
|
||||||
|
- 2025-06-23 TUN-9469: Centralize UDP origin proxy dialing as ingress service
|
||||||
|
|
||||||
|
2025.6.1
|
||||||
|
- 2025-06-16 TUN-9467: add vulncheck to cloudflared
|
||||||
|
- 2025-06-16 TUN-9495: Remove references to cloudflare-go
|
||||||
|
- 2025-06-16 TUN-9371: Add logging format as JSON
|
||||||
|
- 2025-06-12 TUN-9467: bump coredns to solve CVE
|
||||||
|
|
||||||
|
2025.6.0
|
||||||
|
- 2025-06-06 TUN-9016: update go to 1.24
|
||||||
|
- 2025-06-05 TUN-9171: Use `is_default_network` instead of `is_default` to create vnet's
|
||||||
|
|
||||||
|
2025.5.0
|
||||||
|
- 2025-05-14 TUN-9319: Add dynamic loading of features to connections via ConnectionOptionsSnapshot
|
||||||
|
- 2025-05-13 TUN-9322: Add metric for unsupported RPC commands for datagram v3
|
||||||
|
- 2025-05-07 TUN-9291: Remove dynamic reloading of features for datagram v3
|
||||||
|
|
||||||
|
2025.4.2
|
||||||
- 2025-04-30 chore: Do not use gitlab merge request pipelines
|
- 2025-04-30 chore: Do not use gitlab merge request pipelines
|
||||||
- 2025-04-30 DEVTOOLS-16383: Create GitlabCI pipeline to release Mac builds
|
- 2025-04-30 DEVTOOLS-16383: Create GitlabCI pipeline to release Mac builds
|
||||||
- 2025-04-24 TUN-9255: Improve flush on write conditions in http2 tunnel type to match what is done on the edge
|
- 2025-04-24 TUN-9255: Improve flush on write conditions in http2 tunnel type to match what is done on the edge
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
VERSION=$(git describe --tags --always --match "[0-9][0-9][0-9][0-9].*.*")
|
|
||||||
echo $VERSION
|
|
||||||
|
|
||||||
# Disable FIPS module in go-boring
|
|
||||||
export GOEXPERIMENT=noboringcrypto
|
|
||||||
export CGO_ENABLED=0
|
|
||||||
|
|
||||||
# This controls the directory the built artifacts go into
|
|
||||||
export ARTIFACT_DIR=artifacts/
|
|
||||||
mkdir -p $ARTIFACT_DIR
|
|
||||||
|
|
||||||
linuxArchs=("386" "amd64" "arm" "armhf" "arm64")
|
|
||||||
export TARGET_OS=linux
|
|
||||||
for arch in ${linuxArchs[@]}; do
|
|
||||||
unset TARGET_ARM
|
|
||||||
export TARGET_ARCH=$arch
|
|
||||||
|
|
||||||
## Support for arm platforms without hardware FPU enabled
|
|
||||||
if [[ $arch == arm ]] ; then
|
|
||||||
export TARGET_ARCH=arm
|
|
||||||
export TARGET_ARM=5
|
|
||||||
fi
|
|
||||||
|
|
||||||
## Support for armhf builds
|
|
||||||
if [[ $arch == armhf ]] ; then
|
|
||||||
export TARGET_ARCH=arm
|
|
||||||
export TARGET_ARM=7
|
|
||||||
fi
|
|
||||||
|
|
||||||
make cloudflared-deb
|
|
||||||
mv cloudflared\_$VERSION\_$arch.deb $ARTIFACT_DIR/cloudflared-linux-$arch.deb
|
|
||||||
|
|
||||||
# rpm packages invert the - and _ and use x86_64 instead of amd64.
|
|
||||||
RPMVERSION=$(echo $VERSION|sed -r 's/-/_/g')
|
|
||||||
RPMARCH=$arch
|
|
||||||
if [ $arch == "amd64" ];then
|
|
||||||
RPMARCH="x86_64"
|
|
||||||
fi
|
|
||||||
if [ $arch == "arm64" ]; then
|
|
||||||
RPMARCH="aarch64"
|
|
||||||
fi
|
|
||||||
make cloudflared-rpm
|
|
||||||
mv cloudflared-$RPMVERSION-1.$RPMARCH.rpm $ARTIFACT_DIR/cloudflared-linux-$RPMARCH.rpm
|
|
||||||
|
|
||||||
# finally move the linux binary as well.
|
|
||||||
mv ./cloudflared $ARTIFACT_DIR/cloudflared-linux-$arch
|
|
||||||
done
|
|
||||||
|
|
@ -26,11 +26,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StartOptions struct {
|
type StartOptions struct {
|
||||||
AppInfo *token.AppInfo
|
AppInfo *token.AppInfo
|
||||||
OriginURL string
|
OriginURL string
|
||||||
Headers http.Header
|
Headers http.Header
|
||||||
Host string
|
Host string
|
||||||
TLSClientConfig *tls.Config
|
TLSClientConfig *tls.Config
|
||||||
|
AutoCloseInterstitial bool
|
||||||
|
IsFedramp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection wraps up all the needed functions to forward over the tunnel
|
// Connection wraps up all the needed functions to forward over the tunnel
|
||||||
|
|
@ -46,7 +48,6 @@ type StdinoutStream struct{}
|
||||||
// Read will read from Stdin
|
// Read will read from Stdin
|
||||||
func (c *StdinoutStream) Read(p []byte) (int, error) {
|
func (c *StdinoutStream) Read(p []byte) (int, error) {
|
||||||
return os.Stdin.Read(p)
|
return os.Stdin.Read(p)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write will write to Stdout
|
// Write will write to Stdout
|
||||||
|
|
@ -139,7 +140,7 @@ func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Reque
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, log)
|
token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, options.AutoCloseInterstitial, options.IsFedramp, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,7 @@ spec:
|
||||||
lifecycle: "Active"
|
lifecycle: "Active"
|
||||||
owner: "teams/tunnel-teams-routing"
|
owner: "teams/tunnel-teams-routing"
|
||||||
cf:
|
cf:
|
||||||
|
compliance:
|
||||||
|
fedramp-high: "pending"
|
||||||
|
fedramp-moderate: "yes"
|
||||||
FIPS: "required"
|
FIPS: "required"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
type NewVirtualNetwork struct {
|
type NewVirtualNetwork struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
IsDefault bool `json:"is_default"`
|
IsDefault bool `json:"is_default_network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualNetwork struct {
|
type VirtualNetwork struct {
|
||||||
|
|
|
||||||
259
cfsetup.yaml
259
cfsetup.yaml
|
|
@ -1,257 +1,2 @@
|
||||||
pinned_go: &pinned_go go-boring=1.22.10-1
|
# A valid cfsetup.yaml is required but we dont have any real config to specify
|
||||||
|
dummy_key: true
|
||||||
build_dir: &build_dir /cfsetup_build
|
|
||||||
default-flavor: bookworm
|
|
||||||
|
|
||||||
bullseye: &bullseye
|
|
||||||
build-linux:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_deps
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
- rpm
|
|
||||||
- libffi-dev
|
|
||||||
- golangci-lint
|
|
||||||
pre-cache: &build_pre_cache
|
|
||||||
- export GOCACHE=/cfsetup_build/.cache/go-build
|
|
||||||
- go install golang.org/x/tools/cmd/goimports@v0.30.0
|
|
||||||
post-cache:
|
|
||||||
# Linting
|
|
||||||
- make lint
|
|
||||||
- make fmt-check
|
|
||||||
# Build binary for component test
|
|
||||||
- GOOS=linux GOARCH=amd64 make cloudflared
|
|
||||||
build-linux-fips:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deps
|
|
||||||
pre-cache: *build_pre_cache
|
|
||||||
post-cache:
|
|
||||||
- export FIPS=true
|
|
||||||
# Build binary for component test
|
|
||||||
- GOOS=linux GOARCH=amd64 make cloudflared
|
|
||||||
cover:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deps
|
|
||||||
pre-cache: *build_pre_cache
|
|
||||||
post-cache:
|
|
||||||
- make cover
|
|
||||||
# except FIPS and macos
|
|
||||||
build-linux-release:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_deps_release
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
- rpm
|
|
||||||
- libffi-dev
|
|
||||||
- python3-dev
|
|
||||||
- python3-pip
|
|
||||||
- python3-setuptools
|
|
||||||
- wget
|
|
||||||
- python3-venv
|
|
||||||
post-cache:
|
|
||||||
- python3 -m venv env
|
|
||||||
- . /cfsetup_build/env/bin/activate
|
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55 boto3==1.22.9 python-gnupg==0.4.9
|
|
||||||
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
|
|
||||||
- ./build-packages.sh
|
|
||||||
# handle FIPS separately so that we built with gofips compiler
|
|
||||||
build-linux-fips-release:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deps_release
|
|
||||||
post-cache:
|
|
||||||
# same logic as above, but for FIPS packages only
|
|
||||||
- ./build-packages-fips.sh
|
|
||||||
generate-versions-file:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps:
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
post-cache:
|
|
||||||
- make generate-docker-version
|
|
||||||
build-deb:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_deb_deps
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- make cloudflared-deb
|
|
||||||
build-fips-internal-deb:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_fips_deb_deps
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export FIPS=true
|
|
||||||
- export ORIGINAL_NAME=true
|
|
||||||
- make cloudflared-deb
|
|
||||||
build-internal-deb-nightly-amd64:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_fips_deb_deps
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export NIGHTLY=true
|
|
||||||
- export FIPS=true
|
|
||||||
- export ORIGINAL_NAME=true
|
|
||||||
- make cloudflared-deb
|
|
||||||
build-internal-deb-nightly-arm64:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_fips_deb_deps
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=arm64
|
|
||||||
- export NIGHTLY=true
|
|
||||||
# - export FIPS=true # TUN-7595
|
|
||||||
- export ORIGINAL_NAME=true
|
|
||||||
- make cloudflared-deb
|
|
||||||
build-deb-arm64:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deb_deps
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=arm64
|
|
||||||
- make cloudflared-deb
|
|
||||||
package-windows:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps:
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- python3-dev
|
|
||||||
- libffi-dev
|
|
||||||
- python3-setuptools
|
|
||||||
- python3-pip
|
|
||||||
- wget
|
|
||||||
# libmsi and libgcab are libraries the wixl binary depends on.
|
|
||||||
- libmsi-dev
|
|
||||||
- libgcab-dev
|
|
||||||
- python3-venv
|
|
||||||
pre-cache:
|
|
||||||
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
|
|
||||||
- chmod a+x /usr/local/bin/wixl
|
|
||||||
post-cache:
|
|
||||||
- python3 -m venv env
|
|
||||||
- . env/bin/activate
|
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55
|
|
||||||
- .teamcity/package-windows.sh
|
|
||||||
test:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_deps_tests
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
- rpm
|
|
||||||
- libffi-dev
|
|
||||||
- gotest-to-teamcity
|
|
||||||
pre-cache: *build_pre_cache
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export PATH="$HOME/go/bin:$PATH"
|
|
||||||
- make test | gotest-to-teamcity
|
|
||||||
test-fips:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deps_tests
|
|
||||||
pre-cache: *build_pre_cache
|
|
||||||
post-cache:
|
|
||||||
- export GOOS=linux
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export FIPS=true
|
|
||||||
- export PATH="$HOME/go/bin:$PATH"
|
|
||||||
- make test | gotest-to-teamcity
|
|
||||||
component-test:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: &build_deps_component_test
|
|
||||||
- *pinned_go
|
|
||||||
- python3
|
|
||||||
- python3-pip
|
|
||||||
- python3-setuptools
|
|
||||||
# procps installs the ps command which is needed in test_sysv_service
|
|
||||||
# because the init script uses ps pid to determine if the agent is
|
|
||||||
# running
|
|
||||||
- procps
|
|
||||||
- python3-venv
|
|
||||||
pre-cache-copy-paths:
|
|
||||||
- component-tests/requirements.txt
|
|
||||||
post-cache: &component_test_post_cache
|
|
||||||
- python3 -m venv env
|
|
||||||
- . env/bin/activate
|
|
||||||
- pip install --upgrade -r component-tests/requirements.txt
|
|
||||||
# Creates and routes a Named Tunnel for this build. Also constructs
|
|
||||||
# config file from env vars.
|
|
||||||
- python3 component-tests/setup.py --type create
|
|
||||||
- pytest component-tests -o log_cli=true --log-cli-level=INFO
|
|
||||||
# The Named Tunnel is deleted and its route unprovisioned here.
|
|
||||||
- python3 component-tests/setup.py --type cleanup
|
|
||||||
component-test-fips:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps: *build_deps_component_test
|
|
||||||
pre-cache-copy-paths:
|
|
||||||
- component-tests/requirements.txt
|
|
||||||
post-cache: *component_test_post_cache
|
|
||||||
github-release-dryrun:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps:
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- python3-dev
|
|
||||||
- libffi-dev
|
|
||||||
- python3-setuptools
|
|
||||||
- python3-pip
|
|
||||||
- python3-venv
|
|
||||||
post-cache:
|
|
||||||
- python3 -m venv env
|
|
||||||
- . env/bin/activate
|
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55
|
|
||||||
- make github-release-dryrun
|
|
||||||
github-release:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps:
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- python3-dev
|
|
||||||
- libffi-dev
|
|
||||||
- python3-setuptools
|
|
||||||
- python3-pip
|
|
||||||
- python3-venv
|
|
||||||
post-cache:
|
|
||||||
- python3 -m venv env
|
|
||||||
- . env/bin/activate
|
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55
|
|
||||||
- make github-release
|
|
||||||
r2-linux-release:
|
|
||||||
build_dir: *build_dir
|
|
||||||
builddeps:
|
|
||||||
- *pinned_go
|
|
||||||
- build-essential
|
|
||||||
- fakeroot
|
|
||||||
- rubygem-fpm
|
|
||||||
- rpm
|
|
||||||
- wget
|
|
||||||
- python3-dev
|
|
||||||
- libffi-dev
|
|
||||||
- python3-setuptools
|
|
||||||
- python3-pip
|
|
||||||
- reprepro
|
|
||||||
- createrepo-c
|
|
||||||
- python3-venv
|
|
||||||
post-cache:
|
|
||||||
- python3 -m venv env
|
|
||||||
- . env/bin/activate
|
|
||||||
- pip install pynacl==1.4.0 pygithub==1.55 boto3==1.22.9 python-gnupg==0.4.9
|
|
||||||
- make r2-linux-release
|
|
||||||
|
|
||||||
bookworm: *bullseye
|
|
||||||
trixie: *bullseye
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/features"
|
||||||
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config captures the local client runtime configuration.
|
||||||
|
type Config struct {
|
||||||
|
ConnectorID uuid.UUID
|
||||||
|
Version string
|
||||||
|
Arch string
|
||||||
|
|
||||||
|
featureSelector features.FeatureSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(version string, arch string, featureSelector features.FeatureSelector) (*Config, error) {
|
||||||
|
connectorID, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to generate a connector UUID: %w", err)
|
||||||
|
}
|
||||||
|
return &Config{
|
||||||
|
ConnectorID: connectorID,
|
||||||
|
Version: version,
|
||||||
|
Arch: arch,
|
||||||
|
featureSelector: featureSelector,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectionOptionsSnapshot is a snapshot of the current client information used to initialize a connection.
|
||||||
|
//
|
||||||
|
// The FeatureSnapshot is the features that are available for this connection. At the client level they may
|
||||||
|
// change, but they will not change within the scope of this struct.
|
||||||
|
type ConnectionOptionsSnapshot struct {
|
||||||
|
client pogs.ClientInfo
|
||||||
|
originLocalIP net.IP
|
||||||
|
numPreviousAttempts uint8
|
||||||
|
FeatureSnapshot features.FeatureSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) ConnectionOptionsSnapshot(originIP net.IP, previousAttempts uint8) *ConnectionOptionsSnapshot {
|
||||||
|
snapshot := c.featureSelector.Snapshot()
|
||||||
|
return &ConnectionOptionsSnapshot{
|
||||||
|
client: pogs.ClientInfo{
|
||||||
|
ClientID: c.ConnectorID[:],
|
||||||
|
Version: c.Version,
|
||||||
|
Arch: c.Arch,
|
||||||
|
Features: snapshot.FeaturesList,
|
||||||
|
},
|
||||||
|
originLocalIP: originIP,
|
||||||
|
numPreviousAttempts: previousAttempts,
|
||||||
|
FeatureSnapshot: snapshot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConnectionOptionsSnapshot) ConnectionOptions() *pogs.ConnectionOptions {
|
||||||
|
return &pogs.ConnectionOptions{
|
||||||
|
Client: c.client,
|
||||||
|
OriginLocalIP: c.originLocalIP,
|
||||||
|
ReplaceExisting: false,
|
||||||
|
CompressionQuality: 0,
|
||||||
|
NumPreviousAttempts: c.numPreviousAttempts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConnectionOptionsSnapshot) LogFields(event *zerolog.Event) *zerolog.Event {
|
||||||
|
return event.Strs("features", c.client.Features)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/features"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateConnectionOptions(t *testing.T) {
|
||||||
|
version := "1234"
|
||||||
|
arch := "linux_amd64"
|
||||||
|
originIP := net.ParseIP("192.168.1.1")
|
||||||
|
var previousAttempts uint8 = 4
|
||||||
|
|
||||||
|
config, err := NewConfig(version, arch, &mockFeatureSelector{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, version, config.Version)
|
||||||
|
require.Equal(t, arch, config.Arch)
|
||||||
|
|
||||||
|
// Validate ConnectionOptionsSnapshot fields
|
||||||
|
connOptions := config.ConnectionOptionsSnapshot(originIP, previousAttempts)
|
||||||
|
require.Equal(t, version, connOptions.client.Version)
|
||||||
|
require.Equal(t, arch, connOptions.client.Arch)
|
||||||
|
require.Equal(t, config.ConnectorID[:], connOptions.client.ClientID)
|
||||||
|
|
||||||
|
// Vaidate snapshot feature fields against the connOptions generated
|
||||||
|
snapshot := config.featureSelector.Snapshot()
|
||||||
|
require.Equal(t, features.DatagramV3, snapshot.DatagramVersion)
|
||||||
|
require.Equal(t, features.DatagramV3, connOptions.FeatureSnapshot.DatagramVersion)
|
||||||
|
|
||||||
|
pogsConnOptions := connOptions.ConnectionOptions()
|
||||||
|
require.Equal(t, connOptions.client, pogsConnOptions.Client)
|
||||||
|
require.Equal(t, originIP, pogsConnOptions.OriginLocalIP)
|
||||||
|
require.False(t, pogsConnOptions.ReplaceExisting)
|
||||||
|
require.Equal(t, uint8(0), pogsConnOptions.CompressionQuality)
|
||||||
|
require.Equal(t, previousAttempts, pogsConnOptions.NumPreviousAttempts)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockFeatureSelector struct{}
|
||||||
|
|
||||||
|
func (m *mockFeatureSelector) Snapshot() features.FeatureSnapshot {
|
||||||
|
return features.FeatureSnapshot{
|
||||||
|
PostQuantum: features.PostQuantumPrefer,
|
||||||
|
DatagramVersion: features.DatagramV3,
|
||||||
|
FeaturesList: []string{features.FeaturePostQuantum, features.FeatureDatagramV3_2},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,7 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
|
||||||
options := &carrier.StartOptions{
|
options := &carrier.StartOptions{
|
||||||
OriginURL: forwarder.URL,
|
OriginURL: forwarder.URL,
|
||||||
Headers: headers, //TODO: TUN-2688 support custom headers from config file
|
Headers: headers, //TODO: TUN-2688 support custom headers from config file
|
||||||
|
IsFedramp: forwarder.IsFedramp,
|
||||||
}
|
}
|
||||||
|
|
||||||
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
||||||
|
|
@ -92,6 +93,7 @@ func ssh(c *cli.Context) error {
|
||||||
OriginURL: url.String(),
|
OriginURL: url.String(),
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Host: url.Host,
|
Host: url.Host,
|
||||||
|
IsFedramp: c.Bool(fedrampFlag),
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectTo := c.String(sshConnectTo); connectTo != "" {
|
if connectTo := c.String(sshConnectTo); connectTo != "" {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ Host {{.Hostname}}
|
||||||
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
|
fedrampFlag = "fedramp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
||||||
|
|
@ -79,6 +80,10 @@ func Commands() []*cli.Command {
|
||||||
Aliases: []string{"forward"},
|
Aliases: []string{"forward"},
|
||||||
Category: "Access",
|
Category: "Access",
|
||||||
Usage: "access <subcommand>",
|
Usage: "access <subcommand>",
|
||||||
|
Flags: []cli.Flag{&cli.BoolFlag{
|
||||||
|
Name: fedrampFlag,
|
||||||
|
Usage: "use when performing operations in fedramp account",
|
||||||
|
}},
|
||||||
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
||||||
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
|
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
|
||||||
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
|
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
|
||||||
|
|
@ -104,6 +109,10 @@ func Commands() []*cli.Command {
|
||||||
Name: "no-verbose",
|
Name: "no-verbose",
|
||||||
Usage: "print only the jwt to stdout",
|
Usage: "print only the jwt to stdout",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "auto-close",
|
||||||
|
Usage: "automatically close the auth interstitial after action",
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: appURLFlag,
|
Name: appURLFlag,
|
||||||
},
|
},
|
||||||
|
|
@ -322,7 +331,7 @@ func curl(c *cli.Context) error {
|
||||||
log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
||||||
return run("curl", cmdArgs...)
|
return run("curl", cmdArgs...)
|
||||||
}
|
}
|
||||||
tok, err = token.FetchToken(appURL, appInfo, log)
|
tok, err = token.FetchToken(appURL, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), c.Bool(fedrampFlag), log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to refresh token")
|
log.Err(err).Msg("Failed to refresh token")
|
||||||
return err
|
return err
|
||||||
|
|
@ -442,7 +451,7 @@ func sshGen(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log)
|
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), c.Bool(fedrampFlag), log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -542,7 +551,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
|
||||||
if c.IsSet(sshTokenSecretFlag) {
|
if c.IsSet(sshTokenSecretFlag) {
|
||||||
headers.Add(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
headers.Add(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
||||||
}
|
}
|
||||||
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers}
|
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers, AutoCloseInterstitial: c.Bool(cfdflags.AutoCloseInterstitial), IsFedramp: c.Bool(fedrampFlag)}
|
||||||
|
|
||||||
if valid, err := isTokenValid(options, log); err != nil {
|
if valid, err := isTokenValid(options, log); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,32 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
|
|
||||||
cfdflags "github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugLevelWarning = "At debug level cloudflared will log request URL, method, protocol, content length, as well as, all request and response headers. " +
|
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."
|
"This can expose sensitive information in your logs."
|
||||||
|
|
||||||
|
FlagLogOutput = &cli.StringFlag{
|
||||||
|
Name: flags.LogFormatOutput,
|
||||||
|
Usage: "Output format for the logs (default, json)",
|
||||||
|
Value: flags.LogFormatOutputValueDefault,
|
||||||
|
EnvVars: []string{"TUNNEL_MANAGEMENT_OUTPUT", "TUNNEL_LOG_OUTPUT"},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConfigureLoggingFlags(shouldHide bool) []cli.Flag {
|
func ConfigureLoggingFlags(shouldHide bool) []cli.Flag {
|
||||||
return []cli.Flag{
|
return []cli.Flag{
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: cfdflags.LogLevel,
|
Name: flags.LogLevel,
|
||||||
Value: "info",
|
Value: "info",
|
||||||
Usage: "Application logging level {debug, info, warn, error, fatal}. " + debugLevelWarning,
|
Usage: "Application logging level {debug, info, warn, error, fatal}. " + debugLevelWarning,
|
||||||
EnvVars: []string{"TUNNEL_LOGLEVEL"},
|
EnvVars: []string{"TUNNEL_LOGLEVEL"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: cfdflags.TransportLogLevel,
|
Name: flags.TransportLogLevel,
|
||||||
Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel
|
Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel
|
||||||
Value: "info",
|
Value: "info",
|
||||||
Usage: "Transport logging level(previously called protocol logging level) {debug, info, warn, error, fatal}",
|
Usage: "Transport logging level(previously called protocol logging level) {debug, info, warn, error, fatal}",
|
||||||
|
|
@ -30,22 +37,23 @@ func ConfigureLoggingFlags(shouldHide bool) []cli.Flag {
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: cfdflags.LogFile,
|
Name: flags.LogFile,
|
||||||
Usage: "Save application log to this file for reporting issues.",
|
Usage: "Save application log to this file for reporting issues.",
|
||||||
EnvVars: []string{"TUNNEL_LOGFILE"},
|
EnvVars: []string{"TUNNEL_LOGFILE"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: cfdflags.LogDirectory,
|
Name: flags.LogDirectory,
|
||||||
Usage: "Save application log to this directory for reporting issues.",
|
Usage: "Save application log to this directory for reporting issues.",
|
||||||
EnvVars: []string{"TUNNEL_LOGDIRECTORY"},
|
EnvVars: []string{"TUNNEL_LOGDIRECTORY"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: cfdflags.TraceOutput,
|
Name: flags.TraceOutput,
|
||||||
Usage: "Name of trace output file, generated when cloudflared stops.",
|
Usage: "Name of trace output file, generated when cloudflared stops.",
|
||||||
EnvVars: []string{"TUNNEL_TRACE_OUTPUT"},
|
EnvVars: []string{"TUNNEL_TRACE_OUTPUT"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
|
FlagLogOutput,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,11 @@ const (
|
||||||
// LogDirectory is the command line flag to define the directory where application logs will be stored.
|
// LogDirectory is the command line flag to define the directory where application logs will be stored.
|
||||||
LogDirectory = "log-directory"
|
LogDirectory = "log-directory"
|
||||||
|
|
||||||
|
// LogFormatOutput allows the command line logs to be output as JSON.
|
||||||
|
LogFormatOutput = "output"
|
||||||
|
LogFormatOutputValueDefault = "default"
|
||||||
|
LogFormatOutputValueJSON = "json"
|
||||||
|
|
||||||
// TraceOutput is the command line flag to set the name of trace output file
|
// TraceOutput is the command line flag to set the name of trace output file
|
||||||
TraceOutput = "trace-output"
|
TraceOutput = "trace-output"
|
||||||
|
|
||||||
|
|
@ -152,4 +157,13 @@ const (
|
||||||
|
|
||||||
// ApiURL is the command line flag used to define the base URL of the API
|
// ApiURL is the command line flag used to define the base URL of the API
|
||||||
ApiURL = "api-url"
|
ApiURL = "api-url"
|
||||||
|
|
||||||
|
// Virtual DNS resolver service resolver addresses to use instead of dynamically fetching them from the OS.
|
||||||
|
VirtualDNSServiceResolverAddresses = "dns-resolver-addrs"
|
||||||
|
|
||||||
|
// Management hostname to signify incoming management requests
|
||||||
|
ManagementHostname = "management-hostname"
|
||||||
|
|
||||||
|
// Automatically close the login interstitial browser window after the user makes a decision.
|
||||||
|
AutoCloseInterstitial = "auto-close"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
@ -15,7 +16,7 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
func runApp(app *cli.App, _ chan struct{}) {
|
||||||
app.Commands = append(app.Commands, &cli.Command{
|
app.Commands = append(app.Commands, &cli.Command{
|
||||||
Name: "service",
|
Name: "service",
|
||||||
Usage: "Manages the cloudflared system service",
|
Usage: "Manages the cloudflared system service",
|
||||||
|
|
@ -35,7 +36,7 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.Run(os.Args)
|
_ = app.Run(os.Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The directory and files that are used by the service.
|
// The directory and files that are used by the service.
|
||||||
|
|
@ -59,7 +60,7 @@ After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
TimeoutStartSec=0
|
TimeoutStartSec=15
|
||||||
Type=notify
|
Type=notify
|
||||||
ExecStart={{ .Path }} --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
|
ExecStart={{ .Path }} --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
@ -97,6 +98,7 @@ WantedBy=timers.target
|
||||||
var sysvTemplate = ServiceTemplate{
|
var sysvTemplate = ServiceTemplate{
|
||||||
Path: "/etc/init.d/cloudflared",
|
Path: "/etc/init.d/cloudflared",
|
||||||
FileMode: 0755,
|
FileMode: 0755,
|
||||||
|
// nolint: dupword
|
||||||
Content: `#!/bin/sh
|
Content: `#!/bin/sh
|
||||||
# For RedHat and cousins:
|
# For RedHat and cousins:
|
||||||
# chkconfig: 2345 99 01
|
# chkconfig: 2345 99 01
|
||||||
|
|
@ -184,13 +186,11 @@ exit 0
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var noUpdateServiceFlag = &cli.BoolFlag{
|
||||||
noUpdateServiceFlag = &cli.BoolFlag{
|
Name: "no-update-service",
|
||||||
Name: "no-update-service",
|
Usage: "Disable auto-update of the cloudflared linux service, which restarts the server to upgrade for new versions.",
|
||||||
Usage: "Disable auto-update of the cloudflared linux service, which restarts the server to upgrade for new versions.",
|
Value: false,
|
||||||
Value: false,
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func isSystemd() bool {
|
func isSystemd() bool {
|
||||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||||
|
|
@ -430,3 +430,38 @@ func uninstallSysv(log *zerolog.Logger) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureConfigDirExists(configDir string) error {
|
||||||
|
ok, err := config.FileExists(configDir)
|
||||||
|
if !ok && err == nil {
|
||||||
|
err = os.Mkdir(configDir, 0755)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dest string) error {
|
||||||
|
srcFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ok := false
|
||||||
|
defer func() {
|
||||||
|
destFile.Close()
|
||||||
|
if !ok {
|
||||||
|
_ = os.Remove(dest)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(destFile, srcFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceTemplate struct {
|
type ServiceTemplate struct {
|
||||||
|
|
@ -109,38 +107,3 @@ func runCommand(command string, args ...string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureConfigDirExists(configDir string) error {
|
|
||||||
ok, err := config.FileExists(configDir)
|
|
||||||
if !ok && err == nil {
|
|
||||||
err = os.Mkdir(configDir, 0755)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dest string) error {
|
|
||||||
srcFile, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
|
|
||||||
destFile, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ok := false
|
|
||||||
defer func() {
|
|
||||||
destFile.Close()
|
|
||||||
if !ok {
|
|
||||||
_ = os.Remove(dest)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := io.Copy(destFile, srcFile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -50,6 +51,7 @@ func buildTailManagementTokenSubcommand() *cli.Command {
|
||||||
|
|
||||||
func managementTokenCommand(c *cli.Context) error {
|
func managementTokenCommand(c *cli.Context) error {
|
||||||
log := createLogger(c)
|
log := createLogger(c)
|
||||||
|
|
||||||
token, err := getManagementToken(c, log)
|
token, err := getManagementToken(c, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -98,13 +100,7 @@ func buildTailCommand(subcommands []*cli.Command) *cli.Command {
|
||||||
EnvVars: []string{"TUNNEL_MANAGEMENT_TOKEN"},
|
EnvVars: []string{"TUNNEL_MANAGEMENT_TOKEN"},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "output",
|
Name: cfdflags.ManagementHostname,
|
||||||
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",
|
Usage: "Management hostname to signify incoming management requests",
|
||||||
EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"},
|
EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
|
|
@ -128,6 +124,7 @@ func buildTailCommand(subcommands []*cli.Command) *cli.Command {
|
||||||
EnvVars: []string{"TUNNEL_ORIGIN_CERT"},
|
EnvVars: []string{"TUNNEL_ORIGIN_CERT"},
|
||||||
Value: credentials.FindDefaultOriginCertPath(),
|
Value: credentials.FindDefaultOriginCertPath(),
|
||||||
},
|
},
|
||||||
|
cliutil.FlagLogOutput,
|
||||||
},
|
},
|
||||||
Subcommands: subcommands,
|
Subcommands: subcommands,
|
||||||
}
|
}
|
||||||
|
|
@ -171,10 +168,21 @@ func createLogger(c *cli.Context) *zerolog.Logger {
|
||||||
if levelErr != nil {
|
if levelErr != nil {
|
||||||
level = zerolog.InfoLevel
|
level = zerolog.InfoLevel
|
||||||
}
|
}
|
||||||
log := zerolog.New(zerolog.ConsoleWriter{
|
var writer io.Writer
|
||||||
Out: colorable.NewColorable(os.Stderr),
|
switch c.String(cfdflags.LogFormatOutput) {
|
||||||
TimeFormat: time.RFC3339,
|
case cfdflags.LogFormatOutputValueJSON:
|
||||||
}).With().Timestamp().Logger().Level(level)
|
// zerolog by default outputs as JSON
|
||||||
|
writer = os.Stderr
|
||||||
|
case cfdflags.LogFormatOutputValueDefault:
|
||||||
|
// "default" and unset use the same logger output format
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
writer = zerolog.ConsoleWriter{
|
||||||
|
Out: colorable.NewColorable(os.Stderr),
|
||||||
|
TimeFormat: time.RFC3339,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log := zerolog.New(writer).With().Timestamp().Logger().Level(level)
|
||||||
return &log
|
return &log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +237,14 @@ func getManagementToken(c *cli.Context, log *zerolog.Logger) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := userCreds.Client(c.String(cfdflags.ApiURL), buildInfo.UserAgent(), log)
|
var apiURL string
|
||||||
|
if userCreds.IsFEDEndpoint() {
|
||||||
|
apiURL = credentials.FedRampBaseApiURL
|
||||||
|
} else {
|
||||||
|
apiURL = c.String(cfdflags.ApiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := userCreds.Client(apiURL, buildInfo.UserAgent(), log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +269,7 @@ func getManagementToken(c *cli.Context, log *zerolog.Logger) (string, error) {
|
||||||
// buildURL will build the management url to contain the required query parameters to authenticate the request.
|
// 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) {
|
func buildURL(c *cli.Context, log *zerolog.Logger) (url.URL, error) {
|
||||||
var err error
|
var err error
|
||||||
managementHostname := c.String("management-hostname")
|
|
||||||
token := c.String("token")
|
token := c.String("token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
token, err = getManagementToken(c, log)
|
token, err = getManagementToken(c, log)
|
||||||
|
|
@ -262,6 +277,19 @@ func buildURL(c *cli.Context, log *zerolog.Logger) (url.URL, error) {
|
||||||
return url.URL{}, fmt.Errorf("unable to acquire management token for requested tunnel id: %w", err)
|
return url.URL{}, fmt.Errorf("unable to acquire management token for requested tunnel id: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
claims, err := management.ParseToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return url.URL{}, fmt.Errorf("failed to determine if token is FED: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var managementHostname string
|
||||||
|
if claims.IsFed() {
|
||||||
|
managementHostname = credentials.FedRampHostname
|
||||||
|
} else {
|
||||||
|
managementHostname = c.String(cfdflags.ManagementHostname)
|
||||||
|
}
|
||||||
|
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Add("access_token", token)
|
query.Add("access_token", token)
|
||||||
connector := c.String("connector-id")
|
connector := c.String("connector-id")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/coreos/go-systemd/v22/daemon"
|
"github.com/coreos/go-systemd/v22/daemon"
|
||||||
"github.com/facebookgo/grace/gracenet"
|
"github.com/facebookgo/grace/gracenet"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
@ -98,7 +97,7 @@ var (
|
||||||
"no-tls-verify",
|
"no-tls-verify",
|
||||||
"no-chunked-encoding",
|
"no-chunked-encoding",
|
||||||
"http2-origin",
|
"http2-origin",
|
||||||
"management-hostname",
|
cfdflags.ManagementHostname,
|
||||||
"service-op-ip",
|
"service-op-ip",
|
||||||
"local-ssh-port",
|
"local-ssh-port",
|
||||||
"ssh-idle-timeout",
|
"ssh-idle-timeout",
|
||||||
|
|
@ -446,14 +445,7 @@ func StartServer(
|
||||||
log.Err(err).Msg("Couldn't start tunnel")
|
log.Err(err).Msg("Couldn't start tunnel")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var clientID uuid.UUID
|
connectorID := tunnelConfig.ClientConfig.ConnectorID
|
||||||
if tunnelConfig.NamedTunnel != nil {
|
|
||||||
clientID, err = uuid.FromBytes(tunnelConfig.NamedTunnel.Client.ClientID)
|
|
||||||
if err != nil {
|
|
||||||
// set to nil for classic tunnels
|
|
||||||
clientID = uuid.Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable ICMP packet routing for quick tunnels
|
// Disable ICMP packet routing for quick tunnels
|
||||||
if quickTunnelURL != "" {
|
if quickTunnelURL != "" {
|
||||||
|
|
@ -467,11 +459,26 @@ func StartServer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userCreds, err := credentials.Read(c.String(cfdflags.OriginCert), log)
|
||||||
|
var isFEDEndpoint bool
|
||||||
|
if err != nil {
|
||||||
|
isFEDEndpoint = false
|
||||||
|
} else {
|
||||||
|
isFEDEndpoint = userCreds.IsFEDEndpoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
var managementHostname string
|
||||||
|
if isFEDEndpoint {
|
||||||
|
managementHostname = credentials.FedRampHostname
|
||||||
|
} else {
|
||||||
|
managementHostname = c.String(cfdflags.ManagementHostname)
|
||||||
|
}
|
||||||
|
|
||||||
mgmt := management.New(
|
mgmt := management.New(
|
||||||
c.String("management-hostname"),
|
managementHostname,
|
||||||
c.Bool("management-diagnostics"),
|
c.Bool("management-diagnostics"),
|
||||||
serviceIP,
|
serviceIP,
|
||||||
clientID,
|
connectorID,
|
||||||
c.String(cfdflags.ConnectorLabel),
|
c.String(cfdflags.ConnectorLabel),
|
||||||
logger.ManagementLogger.Log,
|
logger.ManagementLogger.Log,
|
||||||
logger.ManagementLogger,
|
logger.ManagementLogger,
|
||||||
|
|
@ -503,14 +510,14 @@ func StartServer(
|
||||||
sources = append(sources, ipv6.String())
|
sources = append(sources, ipv6.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
readinessServer := metrics.NewReadyServer(clientID, tracker)
|
readinessServer := metrics.NewReadyServer(connectorID, tracker)
|
||||||
cliFlags := nonSecretCliFlags(log, c, nonSecretFlagsList)
|
cliFlags := nonSecretCliFlags(log, c, nonSecretFlagsList)
|
||||||
diagnosticHandler := diagnostic.NewDiagnosticHandler(
|
diagnosticHandler := diagnostic.NewDiagnosticHandler(
|
||||||
log,
|
log,
|
||||||
0,
|
0,
|
||||||
diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion),
|
diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion),
|
||||||
tunnelConfig.NamedTunnel.Credentials.TunnelID,
|
tunnelConfig.NamedTunnel.Credentials.TunnelID,
|
||||||
clientID,
|
connectorID,
|
||||||
tracker,
|
tracker,
|
||||||
cliFlags,
|
cliFlags,
|
||||||
sources,
|
sources,
|
||||||
|
|
@ -1050,7 +1057,7 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
|
||||||
Value: false,
|
Value: false,
|
||||||
}),
|
}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{
|
altsrc.NewStringFlag(&cli.StringFlag{
|
||||||
Name: "management-hostname",
|
Name: cfdflags.ManagementHostname,
|
||||||
Usage: "Management hostname to signify incoming management requests",
|
Usage: "Management hostname to signify incoming management requests",
|
||||||
EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"},
|
EnvVars: []string{"TUNNEL_MANAGEMENT_HOSTNAME"},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package tunnel
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/features"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDedup(t *testing.T) {
|
|
||||||
expected := []string{"a", "b"}
|
|
||||||
actual := features.Dedup([]string{"a", "b", "a"})
|
|
||||||
require.ElementsMatch(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
@ -10,13 +10,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/urfave/cli/v2/altsrc"
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/client"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||||
"github.com/cloudflare/cloudflared/features"
|
"github.com/cloudflare/cloudflared/features"
|
||||||
"github.com/cloudflare/cloudflared/ingress"
|
"github.com/cloudflare/cloudflared/ingress"
|
||||||
|
"github.com/cloudflare/cloudflared/ingress/origins"
|
||||||
"github.com/cloudflare/cloudflared/orchestration"
|
"github.com/cloudflare/cloudflared/orchestration"
|
||||||
"github.com/cloudflare/cloudflared/supervisor"
|
"github.com/cloudflare/cloudflared/supervisor"
|
||||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||||
|
|
@ -34,7 +36,6 @@ import (
|
||||||
const (
|
const (
|
||||||
secretValue = "*****"
|
secretValue = "*****"
|
||||||
icmpFunnelTimeout = time.Second * 10
|
icmpFunnelTimeout = time.Second * 10
|
||||||
fedRampRegion = "fed" // const string denoting the region used to connect to FEDRamp servers
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -125,27 +126,29 @@ func prepareTunnelConfig(
|
||||||
observer *connection.Observer,
|
observer *connection.Observer,
|
||||||
namedTunnel *connection.TunnelProperties,
|
namedTunnel *connection.TunnelProperties,
|
||||||
) (*supervisor.TunnelConfig, *orchestration.Config, error) {
|
) (*supervisor.TunnelConfig, *orchestration.Config, error) {
|
||||||
clientID, err := uuid.NewRandom()
|
transportProtocol := c.String(flags.Protocol)
|
||||||
|
isPostQuantumEnforced := c.Bool(flags.PostQuantum)
|
||||||
|
featureSelector, err := features.NewFeatureSelector(ctx, namedTunnel.Credentials.AccountTag, c.StringSlice(flags.Features), isPostQuantumEnforced, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
|
return nil, nil, errors.Wrap(err, "Failed to create feature selector")
|
||||||
}
|
}
|
||||||
log.Info().Msgf("Generated Connector ID: %s", clientID)
|
|
||||||
|
clientConfig, err := client.NewConfig(info.Version(), info.OSArch(), featureSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Generated Connector ID: %s", clientConfig.ConnectorID)
|
||||||
|
|
||||||
tags, err := NewTagSliceFromCLI(c.StringSlice(flags.Tag))
|
tags, err := NewTagSliceFromCLI(c.StringSlice(flags.Tag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Tag parse failure")
|
log.Err(err).Msg("Tag parse failure")
|
||||||
return nil, nil, errors.Wrap(err, "Tag parse failure")
|
return nil, nil, errors.Wrap(err, "Tag parse failure")
|
||||||
}
|
}
|
||||||
tags = append(tags, pogs.Tag{Name: "ID", Value: clientID.String()})
|
tags = append(tags, pogs.Tag{Name: "ID", Value: clientConfig.ConnectorID.String()})
|
||||||
|
|
||||||
transportProtocol := c.String(flags.Protocol)
|
clientFeatures := featureSelector.Snapshot()
|
||||||
isPostQuantumEnforced := c.Bool(flags.PostQuantum)
|
pqMode := clientFeatures.PostQuantum
|
||||||
|
|
||||||
featureSelector, err := features.NewFeatureSelector(ctx, namedTunnel.Credentials.AccountTag, c.StringSlice("features"), c.Bool("post-quantum"), log)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "Failed to create feature selector")
|
|
||||||
}
|
|
||||||
clientFeatures := featureSelector.ClientFeatures()
|
|
||||||
pqMode := featureSelector.PostQuantumMode()
|
|
||||||
if pqMode == features.PostQuantumStrict {
|
if pqMode == features.PostQuantumStrict {
|
||||||
// Error if the user tries to force a non-quic transport protocol
|
// Error if the user tries to force a non-quic transport protocol
|
||||||
if transportProtocol != connection.AutoSelectFlag && transportProtocol != connection.QUIC.String() {
|
if transportProtocol != connection.AutoSelectFlag && transportProtocol != connection.QUIC.String() {
|
||||||
|
|
@ -154,12 +157,6 @@ func prepareTunnelConfig(
|
||||||
transportProtocol = connection.QUIC.String()
|
transportProtocol = connection.QUIC.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
namedTunnel.Client = pogs.ClientInfo{
|
|
||||||
ClientID: clientID[:],
|
|
||||||
Features: clientFeatures,
|
|
||||||
Version: info.Version(),
|
|
||||||
Arch: info.OSArch(),
|
|
||||||
}
|
|
||||||
cfg := config.GetConfiguration()
|
cfg := config.GetConfiguration()
|
||||||
ingressRules, err := ingress.ParseIngressFromConfigAndCLI(cfg, c, log)
|
ingressRules, err := ingress.ParseIngressFromConfigAndCLI(cfg, c, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -223,11 +220,30 @@ func prepareTunnelConfig(
|
||||||
resolvedRegion = endpoint
|
resolvedRegion = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warpRoutingConfig := ingress.NewWarpRoutingConfig(&cfg.WarpRouting)
|
||||||
|
|
||||||
|
// Setup origin dialer service and virtual services
|
||||||
|
originDialerService := ingress.NewOriginDialer(ingress.OriginConfig{
|
||||||
|
DefaultDialer: ingress.NewDialer(warpRoutingConfig),
|
||||||
|
TCPWriteTimeout: c.Duration(flags.WriteStreamTimeout),
|
||||||
|
}, log)
|
||||||
|
|
||||||
|
// Setup DNS Resolver Service
|
||||||
|
originMetrics := origins.NewMetrics(prometheus.DefaultRegisterer)
|
||||||
|
dnsResolverAddrs := c.StringSlice(flags.VirtualDNSServiceResolverAddresses)
|
||||||
|
dnsService := origins.NewDNSResolverService(origins.NewDNSDialer(), log, originMetrics)
|
||||||
|
if len(dnsResolverAddrs) > 0 {
|
||||||
|
addrs, err := parseResolverAddrPorts(dnsResolverAddrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid %s provided: %w", flags.VirtualDNSServiceResolverAddresses, err)
|
||||||
|
}
|
||||||
|
dnsService = origins.NewStaticDNSResolverService(addrs, origins.NewDNSDialer(), log, originMetrics)
|
||||||
|
}
|
||||||
|
originDialerService.AddReservedService(dnsService, []netip.AddrPort{origins.VirtualDNSServiceAddr})
|
||||||
|
|
||||||
tunnelConfig := &supervisor.TunnelConfig{
|
tunnelConfig := &supervisor.TunnelConfig{
|
||||||
|
ClientConfig: clientConfig,
|
||||||
GracePeriod: gracePeriod,
|
GracePeriod: gracePeriod,
|
||||||
ReplaceExisting: c.Bool(flags.Force),
|
|
||||||
OSArch: info.OSArch(),
|
|
||||||
ClientID: clientID.String(),
|
|
||||||
EdgeAddrs: c.StringSlice(flags.Edge),
|
EdgeAddrs: c.StringSlice(flags.Edge),
|
||||||
Region: resolvedRegion,
|
Region: resolvedRegion,
|
||||||
EdgeIPVersion: edgeIPVersion,
|
EdgeIPVersion: edgeIPVersion,
|
||||||
|
|
@ -246,13 +262,14 @@ func prepareTunnelConfig(
|
||||||
NamedTunnel: namedTunnel,
|
NamedTunnel: namedTunnel,
|
||||||
ProtocolSelector: protocolSelector,
|
ProtocolSelector: protocolSelector,
|
||||||
EdgeTLSConfigs: edgeTLSConfigs,
|
EdgeTLSConfigs: edgeTLSConfigs,
|
||||||
FeatureSelector: featureSelector,
|
|
||||||
MaxEdgeAddrRetries: uint8(c.Int(flags.MaxEdgeAddrRetries)), // nolint: gosec
|
MaxEdgeAddrRetries: uint8(c.Int(flags.MaxEdgeAddrRetries)), // nolint: gosec
|
||||||
RPCTimeout: c.Duration(flags.RpcTimeout),
|
RPCTimeout: c.Duration(flags.RpcTimeout),
|
||||||
WriteStreamTimeout: c.Duration(flags.WriteStreamTimeout),
|
WriteStreamTimeout: c.Duration(flags.WriteStreamTimeout),
|
||||||
DisableQUICPathMTUDiscovery: c.Bool(flags.QuicDisablePathMTUDiscovery),
|
DisableQUICPathMTUDiscovery: c.Bool(flags.QuicDisablePathMTUDiscovery),
|
||||||
QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit),
|
QUICConnectionLevelFlowControlLimit: c.Uint64(flags.QuicConnLevelFlowControlLimit),
|
||||||
QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit),
|
QUICStreamLevelFlowControlLimit: c.Uint64(flags.QuicStreamLevelFlowControlLimit),
|
||||||
|
OriginDNSService: dnsService,
|
||||||
|
OriginDialerService: originDialerService,
|
||||||
}
|
}
|
||||||
icmpRouter, err := newICMPRouter(c, log)
|
icmpRouter, err := newICMPRouter(c, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -261,10 +278,10 @@ func prepareTunnelConfig(
|
||||||
tunnelConfig.ICMPRouterServer = icmpRouter
|
tunnelConfig.ICMPRouterServer = icmpRouter
|
||||||
}
|
}
|
||||||
orchestratorConfig := &orchestration.Config{
|
orchestratorConfig := &orchestration.Config{
|
||||||
Ingress: &ingressRules,
|
Ingress: &ingressRules,
|
||||||
WarpRouting: ingress.NewWarpRoutingConfig(&cfg.WarpRouting),
|
WarpRouting: warpRoutingConfig,
|
||||||
ConfigurationFlags: parseConfigFlags(c),
|
OriginDialerService: originDialerService,
|
||||||
WriteTimeout: tunnelConfig.WriteStreamTimeout,
|
ConfigurationFlags: parseConfigFlags(c),
|
||||||
}
|
}
|
||||||
return tunnelConfig, orchestratorConfig, nil
|
return tunnelConfig, orchestratorConfig, nil
|
||||||
}
|
}
|
||||||
|
|
@ -501,3 +518,19 @@ func findLocalAddr(dst net.IP, port int) (netip.Addr, error) {
|
||||||
localAddr := localAddrPort.Addr()
|
localAddr := localAddrPort.Addr()
|
||||||
return localAddr, nil
|
return localAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseResolverAddrPorts(input []string) ([]netip.AddrPort, error) {
|
||||||
|
// We don't allow more than 10 resolvers to be provided statically for the resolver service.
|
||||||
|
if len(input) > 10 {
|
||||||
|
return nil, errors.New("too many addresses provided, max: 10")
|
||||||
|
}
|
||||||
|
addrs := make([]netip.AddrPort, 0, len(input))
|
||||||
|
for _, val := range input {
|
||||||
|
addr, err := netip.ParseAddrPort(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
|
cfdflags "github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/credentials"
|
"github.com/cloudflare/cloudflared/credentials"
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
|
|
@ -19,11 +20,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
||||||
callbackURL = "https://login.cloudflareaccess.org/"
|
callbackURL = "https://login.cloudflareaccess.org/"
|
||||||
// For now these are the same but will change in the future once we know which URLs to use (TUN-8872)
|
fedBaseLoginURL = "https://dash.fed.cloudflare.com/argotunnel"
|
||||||
fedBaseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
fedCallbackStoreURL = "https://login.fed.cloudflareaccess.org/"
|
||||||
fedCallbackStoreURL = "https://login.cloudflareaccess.org/"
|
|
||||||
fedRAMPParamName = "fedramp"
|
fedRAMPParamName = "fedramp"
|
||||||
loginURLParamName = "loginURL"
|
loginURLParamName = "loginURL"
|
||||||
callbackURLParamName = "callbackURL"
|
callbackURLParamName = "callbackURL"
|
||||||
|
|
@ -97,6 +97,8 @@ func login(c *cli.Context) error {
|
||||||
callbackStoreURL,
|
callbackStoreURL,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
c.Bool(cfdflags.AutoCloseInterstitial),
|
||||||
|
isFEDRamp,
|
||||||
log,
|
log,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,12 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelCredentials := connection.Credentials{
|
tunnelCredentials := connection.Credentials{
|
||||||
AccountTag: credential.AccountID(),
|
AccountTag: credential.AccountID(),
|
||||||
TunnelSecret: tunnelSecret,
|
TunnelSecret: tunnelSecret,
|
||||||
TunnelID: tunnel.ID,
|
TunnelID: tunnel.ID,
|
||||||
|
Endpoint: credential.Endpoint(),
|
||||||
}
|
}
|
||||||
usedCertPath := false
|
usedCertPath := false
|
||||||
if credentialsFilePath == "" {
|
if credentialsFilePath == "" {
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,11 @@ var (
|
||||||
Usage: "Overrides the remote configuration for max active private network flows (TCP/UDP) that this cloudflared instance supports",
|
Usage: "Overrides the remote configuration for max active private network flows (TCP/UDP) that this cloudflared instance supports",
|
||||||
EnvVars: []string{"TUNNEL_MAX_ACTIVE_FLOWS"},
|
EnvVars: []string{"TUNNEL_MAX_ACTIVE_FLOWS"},
|
||||||
}
|
}
|
||||||
|
dnsResolverAddrsFlag = &cli.StringSliceFlag{
|
||||||
|
Name: flags.VirtualDNSServiceResolverAddresses,
|
||||||
|
Usage: "Overrides the dynamic DNS resolver resolution to use these address:port's instead.",
|
||||||
|
EnvVars: []string{"TUNNEL_DNS_RESOLVER_ADDRS"},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildCreateCommand() *cli.Command {
|
func buildCreateCommand() *cli.Command {
|
||||||
|
|
@ -718,6 +723,7 @@ func buildRunCommand() *cli.Command {
|
||||||
icmpv4SrcFlag,
|
icmpv4SrcFlag,
|
||||||
icmpv6SrcFlag,
|
icmpv6SrcFlag,
|
||||||
maxActiveFlowsFlag,
|
maxActiveFlowsFlag,
|
||||||
|
dnsResolverAddrsFlag,
|
||||||
}
|
}
|
||||||
flags = append(flags, configureProxyFlags(false)...)
|
flags = append(flags, configureProxyFlags(false)...)
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
|
|
@ -761,7 +767,7 @@ func runCommand(c *cli.Context) error {
|
||||||
if tokenFile := c.String(TunnelTokenFileFlag); tokenFile != "" {
|
if tokenFile := c.String(TunnelTokenFileFlag); tokenFile != "" {
|
||||||
data, err := os.ReadFile(tokenFile)
|
data, err := os.ReadFile(tokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliutil.UsageError("Failed to read token file: " + err.Error())
|
return cliutil.UsageError("Failed to read token file: %s", err.Error())
|
||||||
}
|
}
|
||||||
tokenStr = strings.TrimSpace(string(data))
|
tokenStr = strings.TrimSpace(string(data))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ func installWindowsService(c *cli.Context) error {
|
||||||
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
|
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.Close()
|
s.Close()
|
||||||
return fmt.Errorf(serviceAlreadyExistsWarn(windowsServiceName))
|
return errors.New(serviceAlreadyExistsWarn(windowsServiceName))
|
||||||
}
|
}
|
||||||
extraArgs, err := getServiceExtraArgsFromCliArgs(c, &log)
|
extraArgs, err := getServiceExtraArgsFromCliArgs(c, &log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -238,7 +238,7 @@ func uninstallWindowsService(c *cli.Context) error {
|
||||||
defer m.Disconnect()
|
defer m.Disconnect()
|
||||||
s, err := m.OpenService(windowsServiceName)
|
s, err := m.OpenService(windowsServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Agent service %s is not installed, so it could not be uninstalled", windowsServiceName)
|
return fmt.Errorf("agent service %s is not installed, so it could not be uninstalled", windowsServiceName)
|
||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,13 @@ class TestManagement:
|
||||||
assert resp.status_code == 404, "Expected cloudflared to return 404 for /metrics"
|
assert resp.status_code == 404, "Expected cloudflared to return 404 for /metrics"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||||
def send_request(url, headers={}):
|
def send_request(url, headers={}):
|
||||||
with requests.Session() as s:
|
with requests.Session() as s:
|
||||||
return s.get(url, timeout=BACKOFF_SECS, headers=headers)
|
resp = s.get(url, timeout=BACKOFF_SECS, headers=headers)
|
||||||
|
if resp.status_code == 530:
|
||||||
|
LOGGER.debug(f"Received 530 status, retrying request to {url}")
|
||||||
|
raise Exception(f"Received 530 status code from {url}")
|
||||||
|
return resp
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import pytest
|
||||||
|
|
||||||
import test_logging
|
import test_logging
|
||||||
from conftest import CfdModes
|
from conftest import CfdModes
|
||||||
from util import select_platform, start_cloudflared, wait_tunnel_ready, write_config
|
from util import select_platform, skip_on_ci, start_cloudflared, wait_tunnel_ready, write_config
|
||||||
|
|
||||||
|
|
||||||
def default_config_dir():
|
def default_config_dir():
|
||||||
|
|
@ -82,6 +82,7 @@ class TestServiceMode:
|
||||||
os.remove(default_config_file())
|
os.remove(default_config_file())
|
||||||
self.launchctl_cmd("list", success=False)
|
self.launchctl_cmd("list", success=False)
|
||||||
|
|
||||||
|
@skip_on_ci("we can't run sudo command on CI")
|
||||||
@select_platform("Linux")
|
@select_platform("Linux")
|
||||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
||||||
reason=f"There is already a config file in default path")
|
reason=f"There is already a config file in default path")
|
||||||
|
|
@ -98,6 +99,7 @@ class TestServiceMode:
|
||||||
|
|
||||||
self.sysv_service_scenario(config, tmp_path, assert_log_file)
|
self.sysv_service_scenario(config, tmp_path, assert_log_file)
|
||||||
|
|
||||||
|
@skip_on_ci("we can't run sudo command on CI")
|
||||||
@select_platform("Linux")
|
@select_platform("Linux")
|
||||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
||||||
reason=f"There is already a config file in default path")
|
reason=f"There is already a config file in default path")
|
||||||
|
|
@ -116,6 +118,7 @@ class TestServiceMode:
|
||||||
|
|
||||||
self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
|
self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
|
||||||
|
|
||||||
|
@skip_on_ci("we can't run sudo command on CI")
|
||||||
@select_platform("Linux")
|
@select_platform("Linux")
|
||||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
||||||
reason=f"There is already a config file in default path")
|
reason=f"There is already a config file in default path")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from setup import get_config_from_file, persist_origin_cert
|
from setup import get_config_from_file
|
||||||
from util import start_cloudflared
|
from util import start_cloudflared
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,20 @@ class TestTunnel:
|
||||||
LOGGER.debug(config)
|
LOGGER.debug(config)
|
||||||
with start_cloudflared(tmp_path, config, cfd_pre_args=["tunnel", "--ha-connections", "1"], cfd_args=["run"], new_process=True):
|
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)
|
wait_tunnel_ready(require_min_connections=1)
|
||||||
resp = send_request(config.get_url()+"/")
|
expected_status_code = 503
|
||||||
assert resp.status_code == 503, "Expected cloudflared to return 503 for all requests with no ingress defined"
|
resp = send_request(config.get_url()+"/", expected_status_code)
|
||||||
resp = send_request(config.get_url()+"/test")
|
assert resp.status_code == expected_status_code, "Expected cloudflared to return 503 for all requests with no ingress defined"
|
||||||
assert resp.status_code == 503, "Expected cloudflared to return 503 for all requests with no ingress defined"
|
resp = send_request(config.get_url()+"/test", expected_status_code)
|
||||||
|
assert resp.status_code == expected_status_code, "Expected cloudflared to return 503 for all requests with no ingress defined"
|
||||||
|
|
||||||
|
def retry_if_result_none(result):
|
||||||
|
'''
|
||||||
|
Returns True if the result is None, indicating that the function should be retried.
|
||||||
|
'''
|
||||||
|
return result is None
|
||||||
|
|
||||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
@retry(retry_on_result=retry_if_result_none, stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||||
def send_request(url, headers={}):
|
def send_request(url, expected_status_code=200):
|
||||||
with requests.Session() as s:
|
with requests.Session() as s:
|
||||||
return s.get(url, timeout=BACKOFF_SECS, headers=headers)
|
resp = s.get(url, timeout=BACKOFF_SECS)
|
||||||
|
return resp if resp.status_code == expected_status_code else None
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import pytest
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
import json
|
|
||||||
from retrying import retry
|
from retrying import retry
|
||||||
|
|
||||||
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
|
from constants import METRICS_PORT, MAX_RETRIES, BACKOFF_SECS
|
||||||
|
|
@ -35,6 +34,12 @@ def fips_enabled():
|
||||||
nofips = pytest.mark.skipif(
|
nofips = pytest.mark.skipif(
|
||||||
fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)")
|
fips_enabled(), reason=f"Only runs without FIPS (COMPONENT_TESTS_FIPS=0)")
|
||||||
|
|
||||||
|
def skip_on_ci(reason):
|
||||||
|
env_ci = os.getenv("CI")
|
||||||
|
running_in_ci = env_ci is not None and env_ci != "0"
|
||||||
|
return pytest.mark.skipif(
|
||||||
|
running_in_ci, reason=f"This test can't run on CI due to: {reason}")
|
||||||
|
|
||||||
def write_config(directory, config):
|
def write_config(directory, config):
|
||||||
config_path = directory / "config.yml"
|
config_path = directory / "config.yml"
|
||||||
with open(config_path, 'w') as outfile:
|
with open(config_path, 'w') as outfile:
|
||||||
|
|
@ -111,6 +116,7 @@ def inner_wait_tunnel_ready(tunnel_url=None, require_min_connections=1):
|
||||||
metrics_url = f'http://localhost:{METRICS_PORT}/ready'
|
metrics_url = f'http://localhost:{METRICS_PORT}/ready'
|
||||||
|
|
||||||
with requests.Session() as s:
|
with requests.Session() as s:
|
||||||
|
LOGGER.debug("Waiting for tunnel to be ready...")
|
||||||
resp = send_request(s, metrics_url, True)
|
resp = send_request(s, metrics_url, True)
|
||||||
|
|
||||||
ready_connections = resp.json()["readyConnections"]
|
ready_connections = resp.json()["readyConnections"]
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,8 @@ type AccessConfig struct {
|
||||||
|
|
||||||
// AudTag is the AudTag to verify access JWT against.
|
// AudTag is the AudTag to verify access JWT against.
|
||||||
AudTag []string `yaml:"audTag" json:"audTag"`
|
AudTag []string `yaml:"audTag" json:"audTag"`
|
||||||
|
|
||||||
|
Environment string `yaml:"environment" json:"environment,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IngressIPRule struct {
|
type IngressIPRule struct {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -16,6 +16,7 @@ type Forwarder struct {
|
||||||
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
|
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
|
||||||
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
|
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
|
IsFedramp bool `json:"is_fedramp" yaml:"isFedramp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tunnel represents a tunnel that should be started
|
// Tunnel represents a tunnel that should be started
|
||||||
|
|
@ -46,24 +47,24 @@ type Root struct {
|
||||||
|
|
||||||
// Hash returns the computed values to see if the forwarder values change
|
// Hash returns the computed values to see if the forwarder values change
|
||||||
func (f *Forwarder) Hash() string {
|
func (f *Forwarder) Hash() string {
|
||||||
h := md5.New()
|
h := sha256.New()
|
||||||
io.WriteString(h, f.URL)
|
_, _ = io.WriteString(h, f.URL)
|
||||||
io.WriteString(h, f.Listener)
|
_, _ = io.WriteString(h, f.Listener)
|
||||||
io.WriteString(h, f.TokenClientID)
|
_, _ = io.WriteString(h, f.TokenClientID)
|
||||||
io.WriteString(h, f.TokenSecret)
|
_, _ = io.WriteString(h, f.TokenSecret)
|
||||||
io.WriteString(h, f.Destination)
|
_, _ = io.WriteString(h, f.Destination)
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the computed values to see if the forwarder values change
|
// Hash returns the computed values to see if the forwarder values change
|
||||||
func (r *DNSResolver) Hash() string {
|
func (r *DNSResolver) Hash() string {
|
||||||
h := md5.New()
|
h := sha256.New()
|
||||||
io.WriteString(h, r.Address)
|
_, _ = io.WriteString(h, r.Address)
|
||||||
io.WriteString(h, strings.Join(r.Bootstraps, ","))
|
_, _ = io.WriteString(h, strings.Join(r.Bootstraps, ","))
|
||||||
io.WriteString(h, strings.Join(r.Upstreams, ","))
|
_, _ = io.WriteString(h, strings.Join(r.Upstreams, ","))
|
||||||
io.WriteString(h, fmt.Sprintf("%d", r.Port))
|
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.Port))
|
||||||
io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
|
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
|
||||||
io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
|
_, _ = io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ type Orchestrator interface {
|
||||||
|
|
||||||
type TunnelProperties struct {
|
type TunnelProperties struct {
|
||||||
Credentials Credentials
|
Credentials Credentials
|
||||||
Client pogs.ClientInfo
|
|
||||||
QuickTunnelUrl string
|
QuickTunnelUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/management"
|
"github.com/cloudflare/cloudflared/management"
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerClient derives a named tunnel rpc client that can then be used to register and unregister connections.
|
// registerClient derives a named tunnel rpc client that can then be used to register and unregister connections.
|
||||||
|
|
@ -36,7 +36,7 @@ type controlStream struct {
|
||||||
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
|
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
|
||||||
type ControlStreamHandler interface {
|
type ControlStreamHandler interface {
|
||||||
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
|
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
|
||||||
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *pogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
|
||||||
// IsStopped tells whether the method above has finished
|
// IsStopped tells whether the method above has finished
|
||||||
IsStopped() bool
|
IsStopped() bool
|
||||||
}
|
}
|
||||||
|
|
@ -78,11 +78,11 @@ func NewControlStream(
|
||||||
func (c *controlStream) ServeControlStream(
|
func (c *controlStream) ServeControlStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rw io.ReadWriteCloser,
|
rw io.ReadWriteCloser,
|
||||||
connOptions *tunnelpogs.ConnectionOptions,
|
connOptions *pogs.ConnectionOptions,
|
||||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||||
) error {
|
) error {
|
||||||
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
||||||
|
c.observer.logConnecting(c.connIndex, c.edgeAddress, c.protocol)
|
||||||
registrationDetails, err := registrationClient.RegisterConnection(
|
registrationDetails, err := registrationClient.RegisterConnection(
|
||||||
ctx,
|
ctx,
|
||||||
c.tunnelProperties.Credentials.Auth(),
|
c.tunnelProperties.Credentials.Auth(),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -53,26 +52,26 @@ func serverRegistrationErrorFromRPC(err error) ServerRegisterTunnelError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type muxerShutdownError struct{}
|
type ControlStreamError struct{}
|
||||||
|
|
||||||
func (e muxerShutdownError) Error() string {
|
var _ error = &ControlStreamError{}
|
||||||
return "muxer shutdown"
|
|
||||||
|
func (e *ControlStreamError) Error() string {
|
||||||
|
return "control stream encountered a failure while serving"
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMuxerStopped = muxerShutdownError{}
|
type StreamListenerError struct{}
|
||||||
|
|
||||||
func isHandshakeErrRecoverable(err error, connIndex uint8, observer *Observer) bool {
|
var _ error = &StreamListenerError{}
|
||||||
log := observer.log.With().
|
|
||||||
Uint8(LogFieldConnIndex, connIndex).
|
|
||||||
Err(err).
|
|
||||||
Logger()
|
|
||||||
|
|
||||||
switch err.(type) {
|
func (e *StreamListenerError) Error() string {
|
||||||
case edgediscovery.DialError:
|
return "accept stream listener encountered a failure while serving"
|
||||||
log.Error().Msg("Connection unable to dial edge")
|
}
|
||||||
default:
|
|
||||||
log.Error().Msg("Connection failed")
|
type DatagramManagerError struct{}
|
||||||
return false
|
|
||||||
}
|
var _ error = &DatagramManagerError{}
|
||||||
return true
|
|
||||||
|
func (e *DatagramManagerError) Error() string {
|
||||||
|
return "datagram manager encountered a failure while serving"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ var headerEncoding = base64.RawStdEncoding
|
||||||
func IsControlResponseHeader(headerName string) bool {
|
func IsControlResponseHeader(headerName string) bool {
|
||||||
return strings.HasPrefix(headerName, ":") ||
|
return strings.HasPrefix(headerName, ":") ||
|
||||||
strings.HasPrefix(headerName, "cf-int-") ||
|
strings.HasPrefix(headerName, "cf-int-") ||
|
||||||
strings.HasPrefix(headerName, "cf-cloudflared-")
|
strings.HasPrefix(headerName, "cf-cloudflared-") ||
|
||||||
|
strings.HasPrefix(headerName, "cf-proxy-")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSerializeHeaders(t *testing.T) {
|
func TestSerializeHeaders(t *testing.T) {
|
||||||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mockHeaders := http.Header{
|
mockHeaders := http.Header{
|
||||||
"Mock-Header-One": {"Mock header one value", "three"},
|
"Mock-Header-One": {"Mock header one value", "three"},
|
||||||
|
|
@ -39,22 +38,22 @@ func TestSerializeHeaders(t *testing.T) {
|
||||||
serializedHeaders := SerializeHeaders(request.Header)
|
serializedHeaders := SerializeHeaders(request.Header)
|
||||||
|
|
||||||
// Sanity check: the headers serialized to something that's not an empty string
|
// Sanity check: the headers serialized to something that's not an empty string
|
||||||
assert.NotEqual(t, "", serializedHeaders)
|
require.NotEqual(t, "", serializedHeaders)
|
||||||
|
|
||||||
// Deserialize back, and ensure we get the same set of headers
|
// Deserialize back, and ensure we get the same set of headers
|
||||||
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 13, len(deserializedHeaders))
|
require.Len(t, deserializedHeaders, 13)
|
||||||
expectedHeaders := headerToReqHeader(mockHeaders)
|
expectedHeaders := headerToReqHeader(mockHeaders)
|
||||||
|
|
||||||
sort.Sort(ByName(deserializedHeaders))
|
sort.Sort(ByName(deserializedHeaders))
|
||||||
sort.Sort(ByName(expectedHeaders))
|
sort.Sort(ByName(expectedHeaders))
|
||||||
|
|
||||||
assert.True(
|
require.True(
|
||||||
t,
|
t,
|
||||||
reflect.DeepEqual(expectedHeaders, deserializedHeaders),
|
reflect.DeepEqual(expectedHeaders, deserializedHeaders),
|
||||||
fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, expectedHeaders),
|
"got = %#v, want = %#v\n", deserializedHeaders, expectedHeaders,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,12 +81,12 @@ func headerToReqHeader(headers http.Header) (reqHeaders []HTTPHeader) {
|
||||||
|
|
||||||
func TestSerializeNoHeaders(t *testing.T) {
|
func TestSerializeNoHeaders(t *testing.T) {
|
||||||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
serializedHeaders := SerializeHeaders(request.Header)
|
serializedHeaders := SerializeHeaders(request.Header)
|
||||||
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 0, len(deserializedHeaders))
|
require.Empty(t, deserializedHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeserializeMalformed(t *testing.T) {
|
func TestDeserializeMalformed(t *testing.T) {
|
||||||
|
|
@ -102,21 +101,22 @@ func TestDeserializeMalformed(t *testing.T) {
|
||||||
|
|
||||||
for _, malformedValue := range malformedData {
|
for _, malformedValue := range malformedData {
|
||||||
_, err = DeserializeHeaders(malformedValue)
|
_, err = DeserializeHeaders(malformedValue)
|
||||||
assert.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsControlResponseHeader(t *testing.T) {
|
func TestIsControlResponseHeader(t *testing.T) {
|
||||||
controlResponseHeaders := []string{
|
controlResponseHeaders := []string{
|
||||||
// Anything that begins with cf-int- or cf-cloudflared-
|
// Anything that begins with cf-int-, cf-cloudflared- or cf-proxy-
|
||||||
"cf-int-sample-header",
|
"cf-int-sample-header",
|
||||||
"cf-cloudflared-sample-header",
|
"cf-cloudflared-sample-header",
|
||||||
|
"cf-proxy-sample-header",
|
||||||
// Any http2 pseudoheader
|
// Any http2 pseudoheader
|
||||||
":sample-pseudo-header",
|
":sample-pseudo-header",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range controlResponseHeaders {
|
for _, header := range controlResponseHeaders {
|
||||||
assert.True(t, IsControlResponseHeader(header))
|
require.True(t, IsControlResponseHeader(header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,6 +130,6 @@ func TestIsNotControlResponseHeader(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range notControlResponseHeaders {
|
for _, header := range notControlResponseHeaders {
|
||||||
assert.False(t, IsControlResponseHeader(header))
|
require.False(t, IsControlResponseHeader(header))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/client"
|
||||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/tracing"
|
"github.com/cloudflare/cloudflared/tracing"
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// note: these constants are exported so we can reuse them in the edge-side code
|
// note: these constants are exported so we can reuse them in the edge-side code
|
||||||
|
|
@ -39,7 +39,7 @@ type HTTP2Connection struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
server *http2.Server
|
server *http2.Server
|
||||||
orchestrator Orchestrator
|
orchestrator Orchestrator
|
||||||
connOptions *tunnelpogs.ConnectionOptions
|
connOptions *client.ConnectionOptionsSnapshot
|
||||||
observer *Observer
|
observer *Observer
|
||||||
connIndex uint8
|
connIndex uint8
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ type HTTP2Connection struct {
|
||||||
func NewHTTP2Connection(
|
func NewHTTP2Connection(
|
||||||
conn net.Conn,
|
conn net.Conn,
|
||||||
orchestrator Orchestrator,
|
orchestrator Orchestrator,
|
||||||
connOptions *tunnelpogs.ConnectionOptions,
|
connOptions *client.ConnectionOptionsSnapshot,
|
||||||
observer *Observer,
|
observer *Observer,
|
||||||
connIndex uint8,
|
connIndex uint8,
|
||||||
controlStreamHandler ControlStreamHandler,
|
controlStreamHandler ControlStreamHandler,
|
||||||
|
|
@ -118,7 +118,7 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
var requestErr error
|
var requestErr error
|
||||||
switch connType {
|
switch connType {
|
||||||
case TypeControlStream:
|
case TypeControlStream:
|
||||||
requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator)
|
requestErr = c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions.ConnectionOptions(), c.orchestrator)
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
c.controlStreamErr = requestErr
|
c.controlStreamErr = requestErr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,18 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/client"
|
||||||
"github.com/cloudflare/cloudflared/tracing"
|
"github.com/cloudflare/cloudflared/tracing"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var testTransport = http2.Transport{}
|
||||||
testTransport = http2.Transport{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||||
edgeConn, cfdConn := net.Pipe()
|
edgeConn, cfdConn := net.Pipe()
|
||||||
var connIndex = uint8(0)
|
connIndex := uint8(0)
|
||||||
log := zerolog.Nop()
|
log := zerolog.Nop()
|
||||||
obs := NewObserver(&log, &log)
|
obs := NewObserver(&log, &log)
|
||||||
controlStream := NewControlStream(
|
controlStream := NewControlStream(
|
||||||
|
|
@ -51,7 +50,7 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||||
cfdConn,
|
cfdConn,
|
||||||
// OriginProxy is set in testConfigManager
|
// OriginProxy is set in testConfigManager
|
||||||
testOrchestrator,
|
testOrchestrator,
|
||||||
&pogs.ConnectionOptions{},
|
&client.ConnectionOptionsSnapshot{},
|
||||||
obs,
|
obs,
|
||||||
connIndex,
|
connIndex,
|
||||||
controlStream,
|
controlStream,
|
||||||
|
|
@ -62,7 +61,7 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
|
||||||
func TestHTTP2ConfigurationSet(t *testing.T) {
|
func TestHTTP2ConfigurationSet(t *testing.T) {
|
||||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -130,7 +129,7 @@ func TestServeHTTP(t *testing.T) {
|
||||||
|
|
||||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -261,7 +260,7 @@ func (w *wsRespWriter) close() {
|
||||||
func TestServeWS(t *testing.T) {
|
func TestServeWS(t *testing.T) {
|
||||||
http2Conn, _ := newTestHTTP2Connection()
|
http2Conn, _ := newTestHTTP2Connection()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
|
||||||
respWriter := newWSRespWriter()
|
respWriter := newWSRespWriter()
|
||||||
readPipe, writePipe := io.Pipe()
|
readPipe, writePipe := io.Pipe()
|
||||||
|
|
@ -296,12 +295,12 @@ func TestServeWS(t *testing.T) {
|
||||||
require.False(t, respWriter.panicked)
|
require.False(t, respWriter.panicked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNoWriteAfterServeHTTPReturns is a regression test of https://jira.cfops.it/browse/TUN-5184
|
// TestNoWriteAfterServeHTTPReturns is a regression test of https://jira.cfdata.org/browse/TUN-5184
|
||||||
// to make sure we don't write to the ResponseWriter after the ServeHTTP method returns
|
// to make sure we don't write to the ResponseWriter after the ServeHTTP method returns
|
||||||
func TestNoWriteAfterServeHTTPReturns(t *testing.T) {
|
func TestNoWriteAfterServeHTTPReturns(t *testing.T) {
|
||||||
cfdHTTP2Conn, edgeTCPConn := newTestHTTP2Connection()
|
cfdHTTP2Conn, edgeTCPConn := newTestHTTP2Connection()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
serverDone := make(chan struct{})
|
||||||
|
|
@ -379,7 +378,7 @@ func TestServeControlStream(t *testing.T) {
|
||||||
)
|
)
|
||||||
http2Conn.controlStreamHandler = controlStream
|
http2Conn.controlStreamHandler = controlStream
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -433,7 +432,7 @@ func TestFailRegistration(t *testing.T) {
|
||||||
)
|
)
|
||||||
http2Conn.controlStreamHandler = controlStream
|
http2Conn.controlStreamHandler = controlStream
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -484,7 +483,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||||
|
|
||||||
http2Conn.controlStreamHandler = controlStream
|
http2Conn.controlStreamHandler = controlStream
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -534,7 +533,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServeTCP_RateLimited(t *testing.T) {
|
func TestServeTCP_RateLimited(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
@ -566,7 +565,7 @@ func TestServeTCP_RateLimited(t *testing.T) {
|
||||||
func benchmarkServeHTTP(b *testing.B, test testRequest) {
|
func benchmarkServeHTTP(b *testing.B, test testRequest) {
|
||||||
http2Conn, edgeConn := newTestHTTP2Connection()
|
http2Conn, edgeConn := newTestHTTP2Connection()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(b.Context())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@ func (o *Observer) RegisterSink(sink EventSink) {
|
||||||
o.addSinkChan <- sink
|
o.addSinkChan <- sink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Observer) logConnecting(connIndex uint8, address net.IP, protocol Protocol) {
|
||||||
|
o.log.Debug().
|
||||||
|
Int(management.EventTypeKey, int(management.Cloudflared)).
|
||||||
|
Uint8(LogFieldConnIndex, connIndex).
|
||||||
|
IPAddr(LogFieldIPAddress, address).
|
||||||
|
Str(LogFieldProtocol, protocol.String()).
|
||||||
|
Msg("Registering tunnel connection")
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) {
|
func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) {
|
||||||
o.log.Info().
|
o.log.Info().
|
||||||
Int(management.EventTypeKey, int(management.Cloudflared)).
|
Int(management.EventTypeKey, int(management.Cloudflared)).
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package connection
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -12,17 +13,16 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/client"
|
||||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||||
|
|
||||||
cfdquic "github.com/cloudflare/cloudflared/quic"
|
cfdquic "github.com/cloudflare/cloudflared/quic"
|
||||||
"github.com/cloudflare/cloudflared/tracing"
|
"github.com/cloudflare/cloudflared/tracing"
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
|
||||||
rpcquic "github.com/cloudflare/cloudflared/tunnelrpc/quic"
|
rpcquic "github.com/cloudflare/cloudflared/tunnelrpc/quic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ type quicConnection struct {
|
||||||
orchestrator Orchestrator
|
orchestrator Orchestrator
|
||||||
datagramHandler DatagramSessionHandler
|
datagramHandler DatagramSessionHandler
|
||||||
controlStreamHandler ControlStreamHandler
|
controlStreamHandler ControlStreamHandler
|
||||||
connOptions *tunnelpogs.ConnectionOptions
|
connOptions *client.ConnectionOptionsSnapshot
|
||||||
connIndex uint8
|
connIndex uint8
|
||||||
|
|
||||||
rpcTimeout time.Duration
|
rpcTimeout time.Duration
|
||||||
|
|
@ -60,12 +60,12 @@ func NewTunnelConnection(
|
||||||
orchestrator Orchestrator,
|
orchestrator Orchestrator,
|
||||||
datagramSessionHandler DatagramSessionHandler,
|
datagramSessionHandler DatagramSessionHandler,
|
||||||
controlStreamHandler ControlStreamHandler,
|
controlStreamHandler ControlStreamHandler,
|
||||||
connOptions *pogs.ConnectionOptions,
|
connOptions *client.ConnectionOptionsSnapshot,
|
||||||
rpcTimeout time.Duration,
|
rpcTimeout time.Duration,
|
||||||
streamWriteTimeout time.Duration,
|
streamWriteTimeout time.Duration,
|
||||||
gracePeriod time.Duration,
|
gracePeriod time.Duration,
|
||||||
logger *zerolog.Logger,
|
logger *zerolog.Logger,
|
||||||
) (TunnelConnection, error) {
|
) TunnelConnection {
|
||||||
return &quicConnection{
|
return &quicConnection{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
@ -77,10 +77,11 @@ func NewTunnelConnection(
|
||||||
rpcTimeout: rpcTimeout,
|
rpcTimeout: rpcTimeout,
|
||||||
streamWriteTimeout: streamWriteTimeout,
|
streamWriteTimeout: streamWriteTimeout,
|
||||||
gracePeriod: gracePeriod,
|
gracePeriod: gracePeriod,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve starts a QUIC connection that begins accepting streams.
|
// Serve starts a QUIC connection that begins accepting streams.
|
||||||
|
// Returning a nil error means cloudflared will exit for good and will not attempt to reconnect.
|
||||||
func (q *quicConnection) Serve(ctx context.Context) error {
|
func (q *quicConnection) Serve(ctx context.Context) error {
|
||||||
// The edge assumes the first stream is used for the control plane
|
// The edge assumes the first stream is used for the control plane
|
||||||
controlStream, err := q.conn.OpenStream()
|
controlStream, err := q.conn.OpenStream()
|
||||||
|
|
@ -88,16 +89,16 @@ func (q *quicConnection) Serve(ctx context.Context) error {
|
||||||
return fmt.Errorf("failed to open a registration control stream: %w", err)
|
return fmt.Errorf("failed to open a registration control stream: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If either goroutine returns nil error, we rely on this cancellation to make sure the other goroutine exits
|
|
||||||
// as fast as possible as well. Nil error means we want to exit for good (caller code won't retry serving this
|
|
||||||
// connection).
|
|
||||||
// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the
|
// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the
|
||||||
// other goroutine as fast as possible.
|
// other goroutines. We enforce returning a not-nil error for each function started in the errgroup by logging
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
// the error returned and returning a custom error type instead.
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
// In the future, if cloudflared can autonomously push traffic to the edge, we have to make sure the control
|
// Close the quic connection if any of the following routines return from the errgroup (regardless of their error)
|
||||||
// stream is already fully registered before the other goroutines can proceed.
|
// because they are no longer processing requests for the connection.
|
||||||
|
defer q.Close()
|
||||||
|
|
||||||
|
// Start the control stream routine
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
// err is equal to nil if we exit due to unregistration. If that happens we want to wait the full
|
// err is equal to nil if we exit due to unregistration. If that happens we want to wait the full
|
||||||
// amount of the grace period, allowing requests to finish before we cancel the context, which will
|
// amount of the grace period, allowing requests to finish before we cancel the context, which will
|
||||||
|
|
@ -114,16 +115,26 @@ func (q *quicConnection) Serve(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel()
|
if err != nil {
|
||||||
return err
|
q.logger.Error().Err(err).Msg("failed to serve the control stream")
|
||||||
|
}
|
||||||
|
return &ControlStreamError{}
|
||||||
})
|
})
|
||||||
|
// Start the accept stream loop routine
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
err := q.acceptStream(ctx)
|
||||||
return q.acceptStream(ctx)
|
if err != nil {
|
||||||
|
q.logger.Error().Err(err).Msg("failed to accept incoming stream requests")
|
||||||
|
}
|
||||||
|
return &StreamListenerError{}
|
||||||
})
|
})
|
||||||
|
// Start the datagram handler routine
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
err := q.datagramHandler.Serve(ctx)
|
||||||
return q.datagramHandler.Serve(ctx)
|
if err != nil {
|
||||||
|
q.logger.Error().Err(err).Msg("failed to run the datagram handler")
|
||||||
|
}
|
||||||
|
return &DatagramManagerError{}
|
||||||
})
|
})
|
||||||
|
|
||||||
return errGroup.Wait()
|
return errGroup.Wait()
|
||||||
|
|
@ -131,7 +142,7 @@ func (q *quicConnection) Serve(ctx context.Context) error {
|
||||||
|
|
||||||
// serveControlStream will serve the RPC; blocking until the control plane is done.
|
// serveControlStream will serve the RPC; blocking until the control plane is done.
|
||||||
func (q *quicConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
|
func (q *quicConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
|
||||||
return q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
|
return q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions.ConnectionOptions(), q.orchestrator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the connection with no errors specified.
|
// Close the connection with no errors specified.
|
||||||
|
|
@ -140,7 +151,6 @@ func (q *quicConnection) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *quicConnection) acceptStream(ctx context.Context) error {
|
func (q *quicConnection) acceptStream(ctx context.Context) error {
|
||||||
defer q.Close()
|
|
||||||
for {
|
for {
|
||||||
quicStream, err := q.conn.AcceptStream(ctx)
|
quicStream, err := q.conn.AcceptStream(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -230,12 +240,12 @@ func (q *quicConnection) dispatchRequest(ctx context.Context, stream *rpcquic.Re
|
||||||
ConnIndex: q.connIndex,
|
ConnIndex: q.connIndex,
|
||||||
}), rwa.connectResponseSent
|
}), rwa.connectResponseSent
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unsupported error type: %s", request.Type), false
|
return fmt.Errorf("unsupported error type: %s", request.Type), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateConfiguration is the RPC method invoked by edge when there is a new configuration
|
// UpdateConfiguration is the RPC method invoked by edge when there is a new configuration
|
||||||
func (q *quicConnection) UpdateConfiguration(ctx context.Context, version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
|
func (q *quicConnection) UpdateConfiguration(ctx context.Context, version int32, config []byte) *pogs.UpdateConfigurationResponse {
|
||||||
return q.orchestrator.UpdateConfig(version, config)
|
return q.orchestrator.UpdateConfig(version, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/net/nettest"
|
"golang.org/x/net/nettest"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/client"
|
||||||
|
"github.com/cloudflare/cloudflared/config"
|
||||||
cfdflow "github.com/cloudflare/cloudflared/flow"
|
cfdflow "github.com/cloudflare/cloudflared/flow"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/datagramsession"
|
"github.com/cloudflare/cloudflared/datagramsession"
|
||||||
|
|
@ -59,7 +61,7 @@ func TestQUICServer(t *testing.T) {
|
||||||
err := wsutil.WriteClientBinary(wsBuf, []byte("Hello"))
|
err := wsutil.WriteClientBinary(wsBuf, []byte("Hello"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var tests = []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
dest string
|
dest string
|
||||||
connectionType pogs.ConnectionType
|
connectionType pogs.ConnectionType
|
||||||
|
|
@ -149,7 +151,7 @@ func TestQUICServer(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
test := test // capture range variable
|
test := test // capture range variable
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
// Start a UDP Listener for QUIC.
|
// Start a UDP Listener for QUIC.
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -193,6 +195,7 @@ func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWrite
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fakeControlStream) IsStopped() bool {
|
func (fakeControlStream) IsStopped() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +213,7 @@ func quicServer(
|
||||||
session, err := listener.Accept(ctx)
|
session, err := listener.Accept(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
quicStream, err := session.OpenStreamSync(context.Background())
|
quicStream, err := session.OpenStreamSync(t.Context())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||||
|
|
||||||
|
|
@ -277,7 +280,7 @@ func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.T
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildHTTPRequest(t *testing.T) {
|
func TestBuildHTTPRequest(t *testing.T) {
|
||||||
var tests = []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
connectRequest *pogs.ConnectRequest
|
connectRequest *pogs.ConnectRequest
|
||||||
body io.ReadCloser
|
body io.ReadCloser
|
||||||
|
|
@ -498,7 +501,7 @@ func TestBuildHTTPRequest(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test // capture range variable
|
test := test // capture range variable
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
req, err := buildHTTPRequest(context.Background(), test.connectRequest, test.body, 0, &log)
|
req, err := buildHTTPRequest(t.Context(), test.connectRequest, test.body, 0, &log)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
test.req = test.req.WithContext(req.Context())
|
test.req = test.req.WithContext(req.Context())
|
||||||
require.Equal(t, test.req, req.Request)
|
require.Equal(t, test.req, req.Request)
|
||||||
|
|
@ -524,7 +527,7 @@ func TestServeUDPSession(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer udpListener.Close()
|
defer udpListener.Close()
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
|
||||||
// Establish QUIC connection with edge
|
// Establish QUIC connection with edge
|
||||||
edgeQUICSessionChan := make(chan quic.Connection)
|
edgeQUICSessionChan := make(chan quic.Connection)
|
||||||
|
|
@ -605,7 +608,7 @@ func TestCreateUDPConnReuseSourcePort(t *testing.T) {
|
||||||
// TestTCPProxy_FlowRateLimited tests if the pogs.ConnectResponse returns the expected error and metadata, when a
|
// TestTCPProxy_FlowRateLimited tests if the pogs.ConnectResponse returns the expected error and metadata, when a
|
||||||
// new flow is rate limited.
|
// new flow is rate limited.
|
||||||
func TestTCPProxy_FlowRateLimited(t *testing.T) {
|
func TestTCPProxy_FlowRateLimited(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
|
||||||
// Start a UDP Listener for QUIC.
|
// Start a UDP Listener for QUIC.
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||||
|
|
@ -626,7 +629,7 @@ func TestTCPProxy_FlowRateLimited(t *testing.T) {
|
||||||
session, err := quicListener.Accept(ctx)
|
session, err := quicListener.Accept(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
quicStream, err := session.OpenStreamSync(context.Background())
|
quicStream, err := session.OpenStreamSync(t.Context())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
stream := cfdquic.NewSafeStreamCloser(quicStream, defaultQUICTimeout, &log)
|
||||||
|
|
||||||
|
|
@ -687,9 +690,7 @@ func testCreateUDPConnReuseSourcePortForEdgeIP(t *testing.T, edgeIP netip.AddrPo
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveSession(ctx context.Context, datagramConn *datagramV2Connection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
|
func serveSession(ctx context.Context, datagramConn *datagramV2Connection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
|
||||||
var (
|
payload := []byte(t.Name())
|
||||||
payload = []byte(t.Name())
|
|
||||||
)
|
|
||||||
sessionID := uuid.New()
|
sessionID := uuid.New()
|
||||||
cfdConn, originConn := net.Pipe()
|
cfdConn, originConn := net.Pipe()
|
||||||
// Registers and run a new session
|
// Registers and run a new session
|
||||||
|
|
@ -802,7 +803,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||||
}
|
}
|
||||||
// Start a mock httpProxy
|
// Start a mock httpProxy
|
||||||
log := zerolog.New(io.Discard)
|
log := zerolog.New(io.Discard)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Dial the QUIC connection to the edge
|
// Dial the QUIC connection to the edge
|
||||||
|
|
@ -823,6 +824,15 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||||
sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan)
|
sessionManager := datagramsession.NewManager(&log, datagramMuxer.SendToSession, sessionDemuxChan)
|
||||||
var connIndex uint8 = 0
|
var connIndex uint8 = 0
|
||||||
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, connIndex, &log)
|
packetRouter := ingress.NewPacketRouter(nil, datagramMuxer, connIndex, &log)
|
||||||
|
testDefaultDialer := ingress.NewDialer(ingress.WarpRoutingConfig{
|
||||||
|
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Second},
|
||||||
|
TCPKeepAlive: config.CustomDuration{Duration: 15 * time.Second},
|
||||||
|
MaxActiveFlows: 0,
|
||||||
|
})
|
||||||
|
originDialer := ingress.NewOriginDialer(ingress.OriginConfig{
|
||||||
|
DefaultDialer: testDefaultDialer,
|
||||||
|
TCPWriteTimeout: 1 * time.Second,
|
||||||
|
}, &log)
|
||||||
|
|
||||||
datagramConn := &datagramV2Connection{
|
datagramConn := &datagramV2Connection{
|
||||||
conn,
|
conn,
|
||||||
|
|
@ -830,26 +840,26 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||||
sessionManager,
|
sessionManager,
|
||||||
cfdflow.NewLimiter(0),
|
cfdflow.NewLimiter(0),
|
||||||
datagramMuxer,
|
datagramMuxer,
|
||||||
|
originDialer,
|
||||||
packetRouter,
|
packetRouter,
|
||||||
15 * time.Second,
|
15 * time.Second,
|
||||||
0 * time.Second,
|
0 * time.Second,
|
||||||
&log,
|
&log,
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelConn, err := NewTunnelConnection(
|
tunnelConn := NewTunnelConnection(
|
||||||
ctx,
|
ctx,
|
||||||
conn,
|
conn,
|
||||||
index,
|
index,
|
||||||
&mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}},
|
&mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}},
|
||||||
datagramConn,
|
datagramConn,
|
||||||
fakeControlStream{},
|
fakeControlStream{},
|
||||||
&pogs.ConnectionOptions{},
|
&client.ConnectionOptionsSnapshot{},
|
||||||
15*time.Second,
|
15*time.Second,
|
||||||
0*time.Second,
|
0*time.Second,
|
||||||
0*time.Second,
|
0*time.Second,
|
||||||
&log,
|
&log,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
|
||||||
return tunnelConn, datagramConn
|
return tunnelConn, datagramConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
pkgerrors "github.com/pkg/errors"
|
pkgerrors "github.com/pkg/errors"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
@ -32,6 +34,10 @@ const (
|
||||||
demuxChanCapacity = 16
|
demuxChanCapacity = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidDestinationIP = errors.New("unable to parse destination IP")
|
||||||
|
)
|
||||||
|
|
||||||
// DatagramSessionHandler is a service that can serve datagrams for a connection and handle sessions from incoming
|
// DatagramSessionHandler is a service that can serve datagrams for a connection and handle sessions from incoming
|
||||||
// connection streams.
|
// connection streams.
|
||||||
type DatagramSessionHandler interface {
|
type DatagramSessionHandler interface {
|
||||||
|
|
@ -51,7 +57,10 @@ type datagramV2Connection struct {
|
||||||
|
|
||||||
// datagramMuxer mux/demux datagrams from quic connection
|
// datagramMuxer mux/demux datagrams from quic connection
|
||||||
datagramMuxer *cfdquic.DatagramMuxerV2
|
datagramMuxer *cfdquic.DatagramMuxerV2
|
||||||
packetRouter *ingress.PacketRouter
|
// originDialer is the origin dialer for UDP requests
|
||||||
|
originDialer ingress.OriginUDPDialer
|
||||||
|
// packetRouter acts as the origin router for ICMP requests
|
||||||
|
packetRouter *ingress.PacketRouter
|
||||||
|
|
||||||
rpcTimeout time.Duration
|
rpcTimeout time.Duration
|
||||||
streamWriteTimeout time.Duration
|
streamWriteTimeout time.Duration
|
||||||
|
|
@ -61,6 +70,7 @@ type datagramV2Connection struct {
|
||||||
|
|
||||||
func NewDatagramV2Connection(ctx context.Context,
|
func NewDatagramV2Connection(ctx context.Context,
|
||||||
conn quic.Connection,
|
conn quic.Connection,
|
||||||
|
originDialer ingress.OriginUDPDialer,
|
||||||
icmpRouter ingress.ICMPRouter,
|
icmpRouter ingress.ICMPRouter,
|
||||||
index uint8,
|
index uint8,
|
||||||
rpcTimeout time.Duration,
|
rpcTimeout time.Duration,
|
||||||
|
|
@ -79,6 +89,7 @@ func NewDatagramV2Connection(ctx context.Context,
|
||||||
sessionManager: sessionManager,
|
sessionManager: sessionManager,
|
||||||
flowLimiter: flowLimiter,
|
flowLimiter: flowLimiter,
|
||||||
datagramMuxer: datagramMuxer,
|
datagramMuxer: datagramMuxer,
|
||||||
|
originDialer: originDialer,
|
||||||
packetRouter: packetRouter,
|
packetRouter: packetRouter,
|
||||||
rpcTimeout: rpcTimeout,
|
rpcTimeout: rpcTimeout,
|
||||||
streamWriteTimeout: streamWriteTimeout,
|
streamWriteTimeout: streamWriteTimeout,
|
||||||
|
|
@ -87,24 +98,17 @@ func NewDatagramV2Connection(ctx context.Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *datagramV2Connection) Serve(ctx context.Context) error {
|
func (d *datagramV2Connection) Serve(ctx context.Context) error {
|
||||||
// If either goroutine returns nil error, we rely on this cancellation to make sure the other goroutine exits
|
// If either goroutine from the errgroup returns at all (error or nil), we rely on its cancellation to make sure
|
||||||
// as fast as possible as well. Nil error means we want to exit for good (caller code won't retry serving this
|
// the other goroutines as well.
|
||||||
// connection).
|
|
||||||
// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the
|
|
||||||
// other goroutine as fast as possible.
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.sessionManager.Serve(ctx)
|
return d.sessionManager.Serve(ctx)
|
||||||
})
|
})
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.datagramMuxer.ServeReceive(ctx)
|
return d.datagramMuxer.ServeReceive(ctx)
|
||||||
})
|
})
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.packetRouter.Serve(ctx)
|
return d.packetRouter.Serve(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -128,12 +132,29 @@ func (q *datagramV2Connection) RegisterUdpSession(ctx context.Context, sessionID
|
||||||
tracing.EndWithErrorStatus(registerSpan, err)
|
tracing.EndWithErrorStatus(registerSpan, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// We need to force the net.IP to IPv4 (if it's an IPv4 address) otherwise the net.IP conversion from capnp
|
||||||
|
// will be a IPv4-mapped-IPv6 address.
|
||||||
|
// In the case that the address is IPv6 we leave it untouched and parse it as normal.
|
||||||
|
ip := dstIP.To4()
|
||||||
|
if ip == nil {
|
||||||
|
ip = dstIP
|
||||||
|
}
|
||||||
|
// Parse the dstIP and dstPort into a netip.AddrPort
|
||||||
|
// This should never fail because the IP was already parsed as a valid net.IP
|
||||||
|
destAddr, ok := netip.AddrFromSlice(ip)
|
||||||
|
if !ok {
|
||||||
|
log.Err(errInvalidDestinationIP).Msgf("Failed to parse destination proxy IP: %s", ip)
|
||||||
|
tracing.EndWithErrorStatus(registerSpan, errInvalidDestinationIP)
|
||||||
|
q.flowLimiter.Release()
|
||||||
|
return nil, errInvalidDestinationIP
|
||||||
|
}
|
||||||
|
dstAddrPort := netip.AddrPortFrom(destAddr, dstPort)
|
||||||
|
|
||||||
// Each session is a series of datagram from an eyeball to a dstIP:dstPort.
|
// 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.
|
// (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket.
|
||||||
originProxy, err := ingress.DialUDP(dstIP, dstPort)
|
originProxy, err := q.originDialer.DialUDP(dstAddrPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msgf("Failed to create udp proxy to %s:%d", dstIP, dstPort)
|
log.Err(err).Msgf("Failed to create udp proxy to %s", dstAddrPort)
|
||||||
tracing.EndWithErrorStatus(registerSpan, err)
|
tracing.EndWithErrorStatus(registerSpan, err)
|
||||||
q.flowLimiter.Release()
|
q.flowLimiter.Release()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/mocks"
|
"github.com/cloudflare/cloudflared/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockQuicConnection struct {
|
type mockQuicConnection struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockQuicConnection) AcceptStream(_ context.Context) (quic.Stream, error) {
|
func (m *mockQuicConnection) AcceptStream(_ context.Context) (quic.Stream, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -71,6 +70,10 @@ func (m *mockQuicConnection) ReceiveDatagram(_ context.Context) ([]byte, error)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockQuicConnection) AddPath(*quic.Transport) (*quic.Path, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||||
log := zerolog.Nop()
|
log := zerolog.Nop()
|
||||||
conn := &mockQuicConnection{}
|
conn := &mockQuicConnection{}
|
||||||
|
|
@ -78,9 +81,10 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||||
flowLimiterMock := mocks.NewMockLimiter(ctrl)
|
flowLimiterMock := mocks.NewMockLimiter(ctrl)
|
||||||
|
|
||||||
datagramConn := NewDatagramV2Connection(
|
datagramConn := NewDatagramV2Connection(
|
||||||
context.Background(),
|
t.Context(),
|
||||||
conn,
|
conn,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
0,
|
0,
|
||||||
0*time.Second,
|
0*time.Second,
|
||||||
0*time.Second,
|
0*time.Second,
|
||||||
|
|
@ -91,6 +95,6 @@ func TestRateLimitOnNewDatagramV2UDPSession(t *testing.T) {
|
||||||
flowLimiterMock.EXPECT().Acquire("udp").Return(cfdflow.ErrTooManyActiveFlows)
|
flowLimiterMock.EXPECT().Acquire("udp").Return(cfdflow.ErrTooManyActiveFlows)
|
||||||
flowLimiterMock.EXPECT().Release().Times(0)
|
flowLimiterMock.EXPECT().Release().Times(0)
|
||||||
|
|
||||||
_, err := datagramConn.RegisterUdpSession(context.Background(), uuid.New(), net.IPv4(0, 0, 0, 0), 1000, 1*time.Second, "")
|
_, err := datagramConn.RegisterUdpSession(t.Context(), uuid.New(), net.IPv4(0, 0, 0, 0), 1000, 1*time.Second, "")
|
||||||
require.ErrorIs(t, err, cfdflow.ErrTooManyActiveFlows)
|
require.ErrorIs(t, err, cfdflow.ErrTooManyActiveFlows)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
|
@ -16,10 +16,17 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedRPCUDPRegistration = errors.New("datagram v3 does not support RegisterUdpSession RPC")
|
||||||
|
ErrUnsupportedRPCUDPUnregistration = errors.New("datagram v3 does not support UnregisterUdpSession RPC")
|
||||||
|
)
|
||||||
|
|
||||||
type datagramV3Connection struct {
|
type datagramV3Connection struct {
|
||||||
conn quic.Connection
|
conn quic.Connection
|
||||||
|
index uint8
|
||||||
// datagramMuxer mux/demux datagrams from quic connection
|
// datagramMuxer mux/demux datagrams from quic connection
|
||||||
datagramMuxer cfdquic.DatagramConn
|
datagramMuxer cfdquic.DatagramConn
|
||||||
|
metrics cfdquic.Metrics
|
||||||
logger *zerolog.Logger
|
logger *zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +47,9 @@ func NewDatagramV3Connection(ctx context.Context,
|
||||||
|
|
||||||
return &datagramV3Connection{
|
return &datagramV3Connection{
|
||||||
conn,
|
conn,
|
||||||
|
index,
|
||||||
datagramMuxer,
|
datagramMuxer,
|
||||||
|
metrics,
|
||||||
logger,
|
logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,9 +59,11 @@ func (d *datagramV3Connection) Serve(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *datagramV3Connection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
func (d *datagramV3Connection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration, traceContext string) (*pogs.RegisterUdpSessionResponse, error) {
|
||||||
return nil, fmt.Errorf("datagram v3 does not support RegisterUdpSession RPC")
|
d.metrics.UnsupportedRemoteCommand(d.index, "register_udp_session")
|
||||||
|
return nil, ErrUnsupportedRPCUDPRegistration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *datagramV3Connection) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
func (d *datagramV3Connection) UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID, message string) error {
|
||||||
return fmt.Errorf("datagram v3 does not support UnregisterUdpSession RPC")
|
d.metrics.UnsupportedRemoteCommand(d.index, "unregister_udp_session")
|
||||||
|
return ErrUnsupportedRPCUDPUnregistration
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import (
|
||||||
const (
|
const (
|
||||||
logFieldOriginCertPath = "originCertPath"
|
logFieldOriginCertPath = "originCertPath"
|
||||||
FedEndpoint = "fed"
|
FedEndpoint = "fed"
|
||||||
|
FedRampBaseApiURL = "https://api.fed.cloudflare.com/client/v4"
|
||||||
|
FedRampHostname = "management.fed.argotunnel.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|
@ -21,6 +23,10 @@ func (c User) AccountID() string {
|
||||||
return c.cert.AccountID
|
return c.cert.AccountID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c User) Endpoint() string {
|
||||||
|
return c.cert.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
func (c User) ZoneID() string {
|
func (c User) ZoneID() string {
|
||||||
return c.cert.ZoneID
|
return c.cert.ZoneID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time
|
||||||
// Closing dstConn cancels read so dstToTransport routine in Serve() can return
|
// Closing dstConn cancels read so dstToTransport routine in Serve() can return
|
||||||
defer s.dstConn.Close()
|
defer s.dstConn.Close()
|
||||||
if closeAfterIdle == 0 {
|
if closeAfterIdle == 0 {
|
||||||
// provide deafult is caller doesn't specify one
|
// provide default is caller doesn't specify one
|
||||||
closeAfterIdle = defaultCloseIdleAfter
|
closeAfterIdle = defaultCloseIdleAfter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
|
@ -34,12 +35,10 @@ func TestCloseIdle(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.Duration) {
|
func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.Duration) {
|
||||||
var (
|
localCloseReason := &errClosedSession{
|
||||||
localCloseReason = &errClosedSession{
|
message: "connection closed by origin",
|
||||||
message: "connection closed by origin",
|
byRemote: false,
|
||||||
byRemote: false,
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
sessionID := uuid.New()
|
sessionID := uuid.New()
|
||||||
cfdConn, originConn := net.Pipe()
|
cfdConn, originConn := net.Pipe()
|
||||||
payload := testPayload(sessionID)
|
payload := testPayload(sessionID)
|
||||||
|
|
@ -48,28 +47,28 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||||
mg := NewManager(&log, nil, nil)
|
mg := NewManager(&log, nil, nil)
|
||||||
session := mg.newSession(sessionID, cfdConn)
|
session := mg.newSession(sessionID, cfdConn)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
sessionDone := make(chan struct{})
|
sessionDone := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
closedByRemote, err := session.Serve(ctx, closeAfterIdle)
|
closedByRemote, err := session.Serve(ctx, closeAfterIdle)
|
||||||
switch closeBy {
|
switch closeBy {
|
||||||
case closeByContext:
|
case closeByContext:
|
||||||
require.Equal(t, context.Canceled, err)
|
assert.Equal(t, context.Canceled, err)
|
||||||
require.False(t, closedByRemote)
|
assert.False(t, closedByRemote)
|
||||||
case closeByCallingClose:
|
case closeByCallingClose:
|
||||||
require.Equal(t, localCloseReason, err)
|
assert.Equal(t, localCloseReason, err)
|
||||||
require.Equal(t, localCloseReason.byRemote, closedByRemote)
|
assert.Equal(t, localCloseReason.byRemote, closedByRemote)
|
||||||
case closeByTimeout:
|
case closeByTimeout:
|
||||||
require.Equal(t, SessionIdleErr(closeAfterIdle), err)
|
assert.Equal(t, SessionIdleErr(closeAfterIdle), err)
|
||||||
require.False(t, closedByRemote)
|
assert.False(t, closedByRemote)
|
||||||
}
|
}
|
||||||
close(sessionDone)
|
close(sessionDone)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
n, err := session.transportToDst(payload)
|
n, err := session.transportToDst(payload)
|
||||||
require.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
require.Equal(t, len(payload), n)
|
assert.Equal(t, len(payload), n)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
readBuffer := make([]byte, len(payload)+1)
|
readBuffer := make([]byte, len(payload)+1)
|
||||||
|
|
@ -84,6 +83,8 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
|
||||||
cancel()
|
cancel()
|
||||||
case closeByCallingClose:
|
case closeByCallingClose:
|
||||||
session.close(localCloseReason)
|
session.close(localCloseReason)
|
||||||
|
default:
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
<-sessionDone
|
<-sessionDone
|
||||||
|
|
@ -125,10 +126,10 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
activeUntil := startTime.Add(activeTime)
|
activeUntil := startTime.Add(activeTime)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
session.Serve(ctx, closeAfterIdle)
|
_, _ = session.Serve(ctx, closeAfterIdle)
|
||||||
if time.Now().Before(startTime.Add(activeTime)) {
|
if time.Now().Before(startTime.Add(activeTime)) {
|
||||||
return fmt.Errorf("session closed while it's still active")
|
return fmt.Errorf("session closed while it's still active")
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +209,7 @@ func TestZeroBytePayload(t *testing.T) {
|
||||||
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
mg := NewManager(&nopLogger, sender.muxSession, nil)
|
||||||
session := mg.newSession(sessionID, cfdConn)
|
session := mg.newSession(sessionID, cfdConn)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
// Read from underlying conn and send to transport
|
// Read from underlying conn and send to transport
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
FROM golang:1.22.10 as builder
|
|
||||||
ENV GO111MODULE=on \
|
|
||||||
CGO_ENABLED=0
|
|
||||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
|
||||||
RUN apt-get update
|
|
||||||
COPY . .
|
|
||||||
RUN .teamcity/install-cloudflare-go.sh
|
|
||||||
# compile cloudflared
|
|
||||||
RUN PATH="/tmp/go/bin:$PATH" make cloudflared
|
|
||||||
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package features
|
package features
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FeatureSerializedHeaders = "serialized_headers"
|
FeatureSerializedHeaders = "serialized_headers"
|
||||||
FeatureQuickReconnects = "quick_reconnects"
|
FeatureQuickReconnects = "quick_reconnects"
|
||||||
|
|
@ -8,7 +10,10 @@ const (
|
||||||
FeaturePostQuantum = "postquantum"
|
FeaturePostQuantum = "postquantum"
|
||||||
FeatureQUICSupportEOF = "support_quic_eof"
|
FeatureQUICSupportEOF = "support_quic_eof"
|
||||||
FeatureManagementLogs = "management_logs"
|
FeatureManagementLogs = "management_logs"
|
||||||
FeatureDatagramV3 = "support_datagram_v3"
|
FeatureDatagramV3_2 = "support_datagram_v3_2"
|
||||||
|
|
||||||
|
DeprecatedFeatureDatagramV3 = "support_datagram_v3" // Deprecated: TUN-9291
|
||||||
|
DeprecatedFeatureDatagramV3_1 = "support_datagram_v3_1" // Deprecated: TUN-9883
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultFeatures = []string{
|
var defaultFeatures = []string{
|
||||||
|
|
@ -19,11 +24,26 @@ var defaultFeatures = []string{
|
||||||
FeatureManagementLogs,
|
FeatureManagementLogs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of features that are no longer in-use.
|
||||||
|
var deprecatedFeatures = []string{
|
||||||
|
DeprecatedFeatureDatagramV3,
|
||||||
|
DeprecatedFeatureDatagramV3_1,
|
||||||
|
}
|
||||||
|
|
||||||
// Features set by user provided flags
|
// Features set by user provided flags
|
||||||
type staticFeatures struct {
|
type staticFeatures struct {
|
||||||
PostQuantumMode *PostQuantumMode
|
PostQuantumMode *PostQuantumMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FeatureSnapshot struct {
|
||||||
|
PostQuantum PostQuantumMode
|
||||||
|
DatagramVersion DatagramVersion
|
||||||
|
|
||||||
|
// We provide the list of features since we need it to send in the ConnectionOptions during connection
|
||||||
|
// registrations.
|
||||||
|
FeaturesList []string
|
||||||
|
}
|
||||||
|
|
||||||
type PostQuantumMode uint8
|
type PostQuantumMode uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -40,15 +60,19 @@ const (
|
||||||
// DatagramV2 is the currently supported datagram protocol for UDP and ICMP packets
|
// DatagramV2 is the currently supported datagram protocol for UDP and ICMP packets
|
||||||
DatagramV2 DatagramVersion = FeatureDatagramV2
|
DatagramV2 DatagramVersion = FeatureDatagramV2
|
||||||
// DatagramV3 is a new datagram protocol for UDP and ICMP packets. It is not backwards compatible with datagram v2.
|
// DatagramV3 is a new datagram protocol for UDP and ICMP packets. It is not backwards compatible with datagram v2.
|
||||||
DatagramV3 DatagramVersion = FeatureDatagramV3
|
DatagramV3 DatagramVersion = FeatureDatagramV3_2
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remove any duplicates from the slice
|
// Remove any duplicate features from the list and remove deprecated features
|
||||||
func Dedup(slice []string) []string {
|
func dedupAndRemoveFeatures(features []string) []string {
|
||||||
// Convert the slice into a set
|
// Convert the slice into a set
|
||||||
set := make(map[string]bool, 0)
|
set := map[string]bool{}
|
||||||
for _, str := range slice {
|
for _, feature := range features {
|
||||||
set[str] = true
|
// Remove deprecated features from the provided list
|
||||||
|
if slices.Contains(deprecatedFeatures, feature) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
set[feature] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the set back into a slice
|
// Convert the set back into a slice
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,32 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
featureSelectorHostname = "cfd-features.argotunnel.com"
|
featureSelectorHostname = "cfd-features.argotunnel.com"
|
||||||
defaultRefreshFreq = time.Hour * 6
|
|
||||||
lookupTimeout = time.Second * 10
|
lookupTimeout = time.Second * 10
|
||||||
|
defaultLookupFreq = time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
// If the TXT record adds other fields, the umarshal logic will ignore those keys
|
// If the TXT record adds other fields, the umarshal logic will ignore those keys
|
||||||
// If the TXT record is missing a key, the field will unmarshal to the default Go value
|
// If the TXT record is missing a key, the field will unmarshal to the default Go value
|
||||||
|
|
||||||
type featuresRecord struct {
|
type featuresRecord struct {
|
||||||
// support_datagram_v3
|
DatagramV3Percentage uint32 `json:"dv3_2"`
|
||||||
DatagramV3Percentage int32 `json:"dv3"`
|
|
||||||
|
|
||||||
|
// DatagramV3Percentage int32 `json:"dv3"` // Removed in TUN-9291
|
||||||
|
// DatagramV3Percentage uint32 `json:"dv3_1"` // Removed in TUN-9883
|
||||||
// PostQuantumPercentage int32 `json:"pq"` // Removed in TUN-7970
|
// PostQuantumPercentage int32 `json:"pq"` // Removed in TUN-7970
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFeatureSelector(ctx context.Context, accountTag string, cliFeatures []string, pq bool, logger *zerolog.Logger) (*FeatureSelector, error) {
|
func NewFeatureSelector(ctx context.Context, accountTag string, cliFeatures []string, pq bool, logger *zerolog.Logger) (FeatureSelector, error) {
|
||||||
return newFeatureSelector(ctx, accountTag, logger, newDNSResolver(), cliFeatures, pq, defaultRefreshFreq)
|
return newFeatureSelector(ctx, accountTag, logger, newDNSResolver(), cliFeatures, pq, defaultLookupFreq)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeatureSelector determines if this account will try new features. It periodically queries a DNS TXT record
|
type FeatureSelector interface {
|
||||||
// to see which features are turned on.
|
Snapshot() FeatureSnapshot
|
||||||
type FeatureSelector struct {
|
}
|
||||||
accountHash int32
|
|
||||||
|
// FeatureSelector determines if this account will try new features; loaded once during startup.
|
||||||
|
type featureSelector struct {
|
||||||
|
accountHash uint32
|
||||||
logger *zerolog.Logger
|
logger *zerolog.Logger
|
||||||
resolver resolver
|
resolver resolver
|
||||||
|
|
||||||
|
|
@ -44,11 +48,11 @@ type FeatureSelector struct {
|
||||||
cliFeatures []string
|
cliFeatures []string
|
||||||
|
|
||||||
// lock protects concurrent access to dynamic features
|
// lock protects concurrent access to dynamic features
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
features featuresRecord
|
remoteFeatures featuresRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.Logger, resolver resolver, cliFeatures []string, pq bool, refreshFreq time.Duration) (*FeatureSelector, error) {
|
func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.Logger, resolver resolver, cliFeatures []string, pq bool, refreshFreq time.Duration) (*featureSelector, error) {
|
||||||
// Combine default features and user-provided features
|
// Combine default features and user-provided features
|
||||||
var pqMode *PostQuantumMode
|
var pqMode *PostQuantumMode
|
||||||
if pq {
|
if pq {
|
||||||
|
|
@ -59,28 +63,40 @@ func newFeatureSelector(ctx context.Context, accountTag string, logger *zerolog.
|
||||||
staticFeatures := staticFeatures{
|
staticFeatures := staticFeatures{
|
||||||
PostQuantumMode: pqMode,
|
PostQuantumMode: pqMode,
|
||||||
}
|
}
|
||||||
selector := &FeatureSelector{
|
selector := &featureSelector{
|
||||||
accountHash: switchThreshold(accountTag),
|
accountHash: switchThreshold(accountTag),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
staticFeatures: staticFeatures,
|
staticFeatures: staticFeatures,
|
||||||
cliFeatures: Dedup(cliFeatures),
|
cliFeatures: dedupAndRemoveFeatures(cliFeatures),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the remote features
|
||||||
if err := selector.refresh(ctx); err != nil {
|
if err := selector.refresh(ctx); err != nil {
|
||||||
logger.Err(err).Msg("Failed to fetch features, default to disable")
|
logger.Err(err).Msg("Failed to fetch features, default to disable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spin off reloading routine
|
||||||
go selector.refreshLoop(ctx, refreshFreq)
|
go selector.refreshLoop(ctx, refreshFreq)
|
||||||
|
|
||||||
return selector, nil
|
return selector, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeatureSelector) accountEnabled(percentage int32) bool {
|
func (fs *featureSelector) Snapshot() FeatureSnapshot {
|
||||||
|
fs.lock.RLock()
|
||||||
|
defer fs.lock.RUnlock()
|
||||||
|
return FeatureSnapshot{
|
||||||
|
PostQuantum: fs.postQuantumMode(),
|
||||||
|
DatagramVersion: fs.datagramVersion(),
|
||||||
|
FeaturesList: fs.clientFeatures(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *featureSelector) accountEnabled(percentage uint32) bool {
|
||||||
return percentage > fs.accountHash
|
return percentage > fs.accountHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeatureSelector) PostQuantumMode() PostQuantumMode {
|
func (fs *featureSelector) postQuantumMode() PostQuantumMode {
|
||||||
if fs.staticFeatures.PostQuantumMode != nil {
|
if fs.staticFeatures.PostQuantumMode != nil {
|
||||||
return *fs.staticFeatures.PostQuantumMode
|
return *fs.staticFeatures.PostQuantumMode
|
||||||
}
|
}
|
||||||
|
|
@ -88,12 +104,9 @@ func (fs *FeatureSelector) PostQuantumMode() PostQuantumMode {
|
||||||
return PostQuantumPrefer
|
return PostQuantumPrefer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeatureSelector) DatagramVersion() DatagramVersion {
|
func (fs *featureSelector) datagramVersion() DatagramVersion {
|
||||||
fs.lock.RLock()
|
|
||||||
defer fs.lock.RUnlock()
|
|
||||||
|
|
||||||
// If user provides the feature via the cli, we take it as priority over remote feature evaluation
|
// If user provides the feature via the cli, we take it as priority over remote feature evaluation
|
||||||
if slices.Contains(fs.cliFeatures, FeatureDatagramV3) {
|
if slices.Contains(fs.cliFeatures, FeatureDatagramV3_2) {
|
||||||
return DatagramV3
|
return DatagramV3
|
||||||
}
|
}
|
||||||
// If the user specifies DatagramV2, we also take that over remote
|
// If the user specifies DatagramV2, we also take that over remote
|
||||||
|
|
@ -101,36 +114,20 @@ func (fs *FeatureSelector) DatagramVersion() DatagramVersion {
|
||||||
return DatagramV2
|
return DatagramV2
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.accountEnabled(fs.features.DatagramV3Percentage) {
|
if fs.accountEnabled(fs.remoteFeatures.DatagramV3Percentage) {
|
||||||
return DatagramV3
|
return DatagramV3
|
||||||
}
|
}
|
||||||
|
|
||||||
return DatagramV2
|
return DatagramV2
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientFeatures will return the list of currently available features that cloudflared should provide to the edge.
|
// clientFeatures will return the list of currently available features that cloudflared should provide to the edge.
|
||||||
//
|
func (fs *featureSelector) clientFeatures() []string {
|
||||||
// This list is dynamic and can change in-between returns.
|
|
||||||
func (fs *FeatureSelector) ClientFeatures() []string {
|
|
||||||
// Evaluate any remote features along with static feature list to construct the list of features
|
// Evaluate any remote features along with static feature list to construct the list of features
|
||||||
return Dedup(slices.Concat(defaultFeatures, fs.cliFeatures, []string{string(fs.DatagramVersion())}))
|
return dedupAndRemoveFeatures(slices.Concat(defaultFeatures, fs.cliFeatures, []string{string(fs.datagramVersion())}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeatureSelector) refreshLoop(ctx context.Context, refreshFreq time.Duration) {
|
func (fs *featureSelector) refresh(ctx context.Context) error {
|
||||||
ticker := time.NewTicker(refreshFreq)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
err := fs.refresh(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fs.logger.Err(err).Msg("Failed to refresh feature selector")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *FeatureSelector) refresh(ctx context.Context) error {
|
|
||||||
record, err := fs.resolver.lookupRecord(ctx)
|
record, err := fs.resolver.lookupRecord(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -144,11 +141,26 @@ func (fs *FeatureSelector) refresh(ctx context.Context) error {
|
||||||
fs.lock.Lock()
|
fs.lock.Lock()
|
||||||
defer fs.lock.Unlock()
|
defer fs.lock.Unlock()
|
||||||
|
|
||||||
fs.features = features
|
fs.remoteFeatures = features
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *featureSelector) refreshLoop(ctx context.Context, refreshFreq time.Duration) {
|
||||||
|
ticker := time.NewTicker(refreshFreq)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
err := fs.refresh(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.Err(err).Msg("Failed to refresh feature selector")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// resolver represents an object that can look up featuresRecord
|
// resolver represents an object that can look up featuresRecord
|
||||||
type resolver interface {
|
type resolver interface {
|
||||||
lookupRecord(ctx context.Context) ([]byte, error)
|
lookupRecord(ctx context.Context) ([]byte, error)
|
||||||
|
|
@ -180,8 +192,8 @@ func (dr *dnsResolver) lookupRecord(ctx context.Context) ([]byte, error) {
|
||||||
return []byte(records[0]), nil
|
return []byte(records[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchThreshold(accountTag string) int32 {
|
func switchThreshold(accountTag string) uint32 {
|
||||||
h := fnv.New32a()
|
h := fnv.New32a()
|
||||||
_, _ = h.Write([]byte(accountTag))
|
_, _ = h.Write([]byte(accountTag))
|
||||||
return int32(h.Sum32() % 100)
|
return h.Sum32() % 100
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,26 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testAccountTag = "123456"
|
||||||
|
testAccountHash = 74 // switchThreshold of `accountTag`
|
||||||
|
)
|
||||||
|
|
||||||
func TestUnmarshalFeaturesRecord(t *testing.T) {
|
func TestUnmarshalFeaturesRecord(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
record []byte
|
record []byte
|
||||||
expectedPercentage int32
|
expectedPercentage uint32
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
record: []byte(`{"dv3":0}`),
|
record: []byte(`{"dv3_2":0}`),
|
||||||
expectedPercentage: 0,
|
expectedPercentage: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
record: []byte(`{"dv3":39}`),
|
record: []byte(`{"dv3_2":39}`),
|
||||||
expectedPercentage: 39,
|
expectedPercentage: 39,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
record: []byte(`{"dv3":100}`),
|
record: []byte(`{"dv3_2":100}`),
|
||||||
expectedPercentage: 100,
|
expectedPercentage: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +39,9 @@ func TestUnmarshalFeaturesRecord(t *testing.T) {
|
||||||
{
|
{
|
||||||
record: []byte(`{"kyber":768}`), // Unmarshal to default struct if key is not present
|
record: []byte(`{"kyber":768}`), // Unmarshal to default struct if key is not present
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
record: []byte(`{"pq": 101,"dv3":100,"dv3_1":100}`), // Expired keys don't unmarshal to anything
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
@ -61,7 +69,7 @@ func TestFeaturePrecedenceEvaluationPostQuantum(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "user_specified",
|
name: "user_specified",
|
||||||
cli: true,
|
cli: true,
|
||||||
expectedFeatures: Dedup(append(defaultFeatures, FeaturePostQuantum)),
|
expectedFeatures: dedupAndRemoveFeatures(append(defaultFeatures, FeaturePostQuantum)),
|
||||||
expectedVersion: PostQuantumStrict,
|
expectedVersion: PostQuantumStrict,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -69,10 +77,11 @@ func TestFeaturePrecedenceEvaluationPostQuantum(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
resolver := &staticResolver{record: featuresRecord{}}
|
resolver := &staticResolver{record: featuresRecord{}}
|
||||||
selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, []string{}, test.cli, time.Second)
|
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, []string{}, test.cli, time.Second)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
|
snapshot := selector.Snapshot()
|
||||||
require.Equal(t, test.expectedVersion, selector.PostQuantumMode())
|
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||||
|
require.Equal(t, test.expectedVersion, snapshot.PostQuantum)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,97 +111,126 @@ func TestFeaturePrecedenceEvaluationDatagramVersion(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user_specified_v3",
|
name: "user_specified_v3",
|
||||||
cli: []string{FeatureDatagramV3},
|
cli: []string{FeatureDatagramV3_2},
|
||||||
remote: featuresRecord{},
|
remote: featuresRecord{},
|
||||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
expectedFeatures: dedupAndRemoveFeatures(append(defaultFeatures, FeatureDatagramV3_2)),
|
||||||
expectedVersion: FeatureDatagramV3,
|
expectedVersion: FeatureDatagramV3_2,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remote_specified_v3",
|
|
||||||
cli: []string{},
|
|
||||||
remote: featuresRecord{
|
|
||||||
DatagramV3Percentage: 100,
|
|
||||||
},
|
|
||||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
|
||||||
expectedVersion: FeatureDatagramV3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remote_and_user_specified_v3",
|
|
||||||
cli: []string{FeatureDatagramV3},
|
|
||||||
remote: featuresRecord{
|
|
||||||
DatagramV3Percentage: 100,
|
|
||||||
},
|
|
||||||
expectedFeatures: Dedup(append(defaultFeatures, FeatureDatagramV3)),
|
|
||||||
expectedVersion: FeatureDatagramV3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remote_v3_and_user_specified_v2",
|
|
||||||
cli: []string{FeatureDatagramV2},
|
|
||||||
remote: featuresRecord{
|
|
||||||
DatagramV3Percentage: 100,
|
|
||||||
},
|
|
||||||
expectedFeatures: defaultFeatures,
|
|
||||||
expectedVersion: DatagramV2,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
resolver := &staticResolver{record: test.remote}
|
resolver := &staticResolver{record: test.remote}
|
||||||
selector, err := newFeatureSelector(context.Background(), test.name, &logger, resolver, test.cli, false, time.Second)
|
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, test.cli, false, time.Second)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.ElementsMatch(t, test.expectedFeatures, selector.ClientFeatures())
|
snapshot := selector.Snapshot()
|
||||||
require.Equal(t, test.expectedVersion, selector.DatagramVersion())
|
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||||
|
require.Equal(t, test.expectedVersion, snapshot.DatagramVersion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeprecatedFeaturesRemoved(t *testing.T) {
|
||||||
|
logger := zerolog.Nop()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cli []string
|
||||||
|
remote featuresRecord
|
||||||
|
expectedFeatures []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no_removals",
|
||||||
|
cli: []string{},
|
||||||
|
remote: featuresRecord{},
|
||||||
|
expectedFeatures: defaultFeatures,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "support_datagram_v3",
|
||||||
|
cli: []string{DeprecatedFeatureDatagramV3},
|
||||||
|
remote: featuresRecord{},
|
||||||
|
expectedFeatures: defaultFeatures,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "support_datagram_v3_1",
|
||||||
|
cli: []string{DeprecatedFeatureDatagramV3_1},
|
||||||
|
remote: featuresRecord{},
|
||||||
|
expectedFeatures: defaultFeatures,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
resolver := &staticResolver{record: test.remote}
|
||||||
|
selector, err := newFeatureSelector(t.Context(), test.name, &logger, resolver, test.cli, false, time.Second)
|
||||||
|
require.NoError(t, err)
|
||||||
|
snapshot := selector.Snapshot()
|
||||||
|
require.ElementsMatch(t, test.expectedFeatures, snapshot.FeaturesList)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefreshFeaturesRecord(t *testing.T) {
|
func TestRefreshFeaturesRecord(t *testing.T) {
|
||||||
// The hash of the accountTag is 82
|
percentages := []uint32{0, 10, testAccountHash - 1, testAccountHash, testAccountHash + 1, 100, 101, 1000}
|
||||||
accountTag := t.Name()
|
selector := newTestSelector(t, percentages, false, time.Minute)
|
||||||
threshold := switchThreshold(accountTag)
|
|
||||||
|
|
||||||
percentages := []int32{0, 10, 81, 82, 83, 100, 101, 1000}
|
|
||||||
refreshFreq := time.Millisecond * 10
|
|
||||||
selector := newTestSelector(t, percentages, false, refreshFreq)
|
|
||||||
|
|
||||||
// Starting out should default to DatagramV2
|
// Starting out should default to DatagramV2
|
||||||
require.Equal(t, DatagramV2, selector.DatagramVersion())
|
snapshot := selector.Snapshot()
|
||||||
|
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||||
|
|
||||||
for _, percentage := range percentages {
|
for _, percentage := range percentages {
|
||||||
if percentage > threshold {
|
snapshot = selector.Snapshot()
|
||||||
require.Equal(t, DatagramV3, selector.DatagramVersion())
|
if percentage > testAccountHash {
|
||||||
|
require.Equal(t, DatagramV3, snapshot.DatagramVersion)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, DatagramV2, selector.DatagramVersion())
|
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(refreshFreq + time.Millisecond)
|
// Manually progress the next refresh
|
||||||
|
_ = selector.refresh(t.Context())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure error doesn't override the last fetched features
|
// Make sure a resolver error doesn't override the last fetched features
|
||||||
require.Equal(t, DatagramV3, selector.DatagramVersion())
|
snapshot = selector.Snapshot()
|
||||||
|
require.Equal(t, DatagramV3, snapshot.DatagramVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSnapshotIsolation(t *testing.T) {
|
||||||
|
percentages := []uint32{testAccountHash, testAccountHash + 1}
|
||||||
|
selector := newTestSelector(t, percentages, false, time.Minute)
|
||||||
|
|
||||||
|
// Starting out should default to DatagramV2
|
||||||
|
snapshot := selector.Snapshot()
|
||||||
|
require.Equal(t, DatagramV2, snapshot.DatagramVersion)
|
||||||
|
|
||||||
|
// Manually progress the next refresh
|
||||||
|
_ = selector.refresh(t.Context())
|
||||||
|
|
||||||
|
snapshot2 := selector.Snapshot()
|
||||||
|
require.Equal(t, DatagramV3, snapshot2.DatagramVersion)
|
||||||
|
require.NotEqual(t, snapshot.DatagramVersion, snapshot2.DatagramVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStaticFeatures(t *testing.T) {
|
func TestStaticFeatures(t *testing.T) {
|
||||||
percentages := []int32{0}
|
percentages := []uint32{0}
|
||||||
// PostQuantum Enabled from user flag
|
// PostQuantum Enabled from user flag
|
||||||
selector := newTestSelector(t, percentages, true, time.Millisecond*10)
|
selector := newTestSelector(t, percentages, true, time.Second)
|
||||||
require.Equal(t, PostQuantumStrict, selector.PostQuantumMode())
|
snapshot := selector.Snapshot()
|
||||||
|
require.Equal(t, PostQuantumStrict, snapshot.PostQuantum)
|
||||||
|
|
||||||
// PostQuantum Disabled (or not set)
|
// PostQuantum Disabled (or not set)
|
||||||
selector = newTestSelector(t, percentages, false, time.Millisecond*10)
|
selector = newTestSelector(t, percentages, false, time.Second)
|
||||||
require.Equal(t, PostQuantumPrefer, selector.PostQuantumMode())
|
snapshot = selector.Snapshot()
|
||||||
|
require.Equal(t, PostQuantumPrefer, snapshot.PostQuantum)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestSelector(t *testing.T, percentages []int32, pq bool, refreshFreq time.Duration) *FeatureSelector {
|
func newTestSelector(t *testing.T, percentages []uint32, pq bool, refreshFreq time.Duration) *featureSelector {
|
||||||
accountTag := t.Name()
|
|
||||||
logger := zerolog.Nop()
|
logger := zerolog.Nop()
|
||||||
|
|
||||||
resolver := &mockResolver{
|
resolver := &mockResolver{
|
||||||
percentages: percentages,
|
percentages: percentages,
|
||||||
}
|
}
|
||||||
|
|
||||||
selector, err := newFeatureSelector(context.Background(), accountTag, &logger, resolver, []string{}, pq, refreshFreq)
|
selector, err := newFeatureSelector(t.Context(), testAccountTag, &logger, resolver, []string{}, pq, refreshFreq)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return selector
|
return selector
|
||||||
|
|
@ -200,7 +238,7 @@ func newTestSelector(t *testing.T, percentages []int32, pq bool, refreshFreq tim
|
||||||
|
|
||||||
type mockResolver struct {
|
type mockResolver struct {
|
||||||
nextIndex int
|
nextIndex int
|
||||||
percentages []int32
|
percentages []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mr *mockResolver) lookupRecord(ctx context.Context) ([]byte, error) {
|
func (mr *mockResolver) lookupRecord(ctx context.Context) ([]byte, error) {
|
||||||
|
|
|
||||||
97
go.mod
97
go.mod
|
|
@ -1,47 +1,47 @@
|
||||||
module github.com/cloudflare/cloudflared
|
module github.com/cloudflare/cloudflared
|
||||||
|
|
||||||
go 1.22
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coredns/coredns v1.11.3
|
github.com/coredns/coredns v1.12.2
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0
|
github.com/coreos/go-oidc/v3 v3.10.0
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||||
github.com/fortytw2/leaktest v1.3.0
|
github.com/fortytw2/leaktest v1.3.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/getsentry/sentry-go v0.16.0
|
github.com/getsentry/sentry-go v0.16.0
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1
|
github.com/go-jose/go-jose/v4 v4.1.0
|
||||||
github.com/gobwas/ws v1.2.1
|
github.com/gobwas/ws v1.2.1
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-colorable v0.1.13
|
github.com/mattn/go-colorable v0.1.13
|
||||||
github.com/miekg/dns v1.1.58
|
github.com/miekg/dns v1.1.66
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/prometheus/client_model v0.6.0
|
github.com/prometheus/client_model v0.6.2
|
||||||
github.com/quic-go/quic-go v0.45.0
|
github.com/quic-go/quic-go v0.52.0
|
||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.20.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0
|
go.opentelemetry.io/contrib/propagators v0.22.0
|
||||||
go.opentelemetry.io/otel v1.26.0
|
go.opentelemetry.io/otel v1.35.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0
|
||||||
go.opentelemetry.io/otel/sdk v1.26.0
|
go.opentelemetry.io/otel/sdk v1.35.0
|
||||||
go.opentelemetry.io/otel/trace v1.26.0
|
go.opentelemetry.io/otel/trace v1.35.0
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0
|
go.opentelemetry.io/proto/otlp v1.2.0
|
||||||
go.uber.org/automaxprocs v1.4.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
go.uber.org/mock v0.5.0
|
go.uber.org/mock v0.5.1
|
||||||
golang.org/x/crypto v0.31.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.14.0
|
||||||
golang.org/x/sys v0.28.0
|
golang.org/x/sys v0.33.0
|
||||||
golang.org/x/term v0.27.0
|
golang.org/x/term v0.32.0
|
||||||
google.golang.org/protobuf v1.34.1
|
google.golang.org/protobuf v1.36.6
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
|
|
@ -52,8 +52,9 @@ require (
|
||||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/bytedance/sonic v1.12.0 // indirect
|
||||||
github.com/coredns/caddy v1.1.1 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||||
|
|
@ -61,38 +62,42 @@ require (
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-playground/validator/v10 v10.15.1 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
|
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||||
github.com/klauspost/compress v1.15.11 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||||
github.com/prometheus/common v0.53.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/common v0.64.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
golang.org/x/mod v0.18.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||||
golang.org/x/oauth2 v0.18.0 // indirect
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/tools v0.22.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
|
golang.org/x/tools v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
|
||||||
google.golang.org/grpc v1.63.2 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
|
||||||
|
google.golang.org/grpc v1.72.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -104,4 +109,4 @@ replace github.com/prometheus/golang_client => github.com/prometheus/golang_clie
|
||||||
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
// This fork is based on quic-go v0.45
|
// This fork is based on quic-go v0.45
|
||||||
replace github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910
|
replace github.com/quic-go/quic-go => github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd
|
||||||
|
|
|
||||||
276
go.sum
276
go.sum
|
|
@ -5,14 +5,22 @@ github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4t
|
||||||
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910 h1:/hTvBpxBDj/3NIzTodi1oEOyNBpirvgDSPKSV7VqAZU=
|
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||||
github.com/chungthuang/quic-go v0.45.1-0.20250128102735-2687bd175910/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/coredns/coredns v1.11.3 h1:8RjnpZc42db5th84/QJKH2i137ecJdzZK1HJwhetSPk=
|
github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd h1:VdYI5zFQ2h1/qzoC6rhyPx479bkF8i177Qpg4Q2n1vk=
|
||||||
github.com/coredns/coredns v1.11.3/go.mod h1:lqFkDsHjEUdY7LJ75Nib3lwqJGip6ewWOqNIf8OavIQ=
|
github.com/chungthuang/quic-go v0.45.1-0.20250428085412-43229ad201fd/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98 h1:c+Epklw9xk6BZ1OFBPWLA2PcL8QalKvl3if8CP9x8uw=
|
||||||
|
github.com/coredns/caddy v1.1.2-0.20241029205200-8de985351a98/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||||
|
github.com/coredns/coredns v1.12.2 h1:G4oDfi340zlVsriZ8nYiUemiQIew7nqOO+QPvPxIA4Y=
|
||||||
|
github.com/coredns/coredns v1.12.2/go.mod h1:GFz31oVOfCyMArFoypfu1SoaFoNkbdh6lDxtF1B6vfU=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
|
@ -21,7 +29,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||||
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-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 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
|
@ -42,38 +49,40 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
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/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
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/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
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-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.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
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-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
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/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
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-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
||||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
|
@ -83,34 +92,31 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
||||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.6/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
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/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo=
|
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587 h1:b/8HpQhvKLSNzH5oTXN2WkNcMl6YB5K3FRbb+i+Ml34=
|
||||||
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20250418163039-24c5476c6587/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||||
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 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||||
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 h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI=
|
||||||
|
|
@ -119,29 +125,29 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
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-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.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/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|
@ -150,33 +156,38 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
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/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pkg/errors v0.8.1/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||||
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||||
|
|
@ -185,115 +196,104 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||||
|
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
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 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.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
|
||||||
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
|
||||||
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
|
||||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
|
||||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||||
|
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||||
|
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
|
||||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
|
||||||
golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
|
||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build windows
|
//go:build windows && cgo
|
||||||
|
|
||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ func ParseIngressFromConfigAndCLI(conf *config.Configuration, c *cli.Context, lo
|
||||||
// If no token is provided, the probability of NOT being a remotely managed tunnel is higher.
|
// If no token is provided, the probability of NOT being a remotely managed tunnel is higher.
|
||||||
// So, we should warn the user that no ingress rules were found, because remote configuration will most likely not exist.
|
// So, we should warn the user that no ingress rules were found, because remote configuration will most likely not exist.
|
||||||
if !c.IsSet("token") {
|
if !c.IsSet("token") {
|
||||||
log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
log.Warn().Msg(ErrNoIngressRulesCLI.Error())
|
||||||
}
|
}
|
||||||
return newDefaultOrigin(c, log), nil
|
return newDefaultOrigin(c, log), nil
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +317,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
||||||
return Ingress{}, err
|
return Ingress{}, err
|
||||||
}
|
}
|
||||||
if access.Required {
|
if access.Required {
|
||||||
verifier := middleware.NewJWTValidator(access.TeamName, "", access.AudTag)
|
verifier := middleware.NewJWTValidator(access.TeamName, access.Environment, access.AudTag)
|
||||||
handlers = append(handlers, verifier)
|
handlers = append(handlers, verifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -378,17 +378,17 @@ func validateHostname(r config.UnvalidatedIngressRule, ruleIndex, totalRules int
|
||||||
}
|
}
|
||||||
// ONLY the last rule should catch all hostnames.
|
// ONLY the last rule should catch all hostnames.
|
||||||
if !isLastRule && isCatchAllRule {
|
if !isLastRule && isCatchAllRule {
|
||||||
return errRuleShouldNotBeCatchAll{index: ruleIndex, hostname: r.Hostname}
|
return ruleShouldNotBeCatchAllError{index: ruleIndex, hostname: r.Hostname}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type errRuleShouldNotBeCatchAll struct {
|
type ruleShouldNotBeCatchAllError struct {
|
||||||
index int
|
index int
|
||||||
hostname string
|
hostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errRuleShouldNotBeCatchAll) Error() string {
|
func (e ruleShouldNotBeCatchAllError) Error() string {
|
||||||
return fmt.Sprintf("Rule #%d is matching the hostname '%s', but "+
|
return fmt.Sprintf("Rule #%d is matching the hostname '%s', but "+
|
||||||
"this will match every hostname, meaning the rules which follow it "+
|
"this will match every hostname, meaning the rules which follow it "+
|
||||||
"will never be triggered.", e.index+1, e.hostname)
|
"will never be triggered.", e.index+1, e.hostname)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -13,7 +15,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cloudflareAccessCertsURL = "https://%s.cloudflareaccess.com"
|
cloudflareAccessCertsURL = "https://%s.cloudflareaccess.com"
|
||||||
|
cloudflareAccessFedCertsURL = "https://%s.fed.cloudflareaccess.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTValidator is an implementation of Verifier that validates access based JWT tokens.
|
// JWTValidator is an implementation of Verifier that validates access based JWT tokens.
|
||||||
|
|
@ -22,10 +25,14 @@ type JWTValidator struct {
|
||||||
audTags []string
|
audTags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewJWTValidator(teamName string, certsURL string, audTags []string) *JWTValidator {
|
func NewJWTValidator(teamName string, environment string, audTags []string) *JWTValidator {
|
||||||
if certsURL == "" {
|
var certsURL string
|
||||||
|
if environment == credentials.FedEndpoint {
|
||||||
|
certsURL = fmt.Sprintf(cloudflareAccessFedCertsURL, teamName)
|
||||||
|
} else {
|
||||||
certsURL = fmt.Sprintf(cloudflareAccessCertsURL, teamName)
|
certsURL = fmt.Sprintf(cloudflareAccessCertsURL, teamName)
|
||||||
}
|
}
|
||||||
|
|
||||||
certsEndpoint := fmt.Sprintf("%s/cdn-cgi/access/certs", certsURL)
|
certsEndpoint := fmt.Sprintf("%s/cdn-cgi/access/certs", certsURL)
|
||||||
|
|
||||||
config := &oidc.Config{
|
config := &oidc.Config{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
type OriginConnection interface {
|
type OriginConnection interface {
|
||||||
// Stream should generally be implemented as a bidirectional io.Copy.
|
// Stream should generally be implemented as a bidirectional io.Copy.
|
||||||
Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger)
|
Stream(ctx context.Context, tunnelConn io.ReadWriter, log *zerolog.Logger)
|
||||||
Close()
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger)
|
type streamHandlerFunc func(originConn io.ReadWriter, remoteConn net.Conn, log *zerolog.Logger)
|
||||||
|
|
@ -48,16 +48,7 @@ func (tc *tcpConnection) Write(b []byte) (int, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nBytes, err := tc.Conn.Write(b)
|
return tc.Conn.Write(b)
|
||||||
if err != nil {
|
|
||||||
tc.logger.Err(err).Msg("Error writing to the TCP connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nBytes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *tcpConnection) Close() {
|
|
||||||
tc.Conn.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tcpOverWSConnection is an OriginConnection that streams to TCP over WS.
|
// tcpOverWSConnection is an OriginConnection that streams to TCP over WS.
|
||||||
|
|
@ -75,8 +66,8 @@ func (wc *tcpOverWSConnection) Stream(ctx context.Context, tunnelConn io.ReadWri
|
||||||
wsConn.Close()
|
wsConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *tcpOverWSConnection) Close() {
|
func (wc *tcpOverWSConnection) Close() error {
|
||||||
wc.conn.Close()
|
return wc.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// socksProxyOverWSConnection is an OriginConnection that streams SOCKS connections over WS.
|
// socksProxyOverWSConnection is an OriginConnection that streams SOCKS connections over WS.
|
||||||
|
|
@ -95,5 +86,6 @@ func (sp *socksProxyOverWSConnection) Stream(ctx context.Context, tunnelConn io.
|
||||||
wsConn.Close()
|
wsConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *socksProxyOverWSConnection) Close() {
|
func (sp *socksProxyOverWSConnection) Close() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
package ingress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const writeDeadlineUDP = 200 * time.Millisecond
|
||||||
|
|
||||||
|
// OriginTCPDialer provides a TCP dial operation to a requested address.
|
||||||
|
type OriginTCPDialer interface {
|
||||||
|
DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginUDPDialer provides a UDP dial operation to a requested address.
|
||||||
|
type OriginUDPDialer interface {
|
||||||
|
DialUDP(addr netip.AddrPort) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginDialer provides both TCP and UDP dial operations to an address.
|
||||||
|
type OriginDialer interface {
|
||||||
|
OriginTCPDialer
|
||||||
|
OriginUDPDialer
|
||||||
|
}
|
||||||
|
|
||||||
|
type OriginConfig struct {
|
||||||
|
// The default Dialer used if no reserved services are found for an origin request.
|
||||||
|
DefaultDialer OriginDialer
|
||||||
|
// Timeout on write operations for TCP connections to the origin.
|
||||||
|
TCPWriteTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginDialerService provides a proxy TCP and UDP dialer to origin services while allowing reserved
|
||||||
|
// services to be provided. These reserved services are assigned to specific [netip.AddrPort]s
|
||||||
|
// and provide their own [OriginDialer]'s to handle origin dialing per protocol.
|
||||||
|
type OriginDialerService struct {
|
||||||
|
// Reserved TCP services for reserved AddrPort values
|
||||||
|
reservedTCPServices map[netip.AddrPort]OriginTCPDialer
|
||||||
|
// Reserved UDP services for reserved AddrPort values
|
||||||
|
reservedUDPServices map[netip.AddrPort]OriginUDPDialer
|
||||||
|
// The default Dialer used if no reserved services are found for an origin request
|
||||||
|
defaultDialer OriginDialer
|
||||||
|
defaultDialerM sync.RWMutex
|
||||||
|
// Write timeout for TCP connections
|
||||||
|
writeTimeout time.Duration
|
||||||
|
|
||||||
|
logger *zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOriginDialer(config OriginConfig, logger *zerolog.Logger) *OriginDialerService {
|
||||||
|
return &OriginDialerService{
|
||||||
|
reservedTCPServices: map[netip.AddrPort]OriginTCPDialer{},
|
||||||
|
reservedUDPServices: map[netip.AddrPort]OriginUDPDialer{},
|
||||||
|
defaultDialer: config.DefaultDialer,
|
||||||
|
writeTimeout: config.TCPWriteTimeout,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReservedService adds a reserved virtual service to dial to.
|
||||||
|
// Not locked and expected to be initialized before calling first dial and not afterwards.
|
||||||
|
func (d *OriginDialerService) AddReservedService(service OriginDialer, addrs []netip.AddrPort) {
|
||||||
|
for _, addr := range addrs {
|
||||||
|
d.reservedTCPServices[addr] = service
|
||||||
|
d.reservedUDPServices[addr] = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDefaultDialer updates the default dialer.
|
||||||
|
func (d *OriginDialerService) UpdateDefaultDialer(dialer *Dialer) {
|
||||||
|
d.defaultDialerM.Lock()
|
||||||
|
defer d.defaultDialerM.Unlock()
|
||||||
|
d.defaultDialer = dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialTCP will perform a dial TCP to the requested addr.
|
||||||
|
func (d *OriginDialerService) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
conn, err := d.dialTCP(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Assign the write timeout for the TCP operations
|
||||||
|
return &tcpConnection{
|
||||||
|
Conn: conn,
|
||||||
|
writeTimeout: d.writeTimeout,
|
||||||
|
logger: d.logger,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OriginDialerService) dialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
// Check to see if any reserved services are available for this addr and call their dialer instead.
|
||||||
|
if dialer, ok := d.reservedTCPServices[addr]; ok {
|
||||||
|
return dialer.DialTCP(ctx, addr)
|
||||||
|
}
|
||||||
|
d.defaultDialerM.RLock()
|
||||||
|
dialer := d.defaultDialer
|
||||||
|
d.defaultDialerM.RUnlock()
|
||||||
|
return dialer.DialTCP(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialUDP will perform a dial UDP to the requested addr.
|
||||||
|
func (d *OriginDialerService) DialUDP(addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
// Check to see if any reserved services are available for this addr and call their dialer instead.
|
||||||
|
if dialer, ok := d.reservedUDPServices[addr]; ok {
|
||||||
|
return dialer.DialUDP(addr)
|
||||||
|
}
|
||||||
|
d.defaultDialerM.RLock()
|
||||||
|
dialer := d.defaultDialer
|
||||||
|
d.defaultDialerM.RUnlock()
|
||||||
|
return dialer.DialUDP(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dialer struct {
|
||||||
|
Dialer net.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDialer(config WarpRoutingConfig) *Dialer {
|
||||||
|
return &Dialer{
|
||||||
|
Dialer: net.Dialer{
|
||||||
|
Timeout: config.ConnectTimeout.Duration,
|
||||||
|
KeepAlive: config.TCPKeepAlive.Duration,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) DialTCP(ctx context.Context, dest netip.AddrPort) (net.Conn, error) {
|
||||||
|
conn, err := d.Dialer.DialContext(ctx, "tcp", dest.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to dial tcp to origin %s: %w", dest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dialer) DialUDP(dest netip.AddrPort) (net.Conn, error) {
|
||||||
|
conn, err := d.Dialer.Dial("udp", dest.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to dial udp to origin %s: %w", dest, err)
|
||||||
|
}
|
||||||
|
return &writeDeadlineConn{
|
||||||
|
Conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeDeadlineConn is a wrapper around a net.Conn that sets a write deadline of 200ms.
|
||||||
|
// This is to prevent the socket from blocking on the write operation if it were to occur. However,
|
||||||
|
// we typically never expect this to occur except under high load or kernel issues.
|
||||||
|
type writeDeadlineConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writeDeadlineConn) Write(b []byte) (int, error) {
|
||||||
|
if err := w.SetWriteDeadline(time.Now().Add(writeDeadlineUDP)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return w.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ func (o *httpService) SetOriginServerName(req *http.Request) {
|
||||||
}
|
}
|
||||||
return tls.Client(conn, &tls.Config{
|
return tls.Client(conn, &tls.Config{
|
||||||
RootCAs: o.transport.TLSClientConfig.RootCAs,
|
RootCAs: o.transport.TLSClientConfig.RootCAs,
|
||||||
InsecureSkipVerify: o.transport.TLSClientConfig.InsecureSkipVerify,
|
InsecureSkipVerify: o.transport.TLSClientConfig.InsecureSkipVerify, // nolint: gosec
|
||||||
ServerName: req.Host,
|
ServerName: req.Host,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ func (o *httpService) SetOriginServerName(req *http.Request) {
|
||||||
|
|
||||||
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
|
||||||
if o.defaultResp {
|
if o.defaultResp {
|
||||||
o.log.Warn().Msgf(ErrNoIngressRulesCLI.Error())
|
o.log.Warn().Msg(ErrNoIngressRulesCLI.Error())
|
||||||
}
|
}
|
||||||
resp := &http.Response{
|
resp := &http.Response{
|
||||||
StatusCode: o.code,
|
StatusCode: o.code,
|
||||||
|
|
@ -114,7 +114,6 @@ func (o *tcpOverWSService) EstablishConnection(ctx context.Context, dest string,
|
||||||
streamHandler: o.streamHandler,
|
streamHandler: o.streamHandler,
|
||||||
}
|
}
|
||||||
return originConn, nil
|
return originConn, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *socksProxyOverWSService) EstablishConnection(_ context.Context, _ string, _ *zerolog.Logger) (OriginConnection, error) {
|
func (o *socksProxyOverWSService) EstablishConnection(_ context.Context, _ string, _ *zerolog.Logger) (OriginConnection, error) {
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package ingress
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UDPProxy interface {
|
|
||||||
io.ReadWriteCloser
|
|
||||||
LocalAddr() net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpProxy struct {
|
|
||||||
*net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialUDP(dstIP net.IP, dstPort uint16) (UDPProxy, error) {
|
|
||||||
dstAddr := &net.UDPAddr{
|
|
||||||
IP: dstIP,
|
|
||||||
Port: int(dstPort),
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use nil as local addr to force runtime to find the best suitable local address IP given the destination
|
|
||||||
// address as context.
|
|
||||||
udpConn, err := net.DialUDP("udp", nil, dstAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create UDP proxy to origin (%v:%v): %w", dstIP, dstPort, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &udpProxy{udpConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialUDPAddrPort(dest netip.AddrPort) (*net.UDPConn, error) {
|
|
||||||
addr := net.UDPAddrFromAddrPort(dest)
|
|
||||||
|
|
||||||
// We use nil as local addr to force runtime to find the best suitable local address IP given the destination
|
|
||||||
// address as context.
|
|
||||||
udpConn, err := net.DialUDP("udp", nil, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to dial udp to origin %s: %w", dest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return udpConn, nil
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
package origins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/ingress"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// We need a DNS record:
|
||||||
|
// 1. That will be around for as long as cloudflared is
|
||||||
|
// 2. That Cloudflare controls: to allow us to make changes if needed
|
||||||
|
// 3. That is an external record to a typical customer's network: enforcing that the DNS request go to the
|
||||||
|
// local DNS resolver over any local /etc/host configurations setup.
|
||||||
|
// 4. That cloudflared would normally query: ensuring that users with a positive security model for DNS queries
|
||||||
|
// don't need to adjust anything.
|
||||||
|
//
|
||||||
|
// This hostname is one that used during the edge discovery process and as such satisfies the above constraints.
|
||||||
|
defaultLookupHost = "region1.v2.argotunnel.com"
|
||||||
|
defaultResolverPort uint16 = 53
|
||||||
|
|
||||||
|
// We want the refresh time to be short to accommodate DNS resolver changes locally, but not too frequent as to
|
||||||
|
// shuffle the resolver if multiple are configured.
|
||||||
|
refreshFreq = 5 * time.Minute
|
||||||
|
refreshTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Virtual DNS service address
|
||||||
|
VirtualDNSServiceAddr = netip.AddrPortFrom(netip.MustParseAddr("2606:4700:0cf1:2000:0000:0000:0000:0001"), 53)
|
||||||
|
|
||||||
|
defaultResolverAddr = netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), defaultResolverPort)
|
||||||
|
)
|
||||||
|
|
||||||
|
type netDial func(network string, address string) (net.Conn, error)
|
||||||
|
|
||||||
|
// DNSResolverService will make DNS requests to the local DNS resolver via the Dial method.
|
||||||
|
type DNSResolverService struct {
|
||||||
|
addresses []netip.AddrPort
|
||||||
|
addressesM sync.RWMutex
|
||||||
|
static bool
|
||||||
|
dialer ingress.OriginDialer
|
||||||
|
resolver peekResolver
|
||||||
|
logger *zerolog.Logger
|
||||||
|
metrics Metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDNSResolverService(dialer ingress.OriginDialer, logger *zerolog.Logger, metrics Metrics) *DNSResolverService {
|
||||||
|
return &DNSResolverService{
|
||||||
|
addresses: []netip.AddrPort{defaultResolverAddr},
|
||||||
|
dialer: dialer,
|
||||||
|
resolver: &resolver{dialFunc: net.Dial},
|
||||||
|
logger: logger,
|
||||||
|
metrics: metrics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStaticDNSResolverService(resolverAddrs []netip.AddrPort, dialer ingress.OriginDialer, logger *zerolog.Logger, metrics Metrics) *DNSResolverService {
|
||||||
|
s := NewDNSResolverService(dialer, logger, metrics)
|
||||||
|
s.addresses = resolverAddrs
|
||||||
|
s.static = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNSResolverService) DialTCP(ctx context.Context, _ netip.AddrPort) (net.Conn, error) {
|
||||||
|
s.metrics.IncrementDNSTCPRequests()
|
||||||
|
dest := s.getAddress()
|
||||||
|
// The dialer ignores the provided address because the request will instead go to the local DNS resolver.
|
||||||
|
return s.dialer.DialTCP(ctx, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNSResolverService) DialUDP(_ netip.AddrPort) (net.Conn, error) {
|
||||||
|
s.metrics.IncrementDNSUDPRequests()
|
||||||
|
dest := s.getAddress()
|
||||||
|
// The dialer ignores the provided address because the request will instead go to the local DNS resolver.
|
||||||
|
return s.dialer.DialUDP(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartRefreshLoop is a routine that is expected to run in the background to update the DNS local resolver if
|
||||||
|
// adjusted while the cloudflared process is running.
|
||||||
|
// Does not run when the resolver was provided with external resolver addresses via CLI.
|
||||||
|
func (s *DNSResolverService) StartRefreshLoop(ctx context.Context) {
|
||||||
|
if s.static {
|
||||||
|
s.logger.Debug().Msgf("Canceled DNS local resolver refresh loop because static resolver addresses were provided: %s", s.addresses)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Call update first to load an address before handling traffic
|
||||||
|
err := s.update(ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Err(err).Msg("Failed to initialize DNS local resolver")
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.Tick(refreshFreq):
|
||||||
|
err := s.update(ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Err(err).Msg("Failed to refresh DNS local resolver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DNSResolverService) update(ctx context.Context) error {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, refreshTimeout)
|
||||||
|
defer cancel()
|
||||||
|
// Make a standard DNS request to a well-known DNS record that will last a long time
|
||||||
|
_, err := s.resolver.lookupNetIP(ctx, defaultLookupHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the address before updating internal reference
|
||||||
|
_, address := s.resolver.addr()
|
||||||
|
peekAddrPort, err := netip.ParseAddrPort(address)
|
||||||
|
if err == nil {
|
||||||
|
s.setAddress(peekAddrPort)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// It's possible that the address didn't have an attached port, attempt to parse just the address and use
|
||||||
|
// the default port 53
|
||||||
|
peekAddr, err := netip.ParseAddr(address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.setAddress(netip.AddrPortFrom(peekAddr, defaultResolverPort))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the address from the peekResolver or from the static addresses if provided.
|
||||||
|
// If multiple addresses are provided in the static addresses pick one randomly.
|
||||||
|
func (s *DNSResolverService) getAddress() netip.AddrPort {
|
||||||
|
s.addressesM.RLock()
|
||||||
|
defer s.addressesM.RUnlock()
|
||||||
|
l := len(s.addresses)
|
||||||
|
if l <= 0 {
|
||||||
|
return defaultResolverAddr
|
||||||
|
}
|
||||||
|
if l == 1 {
|
||||||
|
return s.addresses[0]
|
||||||
|
}
|
||||||
|
// Only initialize the random selection if there is more than one element in the list.
|
||||||
|
var i int64 = 0
|
||||||
|
r, err := rand.Int(rand.Reader, big.NewInt(int64(l)))
|
||||||
|
// We ignore errors from crypto rand and use index 0; this should be extremely unlikely and the
|
||||||
|
// list index doesn't need to be cryptographically secure, but linters insist.
|
||||||
|
if err == nil {
|
||||||
|
i = r.Int64()
|
||||||
|
}
|
||||||
|
return s.addresses[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock and update the address used for the local DNS resolver
|
||||||
|
func (s *DNSResolverService) setAddress(addr netip.AddrPort) {
|
||||||
|
s.addressesM.Lock()
|
||||||
|
defer s.addressesM.Unlock()
|
||||||
|
if !slices.Contains(s.addresses, addr) {
|
||||||
|
s.logger.Debug().Msgf("Updating DNS local resolver: %s", addr)
|
||||||
|
}
|
||||||
|
// We only store one address when reading the peekResolver, so we just replace the whole list.
|
||||||
|
s.addresses = []netip.AddrPort{addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
type peekResolver interface {
|
||||||
|
addr() (network string, address string)
|
||||||
|
lookupNetIP(ctx context.Context, host string) ([]netip.Addr, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolver is a shim that inspects the go runtime's DNS resolution process to capture the DNS resolver
|
||||||
|
// address used to complete a DNS request.
|
||||||
|
type resolver struct {
|
||||||
|
network string
|
||||||
|
address string
|
||||||
|
dialFunc netDial
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolver) addr() (network string, address string) {
|
||||||
|
return r.network, r.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolver) lookupNetIP(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||||
|
resolver := &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
// Use the peekDial to inspect the results of the DNS resolver used during the LookupIPAddr call.
|
||||||
|
Dial: r.peekDial,
|
||||||
|
}
|
||||||
|
return resolver.LookupNetIP(ctx, "ip", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolver) peekDial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
r.network = network
|
||||||
|
r.address = address
|
||||||
|
return r.dialFunc(network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSDialer creates a custom dialer for the DNS resolver service to utilize.
|
||||||
|
func NewDNSDialer() *ingress.Dialer {
|
||||||
|
return &ingress.Dialer{
|
||||||
|
Dialer: net.Dialer{
|
||||||
|
// We want short timeouts for the DNS requests
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
// We do not want keep alive since the edge will not reuse TCP connections per request
|
||||||
|
KeepAlive: -1,
|
||||||
|
KeepAliveConfig: net.KeepAliveConfig{
|
||||||
|
Enable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package origins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSResolver_DefaultResolver(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
service := NewDNSResolverService(NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
mockResolver := &mockPeekResolver{
|
||||||
|
address: "127.0.0.2:53",
|
||||||
|
}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
validateAddrs(t, []netip.AddrPort{defaultResolverAddr}, service.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStaticDNSResolver_DefaultResolver(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
addresses := []netip.AddrPort{netip.MustParseAddrPort("1.1.1.1:53"), netip.MustParseAddrPort("1.0.0.1:53")}
|
||||||
|
service := NewStaticDNSResolverService(addresses, NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
mockResolver := &mockPeekResolver{
|
||||||
|
address: "127.0.0.2:53",
|
||||||
|
}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
validateAddrs(t, addresses, service.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSResolver_UpdateResolverAddress(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
service := NewDNSResolverService(NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
|
||||||
|
mockResolver := &mockPeekResolver{}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
addr string
|
||||||
|
expected netip.AddrPort
|
||||||
|
}{
|
||||||
|
{"127.0.0.2:53", netip.MustParseAddrPort("127.0.0.2:53")},
|
||||||
|
// missing port should be added (even though this is unlikely to happen)
|
||||||
|
{"127.0.0.3", netip.MustParseAddrPort("127.0.0.3:53")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
mockResolver.address = test.addr
|
||||||
|
// Update the resolver address
|
||||||
|
err := service.update(t.Context())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// Validate expected
|
||||||
|
validateAddrs(t, []netip.AddrPort{test.expected}, service.addresses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStaticDNSResolver_RefreshLoopExits(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
addresses := []netip.AddrPort{netip.MustParseAddrPort("1.1.1.1:53"), netip.MustParseAddrPort("1.0.0.1:53")}
|
||||||
|
service := NewStaticDNSResolverService(addresses, NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
|
||||||
|
mockResolver := &mockPeekResolver{
|
||||||
|
address: "127.0.0.2:53",
|
||||||
|
}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go service.StartRefreshLoop(ctx)
|
||||||
|
|
||||||
|
// Wait for the refresh loop to end _and_ not update the addresses
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Validate expected
|
||||||
|
validateAddrs(t, addresses, service.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSResolver_UpdateResolverAddressInvalid(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
service := NewDNSResolverService(NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
mockResolver := &mockPeekResolver{}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
invalidAddresses := []string{
|
||||||
|
"999.999.999.999",
|
||||||
|
"localhost",
|
||||||
|
"255.255.255",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range invalidAddresses {
|
||||||
|
mockResolver.address = addr
|
||||||
|
// Update the resolver address should not update for these invalid addresses
|
||||||
|
err := service.update(t.Context())
|
||||||
|
if err == nil {
|
||||||
|
t.Error("service update should throw an error")
|
||||||
|
}
|
||||||
|
// Validate expected
|
||||||
|
validateAddrs(t, []netip.AddrPort{defaultResolverAddr}, service.addresses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSResolver_UpdateResolverErrorIgnored(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
service := NewDNSResolverService(NewDNSDialer(), &log, &noopMetrics{})
|
||||||
|
resolverErr := errors.New("test resolver error")
|
||||||
|
mockResolver := &mockPeekResolver{err: resolverErr}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
// Update the resolver address should not update when the resolver cannot complete the lookup
|
||||||
|
err := service.update(t.Context())
|
||||||
|
if err != resolverErr {
|
||||||
|
t.Error("service update should throw an error")
|
||||||
|
}
|
||||||
|
// Validate expected
|
||||||
|
validateAddrs(t, []netip.AddrPort{defaultResolverAddr}, service.addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSResolver_DialUDPUsesResolvedAddress(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
mockDialer := &mockDialer{expected: defaultResolverAddr}
|
||||||
|
service := NewDNSResolverService(mockDialer, &log, &noopMetrics{})
|
||||||
|
mockResolver := &mockPeekResolver{}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
// Attempt a dial to 127.0.0.2:53 which should be ignored and instead resolve to 127.0.0.1:53
|
||||||
|
_, err := service.DialUDP(netip.MustParseAddrPort("127.0.0.2:53"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDNSResolver_DialTCPUsesResolvedAddress(t *testing.T) {
|
||||||
|
log := zerolog.Nop()
|
||||||
|
mockDialer := &mockDialer{expected: defaultResolverAddr}
|
||||||
|
service := NewDNSResolverService(mockDialer, &log, &noopMetrics{})
|
||||||
|
mockResolver := &mockPeekResolver{}
|
||||||
|
service.resolver = mockResolver
|
||||||
|
|
||||||
|
// Attempt a dial to 127.0.0.2:53 which should be ignored and instead resolve to 127.0.0.1:53
|
||||||
|
_, err := service.DialTCP(t.Context(), netip.MustParseAddrPort("127.0.0.2:53"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockPeekResolver struct {
|
||||||
|
err error
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockPeekResolver) addr() (network, address string) {
|
||||||
|
return "udp", r.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockPeekResolver) lookupNetIP(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||||
|
// We can return an empty result as it doesn't matter as long as the lookup doesn't fail
|
||||||
|
return []netip.Addr{}, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockDialer struct {
|
||||||
|
expected netip.AddrPort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *mockDialer) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
if d.expected != addr {
|
||||||
|
return nil, errors.New("unexpected address dialed")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *mockDialer) DialUDP(addr netip.AddrPort) (net.Conn, error) {
|
||||||
|
if d.expected != addr {
|
||||||
|
return nil, errors.New("unexpected address dialed")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAddrs(t *testing.T, expected []netip.AddrPort, actual []netip.AddrPort) {
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Errorf("addresses should only contain one element: %s", actual)
|
||||||
|
}
|
||||||
|
for _, e := range expected {
|
||||||
|
if !slices.Contains(actual, e) {
|
||||||
|
t.Errorf("missing address: %s in %s", e, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package origins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
namespace = "cloudflared"
|
||||||
|
subsystem = "virtual_origins"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Metrics interface {
|
||||||
|
IncrementDNSUDPRequests()
|
||||||
|
IncrementDNSTCPRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
type metrics struct {
|
||||||
|
dnsResolverRequests *prometheus.CounterVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metrics) IncrementDNSUDPRequests() {
|
||||||
|
m.dnsResolverRequests.WithLabelValues("udp").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metrics) IncrementDNSTCPRequests() {
|
||||||
|
m.dnsResolverRequests.WithLabelValues("tcp").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetrics(registerer prometheus.Registerer) Metrics {
|
||||||
|
m := &metrics{
|
||||||
|
dnsResolverRequests: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: "dns_requests_total",
|
||||||
|
Help: "Total count of DNS requests that have been proxied to the virtual DNS resolver origin",
|
||||||
|
}, []string{"protocol"}),
|
||||||
|
}
|
||||||
|
registerer.MustRegister(m.dnsResolverRequests)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package origins
|
||||||
|
|
||||||
|
type noopMetrics struct{}
|
||||||
|
|
||||||
|
func (noopMetrics) IncrementDNSUDPRequests() {}
|
||||||
|
func (noopMetrics) IncrementDNSTCPRequests() {}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue