├── .dockerignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── documentation.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ ├── auto-merge.yaml │ ├── generate-pgo.yaml │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── .goreleaser.yaml ├── .semgrepignore ├── CODEOWNERS ├── Dockerfile ├── Dockerfile.goreleaser ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── chart ├── .helmignore ├── Chart.yaml ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── configmap.yaml │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── tests │ │ └── test-connection.yaml └── values.yaml ├── cmd └── teler-proxy │ └── main.go ├── common ├── config.go ├── errors.go ├── options.go ├── tls.go ├── utils.go ├── validator.go ├── validator_test.go └── vars.go ├── demo ├── Makefile ├── README.md ├── config │ ├── dvwa │ │ └── config.inc.php │ └── teler-waf │ │ └── config.yaml └── docker-compose.yaml ├── go.mod ├── go.sum ├── internal ├── cron │ ├── cron.go │ └── task.go ├── logger │ └── logger.go ├── runner │ ├── const.go │ ├── errors.go │ ├── options.go │ ├── runner.go │ ├── shutdown.go │ ├── utils.go │ ├── vars.go │ └── watcher.go └── writer │ └── writer.go ├── pkg └── tunnel │ ├── tunnel.go │ └── tunnel_test.go ├── teler-waf.conf.example.json └── teler-waf.conf.example.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/.gitignore 3 | **/.github 4 | **/Dockerfile 5 | **/.dockerignore 6 | **/demo 7 | **/*.out 8 | **/*.test 9 | **/bin 10 | **/dist -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["dwisiswant0"] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[bug]" 5 | labels: 'bug' 6 | assignees: dwisiswant0 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | 16 | Steps to reproduce the behavior: 17 | 18 | ```go 19 | Your teler usage & options... 20 | ``` 21 | 22 | teler WAF configuration: 23 | 24 | ```yaml 25 | # your teler WAF configuration... 26 | ``` 27 | 28 | ```json 29 | // or in JSON format... 30 | ``` 31 | 32 | **Expected behavior** 33 | 34 | A clear and concise description of what you expected to happen. 35 | 36 | **Screenshots** 37 | 38 | If applicable, add screenshots to help explain your problem. 39 | 40 | **Environment (please complete the following information):** 41 | 42 | - OS: [e.g. mac, linux] 43 | - OS version: [uname -a] 44 | - teler Proxy version: [teler-proxy -V] 45 | 46 | **Additional context** 47 | 48 | Add any other context about the problem here. Full output log is probably a helpful thing to add here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/teler-sh/teler-proxy/discussions 5 | about: Ask questions and discuss with other community members -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Suggest better docs coverage for a particular tool or process. 4 | title: "[docs]" 5 | labels: 'documentation' 6 | --- 7 | 8 | 13 | 14 | ## Summary 15 | 16 | _What problem(s) did you run into that caused you to request additional documentation? What questions do you think we should answer? What, if any, existing documentation relates to this proposal?_ 17 | 18 | Some recommended topics to cover: 19 | 20 | - _List the topics you think should be here._ 21 | - _This list does not need to be exhaustive!_ 22 | 23 | ### Motivation 24 | 25 | _Why should we document this and who will benefit from it?_ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[feature]" 5 | labels: 'enhancement' 6 | assignees: dwisiswant0 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT: Please do not create a PR without creating an issue first!** 2 | 3 | _(Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request)._ 4 | 5 | ### Summary 6 | 7 | 8 | 9 | 10 | _Explains the information and/or motivation for making this changes..._ 11 | 12 | 13 | ### Proposed of changes 14 | 15 | This PR fixes/implements the following **bugs/features**: 16 | 17 | - Bug 1 18 | - Bug 2 19 | - Feature 1 20 | - Feature 2 21 | - Breaking changes 22 | 23 | 24 | 25 | ### How has this been tested? 26 | 27 | Proof: 28 | 29 | 30 | 31 | ### Closing issues 32 | 33 | Fixes # 34 | 35 | ### Checklist: 36 | 37 | 38 | 39 | 40 | - [ ] My code follows the code style of this project. 41 | - [ ] My changes successfully ran and pass linters locally (run `make lint`). 42 | - [ ] I have written new tests for my changes. 43 | - [ ] My changes successfully ran and pass tests locally. 44 | - [ ] My change requires a change to the documentation. 45 | - [ ] I have updated the documentation accordingly. -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | labels: 8 | - "dependencies" 9 | groups: 10 | modules: 11 | patterns: ["*"] 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | labels: 18 | - "dependencies" 19 | groups: 20 | actions: 21 | patterns: ["*"] -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yaml: -------------------------------------------------------------------------------- 1 | name: Auto Merge PR 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, reopened] 6 | pull_request_review: 7 | types: [submitted] 8 | workflow_call: 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | auto-merge: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: teler-sh/actions/auto-merge@v1 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/generate-pgo.yaml: -------------------------------------------------------------------------------- 1 | name: "Generate PGO" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "pkg/tunnel/**.go" 9 | - "go.*" 10 | workflow_dispatch: 11 | 12 | env: 13 | PGO_FILE: "default.pgo" 14 | PGO_BRANCH: "pgo" 15 | 16 | jobs: 17 | generate-pgo: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: actions/setup-go@v5 23 | with: 24 | go-version-file: 'go.mod' 25 | cache-dependency-path: '**/go.sum' 26 | 27 | - run: make pgo && cp ${{ env.PGO_FILE }} /tmp/ 28 | 29 | - uses: actions/checkout@v4 30 | with: 31 | ref: ${{ env.PGO_BRANCH }} 32 | 33 | - run: | 34 | git rm --cached ${{ env.PGO_FILE }} 35 | cp /tmp/${{ env.PGO_FILE }} . 36 | 37 | - uses: actions/upload-artifact@v4 38 | if: (success() || failure()) 39 | with: 40 | name: pgo 41 | path: ${{ env.PGO_FILE }} 42 | retention-days: 90 43 | overwrite: true 44 | 45 | - name: pushing PGO file 46 | run: | 47 | git config --local user.email "ghost@users.noreply.github.com" 48 | git config --local user.name "ghost" 49 | git add ${{ env.PGO_FILE }} 50 | git commit -m "build(pgo): generate default PGO profile :robot:" 51 | git push origin ${{ env.PGO_BRANCH }} 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*.*.**"] 6 | workflow_dispatch: 7 | inputs: 8 | tag: 9 | description: "Target tag to release" 10 | required: true 11 | type: string 12 | 13 | env: 14 | PGO_FILE: "default.pgo" 15 | COSIGN_KEY_PREFIX: release-${{ github.event.repository.name }}_${{ github.ref_name }} 16 | 17 | permissions: 18 | contents: write 19 | packages: write 20 | 21 | jobs: 22 | release: 23 | runs-on: ubuntu-latest 24 | container: 25 | image: ghcr.io/goreleaser/goreleaser-cross:latest 26 | steps: 27 | # Downloading PGO file 28 | 29 | - uses: actions/download-artifact@v4 30 | id: artifact 31 | with: 32 | name: pgo 33 | continue-on-error: true 34 | 35 | - uses: actions/checkout@v4 36 | if: steps.artifact.outcome != 'success' 37 | with: 38 | ref: pgo 39 | 40 | - run: cp ${{ env.PGO_FILE }} /tmp 41 | if: steps.artifact.outcome != 'success' 42 | 43 | # Releasing 44 | 45 | - uses: actions/checkout@v4 46 | with: 47 | ref: "${{ inputs.tag || github.ref_name }}" 48 | 49 | - run: cp /tmp/${{ env.PGO_FILE }} . 50 | if: steps.artifact.outcome != 'success' 51 | 52 | - uses: teler-sh/actions/setup-go@v1 53 | 54 | - name: Build PGO file 55 | run: '[ -f "${{ env.PGO_FILE }}" ] || make pgo' 56 | 57 | - run: git config --global --add safe.directory "$(pwd)" 58 | 59 | - uses: anchore/sbom-action/download-syft@v0 60 | 61 | - uses: docker/login-action@v3 62 | with: 63 | registry: ghcr.io 64 | username: ${{ github.actor }} 65 | password: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | - uses: teler-sh/actions/cosign/generate@v1 68 | with: 69 | password: ${{ secrets.COSIGN_PASSWORD }} 70 | key-prefix: ${{ env.COSIGN_KEY_PREFIX }} 71 | 72 | - run: goreleaser release --clean --skip validate 73 | env: 74 | COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} 75 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 76 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - "**.go" 7 | - "go.mod" 8 | pull_request: 9 | branches: 10 | - "**" 11 | paths: 12 | - "**.go" 13 | - "go.mod" 14 | workflow_dispatch: 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.ref }} 18 | cancel-in-progress: true 19 | 20 | permissions: 21 | actions: read 22 | contents: read 23 | pull-requests: write 24 | security-events: write 25 | 26 | name: tests 27 | jobs: 28 | tests: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: teler-sh/actions/setup-go@v1 33 | - uses: teler-sh/actions/resources@v1 34 | - run: make ci 35 | if: (github.event_name != 'workflow_dispatch') 36 | 37 | sast: 38 | runs-on: ubuntu-latest 39 | needs: tests 40 | continue-on-error: true 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: teler-sh/actions/dependency-review@v1 44 | if: (github.event_name == 'push') 45 | - uses: teler-sh/actions/golangci-lint@v1 46 | - uses: teler-sh/actions/semgrep@v1 47 | - uses: teler-sh/actions/codeql@v1 48 | with: 49 | lang: go 50 | 51 | codecov: 52 | runs-on: ubuntu-latest 53 | needs: tests 54 | steps: 55 | - uses: actions/checkout@v4 56 | with: 57 | fetch-depth: 2 58 | - uses: teler-sh/actions/setup-go@v1 59 | - uses: teler-sh/actions/resources@v1 60 | - run: make cover 61 | - name: Upload coverage to Codecov 62 | uses: codecov/codecov-action@v5 63 | with: 64 | token: ${{ secrets.CODECOV_TOKEN }} 65 | file: /tmp/teler-coverage.out 66 | verbose: true 67 | fail_ci_if_error: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | /app 8 | /main 9 | /demo 10 | /examples/*/app 11 | /examples/*/main 12 | /examples/*/demo 13 | 14 | /bin/* 15 | /dist/* 16 | /teler-proxy 17 | 18 | # Test binary, built with `go test -c` 19 | *.test 20 | 21 | # Output of the go coverage tool, specifically when used with LiteIDE 22 | *.out 23 | 24 | # Dependency directories (remove the comment below to include it) 25 | # vendor/ 26 | 27 | *.pgo -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | env: 2 | - CGO_ENABLED=1 3 | - COSIGN_YES=true 4 | 5 | before: 6 | hooks: 7 | - go mod tidy 8 | - go mod verify 9 | 10 | builds: 11 | - id: "darwin-amd64" 12 | binary: "{{ .ProjectName }}" 13 | main: ./cmd/{{ .ProjectName }} 14 | goarch: 15 | - amd64 16 | goos: 17 | - darwin 18 | env: 19 | - CC=o64-clang 20 | - CXX=o64-clang++ 21 | flags: 22 | - -trimpath 23 | - -pgo={{ .Env.PGO_FILE }} 24 | ldflags: 25 | - -s -w -X github.com/teler-sh/{{ .ProjectName }}/common.Version={{ .Version }} 26 | 27 | - id: "darwin-arm64" 28 | binary: "{{ .ProjectName }}" 29 | main: ./cmd/{{ .ProjectName }} 30 | goarch: 31 | - arm64 32 | goos: 33 | - darwin 34 | env: 35 | - CC=oa64-clang 36 | - CXX=oa64-clang++ 37 | flags: 38 | - -trimpath 39 | - -pgo={{ .Env.PGO_FILE }} 40 | ldflags: 41 | - -s -w -X github.com/teler-sh/{{ .ProjectName }}/common.Version={{ .Version }} 42 | 43 | - id: "linux-amd64" 44 | binary: "{{ .ProjectName }}" 45 | main: ./cmd/{{ .ProjectName }} 46 | env: 47 | - CC=x86_64-linux-gnu-gcc 48 | - CXX=x86_64-linux-gnu-g++ 49 | goarch: 50 | - amd64 51 | goos: 52 | - linux 53 | flags: 54 | - -trimpath 55 | - -pgo={{ .Env.PGO_FILE }} 56 | ldflags: 57 | - -s -w -X github.com/teler-sh/{{ .ProjectName }}/common.Version={{ .Version }} 58 | 59 | - id: "linux-arm64" 60 | binary: "{{ .ProjectName }}" 61 | main: ./cmd/{{ .ProjectName }} 62 | goarch: 63 | - arm64 64 | goos: 65 | - linux 66 | env: 67 | - CC=aarch64-linux-gnu-gcc 68 | - CXX=aarch64-linux-gnu-g++ 69 | flags: 70 | - -trimpath 71 | - -pgo={{ .Env.PGO_FILE }} 72 | ldflags: 73 | - -s -w -X github.com/teler-sh/{{ .ProjectName }}/common.Version={{ .Version }} 74 | 75 | - id: "windows-amd64" 76 | binary: "{{ .ProjectName }}" 77 | main: ./cmd/{{ .ProjectName }} 78 | goarch: 79 | - amd64 80 | goos: 81 | - windows 82 | env: 83 | - CC=x86_64-w64-mingw32-gcc 84 | - CXX=x86_64-w64-mingw32-g++ 85 | flags: 86 | - -trimpath 87 | - -pgo={{ .Env.PGO_FILE }} 88 | - -buildmode=exe 89 | ldflags: 90 | - -s -w -X github.com/teler-sh/{{ .ProjectName }}/common.Version={{ .Version }} 91 | 92 | archives: 93 | - id: build 94 | builds: 95 | - "darwin-amd64" 96 | - "darwin-arm64" 97 | - "linux-amd64" 98 | - "linux-arm64" 99 | - "windows-amd64" 100 | name_template: >- 101 | {{- .ProjectName }}_{{- .Tag }}- 102 | {{- .Os }}_ 103 | {{- if eq .Arch "amd64" }}x86_64 104 | {{- else if eq .Arch "386" }}i386 105 | {{- else }}{{ .Arch }}{{ end }} 106 | format: binary 107 | 108 | signs: 109 | - cmd: cosign 110 | stdin: "{{ .Env.COSIGN_PASSWORD }}" 111 | args: 112 | - "sign-blob" 113 | - "--key" 114 | - "{{ .Env.COSIGN_KEY_PREFIX }}.key" 115 | - "--output-signature" 116 | - "${signature}" 117 | - "${artifact}" 118 | artifacts: binary 119 | 120 | sboms: 121 | - artifacts: binary 122 | documents: 123 | - >- 124 | {{- .ProjectName }}_{{- .Tag }}- 125 | {{- .Os }}_ 126 | {{- if eq .Arch "amd64" }}x86_64 127 | {{- else if eq .Arch "386" }}i386 128 | {{- else }}{{ .Arch }}{{ end }}.sbom 129 | 130 | checksum: 131 | name_template: "checksums-{{ .ProjectName }}_{{ .Tag }}.txt" 132 | snapshot: 133 | name_template: "{{ .Tag }}-{{ .ShortCommit }}" 134 | 135 | dockers: 136 | - image_templates: 137 | - "ghcr.io/teler-sh/{{ .ProjectName }}:{{ .Tag }}" 138 | - "ghcr.io/teler-sh/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}" 139 | - "ghcr.io/teler-sh/{{ .ProjectName }}:v{{ .Major }}" 140 | - "ghcr.io/teler-sh/{{ .ProjectName }}:latest" 141 | dockerfile: Dockerfile.goreleaser 142 | use: docker 143 | build_flag_templates: 144 | - "--pull" 145 | - "--label=org.opencontainers.image.authors=\"Dwi Siswanto \"" 146 | - "--label=org.opencontainers.image.created={{ .Date }}" 147 | - "--label=org.opencontainers.image.description=\"teler Proxy enabling seamless integration with teler WAF to protect locally running web service against a variety of web-based attacks\"" 148 | - "--label=org.opencontainers.image.licenses=Apache-2.0" 149 | - "--label=org.opencontainers.image.ref.name={{ .Tag }}" 150 | - "--label=org.opencontainers.image.revision={{ .FullCommit }}" 151 | - "--label=org.opencontainers.image.title={{ .ProjectName }}" 152 | - "--label=org.opencontainers.image.url=https://github.com/teler-sh/{{ .ProjectName }}" 153 | - "--label=org.opencontainers.image.version={{ .Version }}" 154 | 155 | docker_signs: 156 | - cmd: cosign 157 | stdin: "{{ .Env.COSIGN_PASSWORD }}" 158 | args: 159 | - "sign" 160 | - "--key" 161 | - "{{ .Env.COSIGN_KEY_PREFIX }}.key" 162 | - "--upload=false" 163 | - "${artifact}" 164 | artifacts: images 165 | output: true 166 | 167 | changelog: 168 | sort: asc 169 | filters: 170 | exclude: 171 | - "^build" 172 | - "^chore" 173 | - "^ci" 174 | - "^docs" 175 | - "^refactor" 176 | - "^test" 177 | - Merge pull request 178 | - Merge branch 179 | 180 | release: 181 | draft: true 182 | prerelease: auto 183 | footer: | 184 | ## Verify 185 | 186 | > [!IMPORTANT] 187 | > It is strongly recommended to verify the integrity and security of the release assets before executing them. This helps mitigate potential risks associated with running unverified files. 188 | 189 | First, verify the file using checksums. 190 | 191 | ```bash 192 | sha256sum --check --ignore-missing checksums-{{ .ProjectName }}_{{ .Tag }}.txt 193 | ``` 194 | 195 | Then, ensure the authenticity of the release asset with [Cosign](https://github.com/sigstore/cosign): 196 | 197 | ```bash 198 | cosign verify-blob --key release-{{ .ProjectName }}_{{ .Tag }}.pub --signature {{ .ProjectName }}_{{ .Tag }}-OS_ARCH.sig {{ .ProjectName }}_{{ .Tag }}-OS_ARCH 199 | ``` 200 | name_template: "{{ .Tag }}" 201 | extra_files: 202 | - glob: "{{ .Env.COSIGN_KEY_PREFIX }}.pub" 203 | - glob: "teler-waf.conf.example.*" -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | **/_test.go 2 | /.github/workflows/ 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dwisiswant0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | ARG VERSION="docker" 4 | ARG LDFLAGS="-s -w -X github.com/teler-sh/teler-proxy/common.Version=${VERSION}" 5 | ARG PGO_FILE="default.pgo" 6 | 7 | LABEL org.opencontainers.image.authors="Dwi Siswanto " 8 | LABEL org.opencontainers.image.description="teler Proxy enabling seamless integration with teler WAF to protect locally running web service against a variety of web-based attacks" 9 | LABEL org.opencontainers.image.licenses="Apache-2.0" 10 | LABEL org.opencontainers.image.ref.name="${VERSION}" 11 | LABEL org.opencontainers.image.title="teler-proxy" 12 | LABEL org.opencontainers.image.url="https://github.com/teler-sh/teler-proxy" 13 | LABEL org.opencontainers.image.version="${VERSION}" 14 | 15 | WORKDIR /app 16 | 17 | COPY ["go.mod", "${PGO_FILE}", "./"] 18 | RUN go mod download 19 | 20 | COPY . . 21 | 22 | ENV CGO_ENABLED=1 23 | 24 | RUN apk add build-base 25 | RUN go build \ 26 | -pgo "${PGO_FILE}" \ 27 | -ldflags "${LDFLAGS}" \ 28 | -o /bin/teler-proxy \ 29 | -v ./cmd/teler-proxy 30 | 31 | RUN addgroup \ 32 | -g "2000" \ 33 | teler-proxy && \ 34 | adduser \ 35 | -g "teler-proxy" \ 36 | -G "teler-proxy" \ 37 | -u "1000" \ 38 | -h "/app" \ 39 | -D teler-proxy 40 | 41 | USER teler-proxy:teler-proxy 42 | 43 | ENTRYPOINT ["/bin/teler-proxy"] 44 | -------------------------------------------------------------------------------- /Dockerfile.goreleaser: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | COPY teler-proxy /teler-proxy 4 | 5 | ENTRYPOINT ["/teler-proxy"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Dwi Siswanto 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APP_NAME = teler-proxy 2 | VERSION = $(shell git describe --always --tags) 3 | 4 | GO_MOD_VERSION := $(shell grep -Po '^go \K([0-9]+\.[0-9]+(\.[0-9]+)?)$$' go.mod) 5 | GO := go${GO_MOD_VERSION} 6 | GO_LDFLAGS = "-s -w -X 'github.com/teler-sh/teler-proxy/common.Version=${VERSION}'" 7 | 8 | ifeq ($(shell which ${GO}),) 9 | GO = go 10 | endif 11 | 12 | vet: 13 | $(GO) vet ./... 14 | 15 | lint: 16 | golangci-lint run --tests=false ./... 17 | 18 | semgrep: 19 | semgrep --config auto 20 | 21 | bench: 22 | $(GO) test ./pkg/tunnel/... -run "^$$" -bench . -cpu 4 -benchmem $(ARGS) 23 | 24 | cover: FILE := /tmp/teler-coverage.out # Define coverage file 25 | cover: PKG := ./pkg/tunnel/... 26 | cover: 27 | $(GO) test -race -coverprofile=$(FILE) -covermode=atomic $(PKG) 28 | $(GO) tool cover -func=$(FILE) 29 | 30 | pprof: ARGS := -cpuprofile=cpu.out -memprofile=mem.out -benchtime 30s 31 | pprof: bench 32 | 33 | pgo: pprof 34 | pgo: 35 | cp cpu.out default.pgo 36 | 37 | test: 38 | $(GO) test -race -v ./pkg/tunnel/... 39 | 40 | test-all: test vet lint semgrep 41 | 42 | report: 43 | goreportcard-cli 44 | 45 | build: 46 | @echo "Building binary" 47 | @mkdir -p bin/ 48 | CGO_ENABLED="1" go build -ldflags ${GO_LDFLAGS} -trimpath $(ARGS) -o ./bin/${APP_NAME} ./cmd/${APP_NAME} 49 | 50 | build-pgo: ARGS := -pgo=$(shell pwd)/default.pgo 51 | build-pgo: build 52 | 53 | docker: 54 | @echo "Building image" 55 | docker build -t ${APP_NAME}:latest --build-arg="VERSION=${VERSION}" . 56 | 57 | clean: 58 | @echo "Removing binaries" 59 | @rm -rf bin/ 60 | 61 | teler-proxy: build 62 | 63 | ci: vet build clean 64 | 65 | all: test report build -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 by Dwi Siswanto. All rights reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # teler Proxy 2 | 3 | [![codecov](https://codecov.io/gh/teler-sh/teler-proxy/graph/badge.svg?token=QST60Y6BDD)](https://codecov.io/gh/teler-sh/teler-proxy) 4 | [![Tests](https://github.com/teler-sh/teler-proxy/actions/workflows/tests.yaml/badge.svg?branch=master)](https://github.com/teler-sh/teler-proxy/actions/workflows/tests.yaml) 5 | [![Release](https://img.shields.io/github/v/release/teler-sh/teler-proxy?color=violet)](https://github.com/teler-sh/teler-proxy/releases) 6 | [![Platform](https://img.shields.io/badge/platform-osx%2Flinux%2Fwindows-blueviolet)](#) 7 | 8 | 9 | 10 | teler Proxy enabling seamless integration with [teler WAF](https://github.com/teler-sh/teler-waf) to protect locally running web service against a variety of web-based attacks, such as OWASP Top 10 categories like cross-site scripting (XSS) or SQL injection, known vulnerabilities or exploits, malicious actors, botnets, unwanted crawlers or scrapers, and directory bruteforce attacks. 11 | 12 | **See also:** 13 | 14 | * [teler-sh/teler](https://github.com/teler-sh/teler): Real-time HTTP Intrusion Detection. 15 | * [teler-sh/teler-waf](https://github.com/teler-sh/teler-waf): Go HTTP middleware that provides teler IDS functionality. 16 | * [teler-sh/teler-caddy](https://github.com/teler-sh/teler-caddy): teler Caddy integrates the powerful security features of teler WAF into the Caddy web server 17 | 18 | https://github.com/teler-sh/teler-proxy/assets/25837540/df36af09-080a-4cff-98d8-fd2071f602fa 19 | 20 | --- 21 | 22 | **Table of Contents** 23 | 24 | * [Architecture](#architecture) 25 | * [Install](#installation) 26 | * [Binary](#binary) 27 | * [Source](#source) 28 | * [Docker](#docker) 29 | * [Usage](#usage) 30 | * [Options](#options) 31 | * [Configuration](#configuration) 32 | * [Excludes](#excludes) 33 | * [Whitelists](#whitelists) 34 | * [Customs](#customs) 35 | * [Customs from File](#customs-from-file) 36 | * [Log File](#log-file) 37 | * [No Stderr](#no-stderr) 38 | * [No Update Check](#no-update-check) 39 | * [Development](#development) 40 | * [In Memory](#in-memory) 41 | * [FalcoSidekick URL](#falcosidekick-url) 42 | * [Verbose](#verbose) 43 | * [Demo](#demo) 44 | * [Community](#community) 45 | * [License](#license) 46 | 47 | ## Architecture 48 | 49 | ```mermaid 50 | %% --- 51 | %% title: teler WAF proxy architecture 52 | %% --- 53 | sequenceDiagram 54 | participant internet as Internet 🌐 55 | box Internal network 56 | participant proxy as teler-proxy 🔐 57 | participant server as Server 💻 58 | end 59 | 60 | internet->>proxy: request 🙋‍♂️ 61 | 62 | Note over proxy: analyze request 🔍 63 | alt if "you're bad! 😈" 64 | proxy->>internet: early return 🏃 65 | else else 👍🏻 66 | proxy->>server: forward request ↪️ 67 | end 68 | 69 | server-->>proxy: respond 💬 70 | proxy->>internet: "copy that!" ↩️ 71 | ``` 72 | 73 | ## Installation 74 | 75 | ### Binary 76 | 77 | Simply, download a pre-built binary from [releases page](https://github.com/teler-sh/teler-proxy/releases). Unpack and run! 78 | 79 | ### Source 80 | 81 | **Dependencies**: 82 | 83 | * **gcc** (GNU Compiler Collection) should be installed & configured to compile teler-waf. 84 | 85 | Using [Go](https://golang.org/doc/install) (v1.20+) compiler: 86 | 87 | ```bash 88 | CGO_ENABLED=1 go install github.com/teler-sh/teler-proxy/cmd/teler-proxy@latest 89 | ``` 90 | 91 | ### — or 92 | 93 | Manual building executable from source code: 94 | 95 | > [!WARNING] 96 | > The `master` branch contains the latest code changes and updates, which might not have undergone thorough testing and quality assurance - thus, you may encounter instability and unexpected behavior. 97 | 98 | ```bash 99 | git clone https://github.com/teler-sh/teler-proxy.git 100 | cd teler-proxy/ 101 | # git checkout [VERSION TAG] 102 | make build 103 | ``` 104 | 105 | > [!TIP] 106 | > If you're using Go version 1.20 or newer, you can build the executable file with our automatically generated default PGO[?] profile _(see [pgo branch](https://github.com/teler-sh/teler-proxy/tree/pgo))_ to improve the performance by using `make build-pgo` command. 107 | 108 | ### Docker 109 | 110 | Pull the [Docker](https://docs.docker.com/get-docker/) image by running: 111 | 112 | ```bash 113 | docker pull ghcr.io/teler-sh/teler-proxy:latest 114 | ``` 115 | 116 | ## Usage 117 | 118 | Simply, `teler-proxy` can be run with: 119 | 120 | ```bash 121 | teler-proxy -d : [OPTIONS...] 122 | ``` 123 | 124 | ### Options 125 | 126 | 127 | 128 | Here are all the options it supports. 129 | 130 | ```bash 131 | teler-proxy -h 132 | ``` 133 | 134 | | **Flag** | **Description** | 135 | | -------------------------- | --------------------------------------------------------------------- | 136 | | -p, --port `` | Set the local port to listen on **(default: 1337)** | 137 | | -d, --dest `:` | Set the destination address for forwarding requests | 138 | | -c, --conf `` | Specify the path to the teler WAF configuration file | 139 | | -f, --format `` | Specify the configuration file format (json/yaml) **(default: yaml)** | 140 | | --cert `` | Specify the path to the SSL certificate file | 141 | | --key `` | Specify the path to the SSL private key file | 142 | | -V, --version | Display the current teler-proxy version | 143 | | -h, --help | Display this helps text | 144 | 145 | ## Configuration 146 | 147 | The configuration is provides a comprehensive set of options to fine-tune and tailor the behavior of the teler Web Application Firewall (WAF). Through the use of the teler WAF configuration (`-c`/`--conf`), you gain full control over how the WAF operates and responds to incoming traffic. 148 | 149 | > [!NOTE] 150 | > When you supply a configuration file and subsequently make alterations to that configuration, teler Proxy will promptly initiate a live reload, ensuring that the updated settings are applied in real-time without the need for manual intervention or restarting the teler Proxy. 151 | 152 | In case you opt not to provide a custom configuration file, the teler WAF will seamlessly apply a default configuration, ensuring that your application remains protected with sensible and reasonable settings. 153 | 154 | The default configuration options are presented below in YAML format: 155 | 156 | ```yaml 157 | excludes: [] 158 | whitelists: [] 159 | customs: [] 160 | customs_from_file: "" 161 | response: 162 | status: 0 163 | html: "" 164 | html_file: "" 165 | log_file: "" 166 | no_stderr: false 167 | no_update_check: false 168 | development: false 169 | in_memory: false 170 | falcosidekick_url: "" 171 | verbose: false 172 | ``` 173 | 174 | Or the equivalent in JSON format: 175 | 176 | ```json 177 | { 178 | "excludes": [], 179 | "whitelists": [], 180 | "customs": [], 181 | "customs_from_file": "", 182 | "response": { 183 | "status": 0, 184 | "html": "", 185 | "html_file": "" 186 | }, 187 | "log_file": "", 188 | "no_stderr": false, 189 | "no_update_check": false, 190 | "development": false, 191 | "in_memory": false, 192 | "falcosidekick_url": "", 193 | "verbose": false 194 | } 195 | ``` 196 | 197 | By leveraging this versatile teler WAF configuration, you can fine-tune the WAF to perfectly align with your specific security requirements, ensuring maximum protection for your web service while enjoying the flexibility and power of teler WAF. 198 | 199 | ### Excludes 200 | 201 | > [!WARNING] 202 | > Threat exclusions (`Excludes`) will be deprecated in the upcoming teler-waf release (**v2**), use [`Whitelists`](#whitelists) instead. See [teler-waf#73](https://github.com/teler-sh/teler-waf/discussions/73). 203 | 204 | Excludes (**excludes**) is a list of threat types (`[]int`) to exclude from the security checks. Please refer to the [docs](https://pkg.go.dev/github.com/teler-sh/teler-waf/threat#Threat). 205 | 206 | > **Note** 207 | > * **1** for `CommonWebAttack` 208 | > * **2** for `CVE` 209 | > * **3** for `BadIPAddress` 210 | > * **4** for `BadReferrer` 211 | > * **5** for `BadCrawler` 212 | > * **6** for `DirectoryBruteforce` 213 | 214 | ### Whitelists 215 | 216 | Whitelists (**whitelists**) is a list of DSL expressions (`[]string`) that match request elements that should be excluded from the security checks. Please refer to the [docs](https://github.com/teler-sh/teler-waf#dsl-expression). 217 | 218 | ### Customs 219 | 220 | Customs (**customs**) is a list of custom security rules (`[]teler.Rule`) to apply to incoming requests. 221 | 222 | These rules can be used to create custom security checks or to override the default security checks provided by teler-waf. Please refer to the [docs](https://github.com/teler-sh/teler-waf#custom-rules). 223 | 224 | ### Customs from File 225 | 226 | Customs from file (**customs_from_file**) specifies the file path or glob pattern (`string`) for loading custom security rules. These rules can be used to create custom security checks or to override the default security checks provided by teler IDS. 227 | 228 | The glob pattern supports wildcards, allowing you to specify multiple files or a directory with matching files. For example, "/path/to/custom/rules/\**/*.yaml" will load all YAML files in the "rules" directory and its subdirectories. Please refer to the [docs](https://github.com/teler-sh/teler-waf#custom-rules). 229 | 230 | ### Custom Response 231 | 232 | Response (**response**) is the configuration for custom error response pages when a request is blocked or rejected. Please refer to the [docs](https://github.com/teler-sh/teler-waf#custom-response). 233 | 234 | ### Log File 235 | 236 | Log file (**log_file**) is the file path (`string`) for the log file to store the security logs. If `log_file` is specified, log messages will be written to the specified file in addition to stderr (if `no_stderr` is **false**). 237 | 238 | ### No Stderr 239 | 240 | No stderr (**no_stderr**) is a boolean flag indicating whether or not to suppress log messages from being printed to the standard error (stderr) stream. 241 | 242 | When set to `true`, log messages will not be printed to stderr. If set to `false`, log messages will be printed to stderr. By default, log messages are printed to stderr (`false`). 243 | 244 | ### No Update Check 245 | 246 | No update check (**no_update_check**) is a boolean flag indicating whether or not to disable automatic threat dataset updates. 247 | 248 | When set to `true`, automatic updates will be disabled. If set to `false`, automatic updates will be enabled. By default, automatic updates are enabled (`false`). Please refer to the [docs](https://github.com/teler-sh/teler-waf#datasets). 249 | 250 | ### Development 251 | 252 | Development (**development**) is a boolean flag that determines whether the request is cached or not. By default, development mode is disabled (`false`) or requests will cached. Please refer to the [docs](https://github.com/teler-sh/teler-waf#development). 253 | 254 | ### In Memory 255 | 256 | In memory (**in_memory**) is a boolean flag that specifies whether or not to load the threat dataset into memory on initialization. 257 | 258 | When set to `true`, the threat dataset will be loaded into memory, which can be useful when running your service or application on a distroless or runtime image, where file access may be limited or slow. If `in_memory` is set to `false`, the threat dataset will be downloaded and stored under the user-level cache directory on the first startup. Subsequent startups will use the cached dataset. Please refer to the [docs](https://github.com/teler-sh/teler-waf#datasets). 259 | 260 | ### FalcoSidekick URL 261 | 262 | FalcoSidekick URL (**falcosidekick_url**) is the URL of the FalcoSidekick endpoint to which teler-waf's events will be forwarded. 263 | 264 | This field should be set to the URL of your FalcoSidekick instance, including the protocol & port (e.g. "http://localhost:2801"). Please refer to the [docs](https://github.com/teler-sh/teler-waf#falco-sidekick). 265 | 266 | ### Verbose 267 | 268 | Verbose (**verbose**) is a boolean flag that controls whether verbose logging is enabled. When set to `true`, it enables detailed and informative logging messages. 269 | 270 | ## Demo 271 | 272 | To experience the power of the teler WAF Proxy in action, simply follow these steps to set up and run the demo located in the [demo/](/demo) directory. 273 | 274 | ## Community 275 | 276 | We use the Google Groups as our dedicated mailing list. Subscribe to [teler-announce](https://groups.google.com/g/teler-announce) via [teler-announce+subscribe@googlegroups.com](mailto:teler-announce+subscribe@googlegroups.com) for important announcements, such as the availability of new releases. This subscription will keep you informed about significant developments related to [teler IDS](https://github.com/teler-sh/teler), [teler WAF](https://github.com/teler-sh/teler-waf), [teler Proxy](https://github.com/teler-sh/teler-proxy), [teler Caddy](https://github.com/teler-sh/teler-caddy) and [teler Resources](https://github.com/teler-sh/teler-resources). 277 | 278 | For any [inquiries](https://github.com/teler-sh/teler-proxy/discussions/categories/q-a), [discussions](https://github.com/teler-sh/teler-proxy/discussions), or [issues](https://github.com/teler-sh/teler-proxy/issues) are being tracked here on GitHub. This is where we actively manage and address these aspects of our community engagement. 279 | 280 | ## License 281 | 282 | This program is developed and maintained by members of Kitabisa Security Team, and this is not an officially supported Kitabisa product. This program is free software: you can redistribute it and/or modify it under the terms of the [Apache-2.0 license](/LICENSE). Kitabisa teler-proxy and any contributions are copyright © by Dwi Siswanto 2023. -------------------------------------------------------------------------------- /chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: teler-proxy 3 | description: >- 4 | A Helm chart for teler Proxy that enabling seamless integration 5 | with teler WAF to protect locally running web service against a 6 | variety of web-based attacks. 7 | type: application 8 | version: 0.0.1 # chart version 9 | appVersion: "v0.0.1" # teler-proxy version 10 | -------------------------------------------------------------------------------- /chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "teler-proxy.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "teler-proxy.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "teler-proxy.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "teler-proxy.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "teler-proxy.name" -}} 5 | {{- default .Chart.Name | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "teler-proxy.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "teler-proxy.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common application information. 35 | */}} 36 | {{- define "teler-proxy.info.company" -}} 37 | teler.sh 38 | {{- end}} 39 | {{- define "teler-proxy.info.product" -}} 40 | proxy 41 | {{- end}} 42 | {{- define "teler-proxy.info.component" -}} 43 | waf 44 | {{- end}} 45 | 46 | {{/* 47 | Common labels 48 | */}} 49 | {{- define "teler-proxy.labels" -}} 50 | app.{{ include "teler-proxy.info.company" . }}/name: {{ include "teler-proxy.info.product" . }} 51 | app.kubernetes.io/component: {{ include "teler-proxy.info.component" . }} 52 | app.kubernetes.io/part-of: {{ include "teler-proxy.info.company" . }} 53 | helm.sh/chart: {{ include "teler-proxy.chart" . }} 54 | {{ include "teler-proxy.selectorLabels" . }} 55 | {{- if .Chart.AppVersion }} 56 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 57 | {{ include "teler-proxy.info.product" . }}.{{ include "teler-proxy.info.company" . }}/version: {{ .Chart.AppVersion | quote }} 58 | {{- end }} 59 | app.kubernetes.io/managed-by: {{ .Release.Service }} 60 | {{- end }} 61 | 62 | {{/* 63 | Selector labels 64 | */}} 65 | {{- define "teler-proxy.selectorLabels" -}} 66 | app.kubernetes.io/name: {{ include "teler-proxy.name" . }} 67 | app.kubernetes.io/instance: {{ .Release.Name }} 68 | {{- end }} 69 | 70 | {{/* 71 | Create the name of the service account to use 72 | */}} 73 | {{- define "teler-proxy.serviceAccountName" -}} 74 | {{- if .Values.serviceAccount.create }} 75 | {{- default (include "teler-proxy.fullname" .) .Values.serviceAccount.name }} 76 | {{- else }} 77 | {{- default "default" .Values.serviceAccount.name }} 78 | {{- end }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /chart/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: teler-proxy-config 5 | data: 6 | config.yaml: {{ default "" .Values.config }} 7 | -------------------------------------------------------------------------------- /chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "teler-proxy.fullname" . }} 5 | labels: 6 | {{- include "teler-proxy.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "teler-proxy.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "teler-proxy.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "teler-proxy.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | {{- with .Values.volumeMounts }} 37 | volumeMounts: 38 | {{- toYaml . | nindent 12 }} 39 | {{- end }} 40 | ports: 41 | - name: http 42 | containerPort: {{ .Values.service.port }} 43 | protocol: TCP 44 | livenessProbe: 45 | httpGet: 46 | path: / 47 | port: http 48 | readinessProbe: 49 | httpGet: 50 | path: / 51 | port: http 52 | args: 53 | - "--dest" 54 | - "{{ default ":0" .Values.args.destinationAddress }}" 55 | - "--conf" 56 | - "/data/config.yaml" 57 | resources: 58 | {{- toYaml .Values.resources | nindent 12 }} 59 | {{- with .Values.volumes }} 60 | volumes: 61 | {{- toYaml . | nindent 8 }} 62 | {{- end }} 63 | {{- with .Values.nodeSelector }} 64 | nodeSelector: 65 | {{- toYaml . | nindent 8 }} 66 | {{- end }} 67 | {{- with .Values.affinity }} 68 | affinity: 69 | {{- toYaml . | nindent 8 }} 70 | {{- end }} 71 | {{- with .Values.tolerations }} 72 | tolerations: 73 | {{- toYaml . | nindent 8 }} 74 | {{- end }} 75 | -------------------------------------------------------------------------------- /chart/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "teler-proxy.fullname" . }} 6 | labels: 7 | {{- include "teler-proxy.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "teler-proxy.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "teler-proxy.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "teler-proxy.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "teler-proxy.fullname" . }} 5 | labels: 6 | {{- include "teler-proxy.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "teler-proxy.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "teler-proxy.serviceAccountName" . }} 6 | labels: 7 | {{- include "teler-proxy.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "teler-proxy.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "teler-proxy.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "teler-proxy.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /chart/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | image: 4 | repository: ghcr.io/teler-sh/teler-proxy 5 | pullPolicy: IfNotPresent 6 | # Overrides the image tag whose default is the chart appVersion. 7 | tag: "" 8 | 9 | imagePullSecrets: [] 10 | 11 | serviceAccount: 12 | # Specifies whether a service account should be created 13 | create: true 14 | # Annotations to add to the service account 15 | annotations: {} 16 | # The name of the service account to use. 17 | # If not set and create is true, a name is generated using the fullname template 18 | name: "" 19 | 20 | volumeMounts: 21 | - name: config-volume 22 | mountPath: /data 23 | 24 | volumes: 25 | - name: config-volume 26 | configMap: 27 | name: teler-proxy-config 28 | 29 | podAnnotations: {} 30 | 31 | podSecurityContext: {} 32 | # fsGroup: 2000 33 | 34 | securityContext: {} 35 | # capabilities: 36 | # drop: 37 | # - ALL 38 | # readOnlyRootFilesystem: true 39 | # runAsNonRoot: true 40 | # runAsUser: 1000 41 | 42 | args: 43 | destinationAddress: "" 44 | 45 | service: 46 | type: ClusterIP 47 | port: 1337 48 | 49 | ingress: 50 | enabled: false 51 | className: "" 52 | annotations: {} 53 | # kubernetes.io/ingress.class: nginx 54 | # kubernetes.io/tls-acme: "true" 55 | hosts: 56 | - host: chart-example.local 57 | paths: 58 | - path: / 59 | pathType: ImplementationSpecific 60 | tls: [] 61 | # - secretName: chart-example-tls 62 | # hosts: 63 | # - chart-example.local 64 | 65 | resources: {} 66 | # limits: 67 | # cpu: 100m 68 | # memory: 128Mi 69 | # requests: 70 | # cpu: 100m 71 | # memory: 128Mi 72 | 73 | autoscaling: 74 | enabled: false 75 | minReplicas: 1 76 | maxReplicas: 100 77 | targetCPUUtilizationPercentage: 80 78 | # targetMemoryUtilizationPercentage: 80 79 | 80 | nodeSelector: {} 81 | 82 | tolerations: [] 83 | 84 | affinity: {} 85 | -------------------------------------------------------------------------------- /cmd/teler-proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/teler-sh/teler-proxy/internal/runner" 4 | 5 | func main() { 6 | opt := runner.ParseOptions() 7 | 8 | if err := opt.Validate(); err != nil { 9 | opt.Logger.Fatal("Cannot validate options", "err", err) 10 | } 11 | 12 | if err := runner.New(opt); err != nil { 13 | opt.Logger.Fatal("Something went wrong", "err", err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/config.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Config struct { 4 | Path, Format string 5 | } 6 | -------------------------------------------------------------------------------- /common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrCfgFileFormatInv = errors.New("invalid teler configuration file format") 7 | ErrCfgFileFormatUnd = errors.New("undefined teler configuration file format") 8 | ErrDestAddressEmpty = errors.New("empty destination address") 9 | ) 10 | -------------------------------------------------------------------------------- /common/options.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/charmbracelet/log" 4 | 5 | type Options struct { 6 | Port int 7 | Destination string 8 | *Config 9 | *TLS 10 | *log.Logger 11 | } 12 | -------------------------------------------------------------------------------- /common/tls.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type TLS struct { 4 | CertPath, KeyPath string 5 | } 6 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func PrintBanner() { 9 | fmt.Fprintf(os.Stderr, "%s\n\n", Banner) 10 | } 11 | 12 | func PrintUsage() { 13 | fmt.Fprint(os.Stderr, Usage) 14 | } 15 | 16 | func PrintVersion() { 17 | version := Version 18 | if version == "" { 19 | version = "unknown (go-get)" 20 | } 21 | 22 | fmt.Println(App, "version", version) 23 | } 24 | -------------------------------------------------------------------------------- /common/validator.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | func (opt *Options) Validate() error { 4 | validFormats := map[string]bool{ 5 | "yaml": true, 6 | "json": true, 7 | } 8 | 9 | if opt.Destination == "" { 10 | return ErrDestAddressEmpty 11 | } 12 | 13 | if opt.Config.Path != "" && !validFormats[opt.Config.Format] { 14 | return ErrCfgFileFormatInv 15 | } 16 | 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /common/validator_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "testing" 4 | 5 | func TestOptions_Validate_ValidConfig(t *testing.T) { 6 | opt := &Options{ 7 | Port: 8080, 8 | Destination: "example.com", 9 | Config: &Config{ 10 | Path: "config.yaml", 11 | Format: "yaml", 12 | }, 13 | TLS: nil, 14 | } 15 | 16 | err := opt.Validate() 17 | if err != nil { 18 | t.Errorf("Expected no error, but got: %v", err) 19 | } 20 | } 21 | 22 | func TestOptions_Validate_EmptyDestination(t *testing.T) { 23 | opt := &Options{ 24 | Port: 8080, 25 | Destination: "", 26 | Config: &Config{ 27 | Path: "config.yaml", 28 | Format: "yaml", 29 | }, 30 | TLS: nil, 31 | } 32 | 33 | err := opt.Validate() 34 | if err != ErrDestAddressEmpty { 35 | t.Errorf("Expected ErrDestAddressEmpty, but got: %v", err) 36 | } 37 | } 38 | 39 | func TestOptions_Validate_InvalidConfigFormat(t *testing.T) { 40 | opt := &Options{ 41 | Port: 8080, 42 | Destination: "example.com", 43 | Config: &Config{ 44 | Path: "config.json", 45 | Format: "xml", 46 | }, 47 | TLS: nil, 48 | } 49 | 50 | err := opt.Validate() 51 | if err != ErrCfgFileFormatInv { 52 | t.Errorf("Expected ErrCfgFileFormatInv, but got: %v", err) 53 | } 54 | } 55 | 56 | func TestOptions_Validate_MissingConfigPathAndFormat(t *testing.T) { 57 | opt := &Options{ 58 | Port: 8080, 59 | Destination: "example.com", 60 | Config: &Config{}, 61 | TLS: nil, 62 | } 63 | 64 | err := opt.Validate() 65 | if err != nil { 66 | t.Errorf("Expected no error, but got: %v", err) 67 | } 68 | } 69 | 70 | func TestOptions_Validate_NilOptions(t *testing.T) { 71 | opt := &Options{} 72 | 73 | err := opt.Validate() 74 | if err != ErrDestAddressEmpty { 75 | t.Errorf("Expected ErrDestAddressEmpty, but got: %v", err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/vars.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | var ( 4 | // App name 5 | App = "teler-proxy" 6 | // Version of teler-proxy itself 7 | Version = "" 8 | // Banner of teler-proxy 9 | Banner = ` 10 | __ __ 11 | / /____ / /__ ____ 12 | / __/ -_) / -_) __/ 13 | \__/\__/_/\__/_/ proxy ` + Version 14 | // Usage of teler-proxy 15 | Usage = ` 16 | Usage: 17 | teler-proxy -d : [OPTIONS...] 18 | 19 | Options: 20 | -p, --port Set the local port to listen on (default: 1337) 21 | -d, --dest : Set the destination address for forwarding requests 22 | -c, --conf Specify the path to the teler WAF configuration file 23 | -f, --format Specify the configuration file format (json/yaml) (default: yaml) 24 | --cert Specify the path to the SSL certificate file 25 | --key Specify the path to the SSL private key file 26 | -V, --version Display the current teler-proxy version 27 | -h, --help Display this helps text 28 | 29 | Examples: 30 | teler-proxy -d localhost:80 31 | teler-proxy -p 8000 -d localhost:80 32 | teler-proxy -d localhost:80 -c /path/to/teler-waf.conf.yaml 33 | teler-proxy -d localhost:80 -c /path/to/teler-waf.conf.json -f json 34 | 35 | ` 36 | ) 37 | -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | VERSION = $(shell git describe --always --tags) 2 | 3 | build: 4 | docker-compose build --build-arg VERSION=${VERSION} 5 | 6 | start: 7 | docker-compose up ${ARGS} 8 | 9 | start-daemon: ARGS = -d 10 | start-daemon: start 11 | 12 | stop: 13 | docker-compose down -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # teler Proxy (Demo) 2 | 3 | Welcome to the teler Proxy demo! This directory showcases how to utilize teler Proxy to secure the Damn Vulnerable Web Application (DVWA). 4 | 5 | ## Configuration 6 | 7 | The `config/` directory houses essential configurations for both DVWA and teler-proxy. Below are the changes made to each component: 8 | 9 | ### DVWA 10 | 11 | * Authentication: **Disabled** 12 | * Default Security Level: Set to **Low** 13 | 14 | These adjustments ensure that the DVWA is more vulnerable and allows for better testing and demonstration of the capabilities of teler-proxy. 15 | 16 | ### teler-waf 17 | 18 | * Whitelisted URIs: We have added a whitelist pattern that matches URIs with the regex `"^/(index|about)\.php"`. This action protects against the **DirectoryBruteforce** threat, ensuring these specific URIs are not protected. 19 | 20 | ## Run 21 | 22 | **Prerequisites:** 23 | 24 | Before starting, ensure you have the following prerequisites installed on your system: 25 | 26 | * [docker](https://docs.docker.com/engine/install/) 27 | * [docker-compose](https://docs.docker.com/compose/install/) 28 | 29 | To run the teler Proxy demo with the pre-configured settings, simply execute `make build` and `make start` command in this directory. 30 | 31 | The demo will now start running, utilizing the teler Proxy along with the specified configurations for the Damn Vulnerable Web Application (DVWA) and the teler-waf. Access the DVWA application using your preferred web browser in http://localhost:8080. 32 | 33 | Now you are all set! With the teler Proxy in place, you can explore the various functionalities and features it offers for protecting DVWA from potential threats. Test the security of DVWA by attempting various attacks, and observe how teler-waf handles and mitigates them. 34 | 35 | Happy testing and stay secure! 36 | 37 | --- 38 | 39 | > [!CAUTION] 40 | > Please note that this is a simplified demonstration of the teler Proxy's capabilities and is intended for testing purposes only. In a real-world scenario, you would integrate the teler Proxy with your web application to enhance security and protect it from potential attacks. For detailed instructions on integrating the teler Proxy with your application, refer to the full documentation available in the main repository. -------------------------------------------------------------------------------- /demo/config/dvwa/config.inc.php: -------------------------------------------------------------------------------- 1 | 57 | -------------------------------------------------------------------------------- /demo/config/teler-waf/config.yaml: -------------------------------------------------------------------------------- 1 | whitelists: 2 | - request.URI matches "^/(index|about)\\.php" && threat == DirectoryBruteforce 3 | # - request.URI matches "^/((log(out|in)|index|about)|setup)\\.php" && threat == DirectoryBruteforce 4 | -------------------------------------------------------------------------------- /demo/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | volumes: 2 | teler-proxy: 3 | 4 | networks: 5 | teler-proxy: 6 | 7 | services: 8 | teler-proxy: 9 | build: 10 | context: ../ 11 | args: 12 | VERSION: docker 13 | depends_on: 14 | - dvwa 15 | volumes: 16 | - ./config/teler-waf:/data 17 | networks: 18 | - teler-proxy 19 | ports: 20 | - 8080:1337 21 | command: 22 | - --dest 23 | - dvwa:80 24 | - --conf 25 | - /data/config.yaml 26 | restart: unless-stopped 27 | 28 | dvwa: 29 | build: https://github.com/digininja/DVWA.git 30 | environment: 31 | - DB_SERVER=db 32 | depends_on: 33 | - db 34 | volumes: 35 | - ./config/dvwa:/var/www/html/config 36 | networks: 37 | - teler-proxy 38 | ports: 39 | - 80 40 | restart: unless-stopped 41 | 42 | db: 43 | image: docker.io/library/mariadb:10 44 | environment: 45 | - MYSQL_ROOT_PASSWORD=dvwa 46 | - MYSQL_DATABASE=dvwa 47 | - MYSQL_USER=dvwa 48 | - MYSQL_PASSWORD=p@ssw0rd 49 | volumes: 50 | - teler-proxy:/var/lib/mysql 51 | networks: 52 | - teler-proxy 53 | restart: unless-stopped -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/teler-sh/teler-proxy 2 | 3 | go 1.21.0 4 | 5 | toolchain go1.22.0 6 | 7 | require ( 8 | github.com/charmbracelet/lipgloss v0.9.1 9 | github.com/charmbracelet/log v0.3.1 10 | github.com/fsnotify/fsnotify v1.7.0 11 | github.com/go-co-op/gocron v1.34.0 12 | github.com/mattn/go-colorable v0.1.13 13 | github.com/teler-sh/teler-waf v1.5.1 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go v0.112.2 // indirect 18 | cloud.google.com/go/auth v0.4.1 // indirect 19 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 20 | cloud.google.com/go/compute v1.25.1 // indirect 21 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 22 | cloud.google.com/go/iam v1.1.8 // indirect 23 | cloud.google.com/go/storage v1.39.1 // indirect 24 | filippo.io/edwards25519 v1.1.0 // indirect 25 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect 26 | github.com/Masterminds/goutils v1.1.1 // indirect 27 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 28 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 29 | github.com/Microsoft/go-winio v0.6.0 // indirect 30 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 31 | github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect 32 | github.com/aws/aws-sdk-go v1.44.298 // indirect 33 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 34 | github.com/aymerick/douceur v0.2.0 // indirect 35 | github.com/beorn7/perks v1.0.1 // indirect 36 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 37 | github.com/bitfield/script v0.22.0 // indirect 38 | github.com/caddyserver/caddy/v2 v2.8.4 // indirect 39 | github.com/caddyserver/certmagic v0.21.3 // indirect 40 | github.com/caddyserver/zerossl v0.1.3 // indirect 41 | github.com/cespare/xxhash v1.1.0 // indirect 42 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 43 | github.com/chzyer/readline v1.5.1 // indirect 44 | github.com/codingsince1985/checksum v1.3.0 // indirect 45 | github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect 46 | github.com/daniel-hutao/spinlock v0.1.0 // indirect 47 | github.com/dgraph-io/badger v1.6.2 // indirect 48 | github.com/dgraph-io/badger/v2 v2.2007.4 // indirect 49 | github.com/dgraph-io/ristretto v0.1.0 // indirect 50 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect 51 | github.com/dustin/go-humanize v1.0.1 // indirect 52 | github.com/dwisiswant0/clientip v0.3.0 // indirect 53 | github.com/expr-lang/expr v1.16.3 // indirect 54 | github.com/felixge/httpsnoop v1.0.4 // indirect 55 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 56 | github.com/go-jose/go-jose/v3 v3.0.3 // indirect 57 | github.com/go-kit/kit v0.13.0 // indirect 58 | github.com/go-kit/log v0.2.1 // indirect 59 | github.com/go-logfmt/logfmt v0.6.0 // indirect 60 | github.com/go-logr/logr v1.4.1 // indirect 61 | github.com/go-logr/stdr v1.2.2 // indirect 62 | github.com/go-playground/locales v0.14.1 // indirect 63 | github.com/go-playground/universal-translator v0.18.1 // indirect 64 | github.com/go-playground/validator/v10 v10.19.0 // indirect 65 | github.com/go-sql-driver/mysql v1.7.1 // indirect 66 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 67 | github.com/golang/glog v1.2.0 // indirect 68 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 69 | github.com/golang/protobuf v1.5.4 // indirect 70 | github.com/golang/snappy v0.0.4 // indirect 71 | github.com/google/cel-go v0.20.1 // indirect 72 | github.com/google/go-cmp v0.6.0 // indirect 73 | github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect 74 | github.com/google/s2a-go v0.1.7 // indirect 75 | github.com/google/uuid v1.6.0 // indirect 76 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 77 | github.com/googleapis/gax-go/v2 v2.12.4 // indirect 78 | github.com/gorilla/css v1.0.0 // indirect 79 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 80 | github.com/hashicorp/go-getter v1.7.5 // indirect 81 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 82 | github.com/hashicorp/go-version v1.6.0 // indirect 83 | github.com/huandu/xstrings v1.3.3 // indirect 84 | github.com/imdario/mergo v0.3.12 // indirect 85 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 86 | github.com/itchyny/gojq v0.12.12 // indirect 87 | github.com/itchyny/timefmt-go v0.1.5 // indirect 88 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 89 | github.com/jackc/pgconn v1.14.3 // indirect 90 | github.com/jackc/pgio v1.0.0 // indirect 91 | github.com/jackc/pgpassfile v1.0.0 // indirect 92 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 93 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 94 | github.com/jackc/pgtype v1.14.0 // indirect 95 | github.com/jackc/pgx/v4 v4.18.3 // indirect 96 | github.com/jmespath/go-jmespath v0.4.0 // indirect 97 | github.com/klauspost/compress v1.17.8 // indirect 98 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 99 | github.com/leodido/go-urn v1.4.0 // indirect 100 | github.com/libdns/libdns v0.2.2 // indirect 101 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 102 | github.com/manifoldco/promptui v0.9.0 // indirect 103 | github.com/mattn/go-isatty v0.0.20 // indirect 104 | github.com/mattn/go-runewidth v0.0.15 // indirect 105 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 106 | github.com/mholt/acmez/v2 v2.0.1 // indirect 107 | github.com/microcosm-cc/bluemonday v1.0.26 // indirect 108 | github.com/miekg/dns v1.1.59 // indirect 109 | github.com/mitchellh/copystructure v1.2.0 // indirect 110 | github.com/mitchellh/go-homedir v1.1.0 // indirect 111 | github.com/mitchellh/go-ps v1.0.0 // indirect 112 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 113 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 114 | github.com/muesli/reflow v0.3.0 // indirect 115 | github.com/muesli/termenv v0.15.2 // indirect 116 | github.com/onsi/ginkgo/v2 v2.13.2 // indirect 117 | github.com/otiai10/copy v1.14.0 // indirect 118 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 119 | github.com/pkg/errors v0.9.1 // indirect 120 | github.com/projectdiscovery/blackrock v0.0.1 // indirect 121 | github.com/projectdiscovery/mapcidr v1.1.16 // indirect 122 | github.com/projectdiscovery/utils v0.0.64 // indirect 123 | github.com/prometheus/client_golang v1.19.1 // indirect 124 | github.com/prometheus/client_model v0.5.0 // indirect 125 | github.com/prometheus/common v0.48.0 // indirect 126 | github.com/prometheus/procfs v0.12.0 // indirect 127 | github.com/quic-go/qpack v0.4.0 // indirect 128 | github.com/quic-go/quic-go v0.44.0 // indirect 129 | github.com/rivo/uniseg v0.4.4 // indirect 130 | github.com/robfig/cron/v3 v3.0.1 // indirect 131 | github.com/rs/xid v1.5.0 // indirect 132 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 133 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect 134 | github.com/samber/lo v1.39.0 // indirect 135 | github.com/scorpionknifes/go-pcre v0.0.0-20210805092536-77486363b797 // indirect 136 | github.com/shopspring/decimal v1.2.0 // indirect 137 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 138 | github.com/slackhq/nebula v1.6.1 // indirect 139 | github.com/smallstep/certificates v0.26.1 // indirect 140 | github.com/smallstep/nosql v0.6.1 // indirect 141 | github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect 142 | github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect 143 | github.com/smallstep/truststore v0.13.0 // indirect 144 | github.com/sourcegraph/conc v0.3.0 // indirect 145 | github.com/spf13/cast v1.4.1 // indirect 146 | github.com/spf13/cobra v1.8.0 // indirect 147 | github.com/spf13/pflag v1.0.5 // indirect 148 | github.com/stoewer/go-strcase v1.2.0 // indirect 149 | github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect 150 | github.com/teler-sh/dsl v1.0.2 // indirect 151 | github.com/twharmon/gouid v0.6.0 // indirect 152 | github.com/ulikunitz/xz v0.5.11 // indirect 153 | github.com/urfave/cli v1.22.14 // indirect 154 | github.com/valyala/bytebufferpool v1.0.0 // indirect 155 | github.com/valyala/fastjson v1.6.4 // indirect 156 | github.com/valyala/fasttemplate v1.2.2 // indirect 157 | github.com/zeebo/blake3 v0.2.3 // indirect 158 | go.etcd.io/bbolt v1.3.9 // indirect 159 | go.opencensus.io v0.24.0 // indirect 160 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 161 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 162 | go.opentelemetry.io/otel v1.24.0 // indirect 163 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 164 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 165 | go.step.sm/cli-utils v0.9.0 // indirect 166 | go.step.sm/crypto v0.45.0 // indirect 167 | go.step.sm/linkedca v0.20.1 // indirect 168 | go.uber.org/atomic v1.10.0 // indirect 169 | go.uber.org/automaxprocs v1.5.3 // indirect 170 | go.uber.org/mock v0.4.0 // indirect 171 | go.uber.org/multierr v1.11.0 // indirect 172 | go.uber.org/zap v1.27.0 // indirect 173 | go.uber.org/zap/exp v0.2.0 // indirect 174 | golang.org/x/crypto v0.25.0 // indirect 175 | golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect 176 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 177 | golang.org/x/mod v0.17.0 // indirect 178 | golang.org/x/net v0.27.0 // indirect 179 | golang.org/x/oauth2 v0.20.0 // indirect 180 | golang.org/x/sync v0.7.0 // indirect 181 | golang.org/x/sys v0.22.0 // indirect 182 | golang.org/x/term v0.22.0 // indirect 183 | golang.org/x/text v0.16.0 // indirect 184 | golang.org/x/time v0.5.0 // indirect 185 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 186 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 187 | google.golang.org/api v0.180.0 // indirect 188 | google.golang.org/appengine v1.6.8 // indirect 189 | google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect 190 | google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect 191 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect 192 | google.golang.org/grpc v1.63.2 // indirect 193 | google.golang.org/protobuf v1.34.1 // indirect 194 | gopkg.in/yaml.v3 v3.0.1 // indirect 195 | howett.net/plist v1.0.0 // indirect 196 | mvdan.cc/sh/v3 v3.6.0 // indirect 197 | tlog.app/go/loc v0.7.0 // indirect 198 | ) 199 | -------------------------------------------------------------------------------- /internal/cron/cron.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-co-op/gocron" 7 | ) 8 | 9 | type Cron struct { 10 | *gocron.Scheduler 11 | *gocron.Job 12 | } 13 | 14 | func New() (*Cron, error) { 15 | c := new(Cron) 16 | 17 | tz, err := time.LoadLocation("UTC") 18 | if err != nil { 19 | return c, err 20 | } 21 | 22 | c.Scheduler = gocron.NewScheduler(tz) 23 | c.Job, err = c.Scheduler.Every(1).Day().At("00:10").WaitForSchedule().Do(task) 24 | 25 | return c, err 26 | } 27 | -------------------------------------------------------------------------------- /internal/cron/task.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/teler-sh/teler-waf/threat" 7 | ) 8 | 9 | var task = func() error { 10 | updated, err := threat.IsUpdated() 11 | if err != nil { 12 | return err 13 | } 14 | 15 | if !updated { 16 | path, err := threat.Location() 17 | if err != nil { 18 | return err 19 | } 20 | 21 | if err = os.RemoveAll(path); err != nil { 22 | return err 23 | } 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/charmbracelet/lipgloss" 7 | "github.com/charmbracelet/log" 8 | "github.com/mattn/go-colorable" 9 | ) 10 | 11 | func New() *log.Logger { 12 | logger := log.NewWithOptions( 13 | colorable.NewColorableStderr(), 14 | log.Options{ 15 | ReportTimestamp: true, 16 | TimeFormat: time.DateTime, 17 | }, 18 | ) 19 | 20 | styles := log.DefaultStyles() 21 | styles.Values["id"] = lipgloss.NewStyle().Foreground(lipgloss.Color("86")) 22 | styles.Values["threat"] = lipgloss.NewStyle().Foreground(lipgloss.Color("192")) 23 | styles.Values["request"] = lipgloss.NewStyle().Foreground(lipgloss.Color("204")) 24 | styles.Values["options"] = styles.Values["threat"] 25 | 26 | logger.SetStyles(styles) 27 | 28 | log.TimestampKey = "ts" 29 | 30 | return logger 31 | } 32 | -------------------------------------------------------------------------------- /internal/runner/const.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | const ( 4 | errSomething = "Something went wrong" 5 | ) 6 | -------------------------------------------------------------------------------- /internal/runner/errors.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "errors" 4 | 5 | var errDestUnreachable = errors.New("destination address unreachable") 6 | -------------------------------------------------------------------------------- /internal/runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/teler-sh/teler-proxy/common" 8 | "github.com/teler-sh/teler-proxy/internal/logger" 9 | ) 10 | 11 | func ParseOptions() *common.Options { 12 | opt := &common.Options{} 13 | cfg := &common.Config{} 14 | tls := &common.TLS{} 15 | 16 | opt.Logger = logger.New() 17 | 18 | flag.IntVar(&opt.Port, "p", 1337, "") 19 | flag.IntVar(&opt.Port, "port", 1337, "") 20 | 21 | flag.StringVar(&opt.Destination, "d", "", "") 22 | flag.StringVar(&opt.Destination, "dest", "", "") 23 | 24 | flag.StringVar(&cfg.Path, "c", "", "") 25 | flag.StringVar(&cfg.Path, "conf", "", "") 26 | 27 | flag.StringVar(&cfg.Format, "f", "yaml", "") 28 | flag.StringVar(&cfg.Format, "format", "yaml", "") 29 | 30 | flag.StringVar(&tls.CertPath, "cert", "", "") 31 | flag.StringVar(&tls.KeyPath, "key", "", "") 32 | 33 | flag.BoolVar(&version, "V", false, "") 34 | flag.BoolVar(&version, "version", false, "") 35 | 36 | flag.Usage = func() { 37 | common.PrintBanner() 38 | common.PrintUsage() 39 | } 40 | flag.Parse() 41 | 42 | if version { 43 | common.PrintVersion() 44 | os.Exit(1) 45 | } 46 | 47 | if opt.Destination == "" { 48 | common.PrintBanner() 49 | opt.Logger.Fatal("Something went wrong", "err", "missing destination address") 50 | } 51 | 52 | common.PrintBanner() 53 | 54 | opt.Config = cfg 55 | opt.TLS = tls 56 | 57 | return opt 58 | } 59 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "sync" 8 | "syscall" 9 | "time" 10 | 11 | "net/http" 12 | "os/signal" 13 | 14 | "github.com/charmbracelet/log" 15 | "github.com/fsnotify/fsnotify" 16 | "github.com/teler-sh/teler-proxy/common" 17 | "github.com/teler-sh/teler-proxy/internal/cron" 18 | "github.com/teler-sh/teler-proxy/internal/writer" 19 | "github.com/teler-sh/teler-waf" 20 | "github.com/teler-sh/teler-waf/threat" 21 | ) 22 | 23 | type Runner struct { 24 | *common.Options 25 | *cron.Cron 26 | *http.Server 27 | 28 | shutdown shutdown 29 | telerOpts teler.Options 30 | watcher 31 | } 32 | 33 | func New(opt *common.Options) error { 34 | reachable := isReachable(opt.Destination, 5*time.Second) 35 | if !reachable { 36 | return errDestUnreachable 37 | } 38 | 39 | run := &Runner{Options: opt} 40 | 41 | if opt.Config.Path != "" { 42 | w, err := fsnotify.NewWatcher() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | if err := w.Add(opt.Config.Path); err != nil { 48 | return err 49 | } 50 | 51 | defer w.Close() 52 | run.watcher.config = w 53 | } else { 54 | run.watcher.config = new(fsnotify.Watcher) 55 | } 56 | 57 | dest := buildDest(opt.Destination) 58 | writer := writer.New() 59 | 60 | tun, err := run.createTunnel(dest, writer) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | logger := log.StandardLog(log.StandardLogOptions{ 66 | ForceLevel: log.ErrorLevel, 67 | }) 68 | 69 | server := &http.Server{ 70 | Addr: fmt.Sprintf(":%d", opt.Port), 71 | Handler: tun, 72 | ErrorLog: logger, 73 | } 74 | 75 | run.Server = server 76 | run.telerOpts = tun.Options 77 | run.shutdown.Once = new(sync.Once) 78 | 79 | if run.shouldCron() && run.Cron == nil { 80 | w, err := fsnotify.NewWatcher() 81 | if err != nil { 82 | return err 83 | } 84 | 85 | ds, err := threat.Location() 86 | if err != nil { 87 | return err 88 | } 89 | 90 | if err := w.Add(ds); err != nil { 91 | return err 92 | } 93 | 94 | defer w.Close() 95 | run.watcher.datasets = w 96 | 97 | if err := run.cron(); err != nil { 98 | opt.Logger.Fatal(errSomething, "err", err) 99 | } 100 | } else { 101 | run.watcher.datasets = new(fsnotify.Watcher) 102 | } 103 | 104 | go func() { 105 | if err := run.watch(); err != nil { 106 | opt.Logger.Fatal(errSomething, "err", err) 107 | } 108 | }() 109 | 110 | go func() { 111 | if err := run.start(); err != nil { 112 | opt.Logger.Fatal(errSomething, "err", err) 113 | } 114 | }() 115 | 116 | sig := make(chan os.Signal, 1) 117 | signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) 118 | 119 | return run.notify(sig) 120 | } 121 | 122 | func (r *Runner) start() error { 123 | var err error 124 | 125 | cert := r.Options.TLS.CertPath 126 | key := r.Options.TLS.KeyPath 127 | tls := (cert != "" && key != "") 128 | 129 | r.Options.Logger.Info( 130 | "Server started!", 131 | "port", r.Options.Port, 132 | "tls", tls, 133 | "pid", os.Getpid(), 134 | ) 135 | 136 | if tls { 137 | err = r.Server.ListenAndServeTLS(cert, key) 138 | } else { 139 | err = r.Server.ListenAndServe() 140 | } 141 | 142 | if err != nil && err != http.ErrServerClosed { 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | 149 | func (r *Runner) stop() error { 150 | r.shutdown.Do(func() { 151 | r.Options.Logger.Info("Gracefully shutdown...") 152 | 153 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 154 | defer cancel() 155 | 156 | r.shutdown.err = r.Server.Shutdown(ctx) 157 | }) 158 | 159 | return r.shutdown.err 160 | } 161 | 162 | func (r *Runner) restart() error { 163 | r.Options.Logger.Info("Restarting...") 164 | 165 | if err := r.stop(); err != nil { 166 | return err 167 | } 168 | 169 | return New(r.Options) 170 | } 171 | 172 | func (r *Runner) notify(sigCh chan os.Signal) error { 173 | sig := <-sigCh 174 | 175 | switch sig { 176 | case syscall.SIGINT, syscall.SIGTERM: 177 | return r.stop() 178 | case syscall.SIGHUP: 179 | return r.restart() 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func (r *Runner) watch() error { 186 | for { 187 | select { 188 | case event := <-r.watcher.config.Events: 189 | if event.Op.Has(fsnotify.Write) { 190 | r.Options.Logger.Warn("Configuration file has changed", "conf", r.Options.Config.Path) 191 | return r.restart() 192 | } 193 | case event := <-r.watcher.datasets.Events: 194 | if event.Op.Has(fsnotify.Write) || event.Op.Has(fsnotify.Remove) { 195 | r.Options.Logger.Warn("Threat datasets has updated", "event", event.Op) 196 | return r.restart() 197 | } 198 | case err := <-r.watcher.config.Errors: 199 | return err 200 | case err := <-r.watcher.datasets.Errors: 201 | return err 202 | } 203 | } 204 | } 205 | 206 | func (r *Runner) cron() error { 207 | c, err := cron.New() 208 | if err != nil { 209 | return err 210 | } 211 | 212 | r.Cron = c 213 | c.Scheduler.StartAsync() 214 | 215 | return nil 216 | } 217 | -------------------------------------------------------------------------------- /internal/runner/shutdown.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "sync" 4 | 5 | type shutdown struct { 6 | *sync.Once 7 | 8 | err error 9 | } 10 | -------------------------------------------------------------------------------- /internal/runner/utils.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "strings" 8 | "time" 9 | 10 | "net/url" 11 | 12 | "github.com/teler-sh/teler-proxy/pkg/tunnel" 13 | ) 14 | 15 | func parseURL(dest string) (*url.URL, error) { 16 | if !strings.HasPrefix(dest, "http://") && !strings.HasPrefix(dest, "https://") { 17 | dest = "http://" + dest 18 | } 19 | 20 | destURL, err := url.Parse(dest) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return destURL, nil 26 | } 27 | 28 | func cleanURL(inputURL string) string { 29 | parsedURL, err := parseURL(inputURL) 30 | if err != nil { 31 | // Return the input URL as-is if parsing fails 32 | return inputURL 33 | } 34 | 35 | return parsedURL.Host 36 | } 37 | 38 | func buildDest(dest string) string { 39 | parsedURL, err := parseURL(dest) 40 | if err != nil { 41 | // Return the input URL as-is if parsing fails 42 | return dest 43 | } 44 | 45 | return fmt.Sprint(parsedURL.Scheme, "://", parsedURL.Host) 46 | } 47 | 48 | func isReachable(inputURL string, timeout time.Duration) bool { 49 | host := cleanURL(inputURL) 50 | 51 | conn, err := net.DialTimeout("tcp", host, timeout) 52 | if err != nil { 53 | return false 54 | } 55 | defer conn.Close() 56 | 57 | return true 58 | } 59 | 60 | func (r *Runner) shouldCron() bool { 61 | if r.Options.Config.Path == "" { 62 | return true 63 | } 64 | 65 | opt := r.telerOpts 66 | 67 | if !opt.InMemory && !opt.NoUpdateCheck { 68 | return true 69 | } 70 | 71 | return false 72 | } 73 | 74 | func (r *Runner) createTunnel(dest string, writer io.Writer) (*tunnel.Tunnel, error) { 75 | opt := r.Options 76 | 77 | return tunnel.NewTunnel(opt.Port, dest, opt.Config.Path, opt.Config.Format, writer) 78 | } 79 | -------------------------------------------------------------------------------- /internal/runner/vars.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | var version bool 4 | -------------------------------------------------------------------------------- /internal/runner/watcher.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "github.com/fsnotify/fsnotify" 4 | 5 | type watcher struct { 6 | config, datasets *fsnotify.Watcher 7 | } 8 | -------------------------------------------------------------------------------- /internal/writer/writer.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/charmbracelet/log" 7 | "github.com/teler-sh/teler-proxy/internal/logger" 8 | ) 9 | 10 | type Writer interface { 11 | Write(p []byte) (n int, err error) 12 | } 13 | 14 | type logWriter struct { 15 | *log.Logger 16 | } 17 | 18 | type data = map[string]any 19 | 20 | func New() *logWriter { 21 | w := new(logWriter) 22 | w.Logger = logger.New().WithPrefix("teler-waf") 23 | 24 | return w 25 | } 26 | 27 | func (w *logWriter) Write(p []byte) (n int, err error) { 28 | var d data 29 | 30 | n = len(p) 31 | 32 | err = json.Unmarshal(p, &d) 33 | if err != nil { 34 | return 0, err 35 | } 36 | 37 | err = w.write(d) 38 | 39 | return 40 | } 41 | 42 | func (w *logWriter) write(d data) error { 43 | switch level := d["level"].(string); level { 44 | case "debug": 45 | w.writeDebug(d) 46 | // case "info": 47 | // w.writeInfo(d) 48 | case "warn": 49 | w.writeWarn(d) 50 | case "error": 51 | w.writeError(d) 52 | case "fatal": 53 | w.writeFatal(d) 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (w *logWriter) writeDebug(d data) { 60 | w.Debug(d["msg"]) 61 | } 62 | 63 | // func (w *logWriter) writeInfo(d data) { 64 | // // if opt, ok := d["options"].(data); ok { 65 | // // o, err := json.Marshal(opt) 66 | // // if err != nil { 67 | // // return 68 | // // } 69 | 70 | // // w.Info(d["msg"], "options", string(o)) 71 | // // } else { 72 | // w.Info(d["msg"]) 73 | // // } 74 | // } 75 | 76 | func (w *logWriter) writeWarn(d data) { 77 | if req, ok := d["request"].(data); ok { 78 | r, err := json.Marshal(req) 79 | if err != nil { 80 | return 81 | } 82 | 83 | w.Warn(d["msg"], 84 | "id", d["id"], 85 | "threat", d["category"], 86 | "request", string(r), 87 | ) 88 | } else { 89 | w.Warn(d["msg"]) 90 | } 91 | } 92 | 93 | func (w *logWriter) writeError(d data) { 94 | w.Error(d["msg"], 95 | "source", d["caller"], 96 | ) 97 | } 98 | 99 | func (w *logWriter) writeFatal(d data) { 100 | w.writeError(d) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/tunnel/tunnel.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package tunnel provides functionality for creating HTTP tunnels 3 | and reverse proxies with [teler] WAF capabilities. 4 | 5 | The main components of this package include the [Tunnel] type, 6 | which represents a tunneling configuration, and related functions 7 | and methods for tunnel setup and HTTP request handling. 8 | 9 | To create a new tunnel, use the [NewTunnel] function, specifying 10 | the local port, destination address, and optional configuration 11 | parameters. The [Tunnel] type also provides the [Tunnel.ServeHTTP] method 12 | for handling incoming HTTP requests and proxying them to the 13 | destination, analyzing the incoming HTTP request from threats using 14 | the [teler.Teler] middleware. 15 | 16 | Additional configuration options can be loaded from YAML or JSON 17 | files, allowing for customizing the [teler] WAF behavior. 18 | */ 19 | package tunnel 20 | 21 | import ( 22 | "io" 23 | "strings" 24 | 25 | "net/http" 26 | "net/http/httputil" 27 | "net/url" 28 | 29 | "github.com/teler-sh/teler-proxy/common" 30 | "github.com/teler-sh/teler-waf" 31 | "github.com/teler-sh/teler-waf/option" 32 | ) 33 | 34 | type Tunnel struct { 35 | *httputil.ReverseProxy 36 | *teler.Teler 37 | 38 | Options teler.Options 39 | } 40 | 41 | // NewTunnel creates a new [Tunnel] instance for proxying HTTP traffic. 42 | // 43 | // Parameters: 44 | // - `port`: The local port on which the tunnel will listen for incoming requests. 45 | // - `dest`: The destination address to which incoming requests will be forwarded. 46 | // - `cfgPath`: The path to a configuration file for additional tunnel options. 47 | // - `optFormat`: The format of the configuration file ("yaml" or "json"). 48 | // - `writer`: An optional [io.Writer] where tunnel log output will be written. 49 | // Pass nil to use default [teler] logging only. 50 | // 51 | // Please be aware that when you pass a custom `writer`, the [teler.Options.NoStderr] 52 | // option value will be forcibly set to `true`, regardless of the `no_stderr` value 53 | // that might be loaded from additional configuration options. 54 | func NewTunnel(port int, dest, cfgPath, optFormat string, writer io.Writer) (*Tunnel, error) { 55 | if dest == "" { 56 | return nil, common.ErrDestAddressEmpty 57 | } 58 | 59 | // NOTE(dwisiswant0): should we accept the input `dest` parameter 60 | // as pointer of url.URL directly instead of string? 61 | destURL, err := url.Parse(dest) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | var opt teler.Options 67 | 68 | tun := &Tunnel{} 69 | tun.ReverseProxy = httputil.NewSingleHostReverseProxy(destURL) 70 | 71 | if cfgPath != "" { 72 | switch strings.ToLower(optFormat) { 73 | case "yaml": 74 | opt, err = option.LoadFromYAMLFile(cfgPath) 75 | case "json": 76 | opt, err = option.LoadFromJSONFile(cfgPath) 77 | case "": 78 | return nil, common.ErrCfgFileFormatUnd 79 | default: 80 | return nil, common.ErrCfgFileFormatInv 81 | } 82 | 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | tun.Options = opt 88 | } 89 | 90 | if writer != nil { 91 | opt.LogWriter = writer 92 | opt.NoStderr = true 93 | } 94 | 95 | tun.Teler = teler.New(opt) 96 | 97 | return tun, nil 98 | } 99 | 100 | // ServeHTTP is a method of the [Tunnel] type, which allows 101 | // the [Tunnel] to implement the [http.Handler] interface. 102 | // 103 | // This method forwards the incoming HTTP request to the 104 | // [httputil.ReverseProxy.ServeHTTP] method, while also 105 | // analyzing the incoming HTTP request from threats using 106 | // the [teler.Teler] middleware. 107 | func (t *Tunnel) ServeHTTP(w http.ResponseWriter, r *http.Request) { 108 | t.Teler.HandlerFuncWithNext(w, r, t.ReverseProxy.ServeHTTP) 109 | } 110 | -------------------------------------------------------------------------------- /pkg/tunnel/tunnel_test.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "net/http" 8 | "net/http/httptest" 9 | "net/http/httputil" 10 | "net/url" 11 | "path/filepath" 12 | 13 | "github.com/teler-sh/teler-proxy/common" 14 | "github.com/teler-sh/teler-waf" 15 | ) 16 | 17 | var ( 18 | cwd, workspaceDir string 19 | 20 | dest = "http://example.com" 21 | 22 | handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | w.WriteHeader(http.StatusOK) 24 | }) 25 | ) 26 | 27 | func init() { 28 | var err error 29 | 30 | cwd, err = os.Getwd() 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | workspaceDir = filepath.Join(cwd, "..", "..") 36 | } 37 | 38 | func TestNewTunnel(t *testing.T) { 39 | // Test case 1: valid destination and no configuration file 40 | t.Run("Valid Destination & No Config File", func(t *testing.T) { 41 | tun, err := NewTunnel(8080, dest, "", "", nil) 42 | if err != nil { 43 | t.Fatalf("Expected no error, but got: %v", err) 44 | } 45 | 46 | if tun == nil { 47 | t.Fatal("Expected Tunnel instance, but got nil") 48 | } 49 | }) 50 | 51 | // Test case 2: invalid destination (empty) 52 | t.Run("Invalid Destination", func(t *testing.T) { 53 | _, err := NewTunnel(8080, "", "", "", nil) 54 | if err != common.ErrDestAddressEmpty { 55 | t.Fatalf("Expected %v, but got: %v", common.ErrDestAddressEmpty, err) 56 | } 57 | }) 58 | 59 | // Test case 3: with config file but empty format 60 | t.Run("With Config File but Empty Format", func(t *testing.T) { 61 | _, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.yaml"), "", nil) 62 | if err != common.ErrCfgFileFormatUnd { 63 | t.Fatalf("Expected %v, but got: %v", common.ErrCfgFileFormatUnd, err) 64 | } 65 | }) 66 | 67 | // Test case 4: with config file and YAML format 68 | t.Run("With Config File and YAML Format", func(t *testing.T) { 69 | tun, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.yaml"), "yaml", nil) 70 | if err != nil { 71 | t.Fatalf("Expected no error, but got: %v", err) 72 | } 73 | if tun == nil { 74 | t.Fatal("Expected Tunnel instance, but got nil") 75 | } 76 | }) 77 | 78 | // Test case 5: with config file and JSON format 79 | t.Run("With Config File and JSON Format", func(t *testing.T) { 80 | tun, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.json"), "json", nil) 81 | if err != nil { 82 | t.Fatalf("Expected no error, but got: %v", err) 83 | } 84 | if tun == nil { 85 | t.Fatal("Expected Tunnel instance, but got nil") 86 | } 87 | }) 88 | 89 | // Test case 6: with config file and xml format 90 | t.Run("With Config File and XML Format", func(t *testing.T) { 91 | _, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.json"), "xml", nil) 92 | if err != common.ErrCfgFileFormatInv { 93 | t.Fatalf("Expected %v, but got: %v", common.ErrCfgFileFormatInv, err) 94 | } 95 | }) 96 | 97 | // Test case 7: invalid destination 98 | t.Run("Invalid Destination", func(t *testing.T) { 99 | tun, _ := NewTunnel(8080, "http://this is not a valid URL", "", "", nil) 100 | if tun != nil { 101 | t.Fatalf("Expected %v, but got: %v", nil, tun) 102 | } 103 | }) 104 | 105 | // Test case 8: with invalid config file 106 | t.Run("Invalid YAML Config File", func(t *testing.T) { 107 | _, err := NewTunnel(8080, dest, "nonexistent", "yaml", nil) 108 | if err == nil { 109 | t.Fatal("Expected error, but got nil") 110 | } 111 | }) 112 | 113 | // Test case 9: with invalid config file 114 | t.Run("Invalid JSON Config File", func(t *testing.T) { 115 | _, err := NewTunnel(8080, dest, "nonexistent", "json", nil) 116 | if err == nil { 117 | t.Fatal("Expected no error, but got nil") 118 | } 119 | }) 120 | 121 | // Test case 10: with io.Writer 122 | t.Run("With Writer", func(t *testing.T) { 123 | _, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.yaml"), "yaml", os.Stderr) 124 | if err != nil { 125 | t.Fatalf("Expected no error, but got: %v", err) 126 | } 127 | }) 128 | } 129 | 130 | func TestServeHTTP(t *testing.T) { 131 | ts := httptest.NewServer(handler) 132 | defer ts.Close() 133 | 134 | parsedURL, _ := url.Parse(ts.URL) 135 | mockReverseProxy := httputil.NewSingleHostReverseProxy(parsedURL) 136 | 137 | tunnel := &Tunnel{ 138 | Teler: teler.New(), 139 | ReverseProxy: mockReverseProxy, 140 | } 141 | 142 | // Create a mock HTTP request and response recorder 143 | req, err := http.NewRequest("GET", "/", nil) 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | recorder := httptest.NewRecorder() 148 | 149 | tunnel.ServeHTTP(recorder, req) 150 | 151 | if recorder.Code != http.StatusOK { 152 | t.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) 153 | } 154 | } 155 | 156 | func BenchmarkNewTunnel(b *testing.B) { 157 | b.Run("YAML", func(b *testing.B) { 158 | b.ResetTimer() 159 | b.ReportAllocs() 160 | 161 | for i := 0; i < b.N; i++ { 162 | _, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.yaml"), "yaml", nil) 163 | if err != nil { 164 | b.Fatalf("Expected no error, but got: %v", err) 165 | } 166 | } 167 | }) 168 | 169 | b.Run("JSON", func(b *testing.B) { 170 | b.ResetTimer() 171 | b.ReportAllocs() 172 | 173 | for i := 0; i < b.N; i++ { 174 | _, err := NewTunnel(8080, dest, filepath.Join(workspaceDir, "teler-waf.conf.example.json"), "json", nil) 175 | if err != nil { 176 | b.Fatalf("Expected no error, but got: %v", err) 177 | } 178 | } 179 | }) 180 | } 181 | 182 | func BenchmarkServeHTTP(b *testing.B) { 183 | ts := httptest.NewServer(handler) 184 | defer ts.Close() 185 | 186 | parsedURL, _ := url.Parse(ts.URL) 187 | mockReverseProxy := httputil.NewSingleHostReverseProxy(parsedURL) 188 | 189 | tunnel := &Tunnel{ 190 | Teler: teler.New(), 191 | ReverseProxy: mockReverseProxy, 192 | } 193 | 194 | // Create a mock HTTP request and response recorder 195 | req, err := http.NewRequest("GET", "/", nil) 196 | if err != nil { 197 | b.Fatal(err) 198 | } 199 | recorder := httptest.NewRecorder() 200 | 201 | b.ResetTimer() 202 | b.ReportAllocs() 203 | 204 | for i := 0; i < b.N; i++ { 205 | tunnel.ServeHTTP(recorder, req) 206 | 207 | if recorder.Code != http.StatusOK { 208 | b.Errorf("Expected status code %d, but got %d", http.StatusOK, recorder.Code) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /teler-waf.conf.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludes": [], 3 | "whitelists": [], 4 | "customs": [], 5 | "customs_from_file": "", 6 | "response": { 7 | "status": 0, 8 | "html": "", 9 | "html_file": "" 10 | }, 11 | "log_file": "", 12 | "no_stderr": false, 13 | "no_update_check": false, 14 | "development": false, 15 | "in_memory": false, 16 | "falcosidekick_url": "", 17 | "verbose": false 18 | } -------------------------------------------------------------------------------- /teler-waf.conf.example.yaml: -------------------------------------------------------------------------------- 1 | excludes: [] 2 | whitelists: [] 3 | customs: [] 4 | customs_from_file: "" 5 | response: 6 | status: 0 7 | html: "" 8 | html_file: "" 9 | log_file: "" 10 | no_stderr: false 11 | no_update_check: false 12 | development: false 13 | in_memory: false 14 | falcosidekick_url: "" 15 | verbose: false --------------------------------------------------------------------------------