├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation.md │ ├── feature_request.md │ └── q-a.md ├── pull_request_template.md ├── stale.yml ├── vert.sh └── workflows │ ├── docker.yml │ ├── package.yaml │ ├── release.yml │ └── testing.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Makefile ├── README-zh.md ├── README.md ├── bootstrap ├── agent.go ├── config │ ├── config.go │ ├── config_test.go │ └── consts.go ├── signals_darwin.go ├── signals_linux.go └── signals_windows.go ├── build.sh ├── build_docker.sh ├── build_vm.sh ├── cmd ├── root.go ├── start.go └── version.go ├── deploy ├── k8s │ ├── configmap-polaris-sidecar.yaml │ ├── deployment-dnsagent.yaml │ └── job-test.yaml └── vm │ ├── install-linux.sh │ └── uninstall-linux.sh ├── docs └── image │ ├── deploy_job.png │ └── polaris_architecture.png ├── envoy ├── metrics │ ├── config.go │ ├── server.go │ └── stat_object.go └── rls │ ├── config.go │ └── server.go ├── go.mod ├── go.sum ├── import-format.sh ├── main.go ├── pkg ├── client │ ├── client.go │ └── config.go ├── http │ └── debug.go └── log │ ├── config.go │ ├── default.go │ ├── options.go │ └── scope.go ├── polaris-sidecar.yaml ├── resolver ├── dns_test.go ├── dnsagent │ ├── config.go │ ├── plugin.go │ └── plugin_test.go ├── meshproxy │ ├── base_test.go │ ├── config.go │ ├── dns.go │ ├── plugin.go │ ├── plugin_test.go │ └── registry_proxy.go ├── resolver.go ├── server.go └── util.go ├── security └── mtls │ ├── agent │ ├── agent.go │ ├── option.go │ └── serviceaccount.go │ ├── certificate │ ├── bundle.go │ ├── caclient │ │ ├── client.go │ │ ├── client_test.go │ │ └── sat.go │ └── manager │ │ ├── interface.go │ │ └── manager.go │ ├── rotator │ └── rotate.go │ └── sds │ ├── secret_amd64.go │ ├── secret_arm64.go │ └── server.go ├── tool ├── check.sh ├── include ├── p.bat ├── p.sh ├── reload.sh ├── start.bat ├── start.sh ├── stop.bat └── stop.sh └── version └── version.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment** 20 | - Version: [e.g. v1.0.0] 21 | - OS: [e.g. CentOS8] 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Improvements or additions to documentation 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is the feature you want to add?** 11 | 12 | **Why do you want to add this feature?** 13 | 14 | **How to implement this feature?** 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/q-a.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Q&A 3 | about: Please tell me your questions 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Please provide issue(s) of this PR:** 2 | Fixes # 3 | 4 | **To help us figure out who should review this PR, please put an X in all the areas that this PR affects.** 5 | 6 | - [ ] Configuration 7 | - [ ] Docs 8 | - [ ] Installation 9 | - [ ] Performance and Scalability 10 | - [ ] Naming 11 | - [ ] HealthCheck 12 | - [ ] Test and Release 13 | 14 | **Please check any characteristics that apply to this pull request.** 15 | 16 | - [ ] Does not have any user-facing changes. This may include API changes, behavior changes, performance improvements, etc. 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # General configuration 4 | # Label to use when marking as stale 5 | staleLabel: stale 6 | 7 | # Pull request specific configuration 8 | pulls: 9 | # Number of days of inactivity before an Issue or Pull Request becomes stale 10 | daysUntilStale: 14 11 | # Number of days of inactivity before a stale Issue or Pull Request is closed. 12 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 13 | daysUntilClose: 30 14 | # Comment to post when marking as stale. Set to `false` to disable 15 | markComment: > 16 | This pull request has been automatically marked as stale because it has not had 17 | activity in the last 2 weeks. It will be closed in 30 days if no further activity occurs. Please 18 | feel free to give a status update now, ping for review, or re-open when it's ready. 19 | Thank you for your contributions! 20 | # Comment to post when closing a stale Issue or Pull Request. 21 | closeComment: > 22 | This pull request has been automatically closed because it has not had 23 | activity in the last 30 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. 24 | Thank you for your contributions! 25 | # Limit the number of actions per hour, from 1-30. Default is 30 26 | limitPerRun: 1 27 | 28 | exemptLabels: 29 | - help wanted 30 | - kind/customer issue 31 | - kind/test failure 32 | - Epic 33 | - no stalebot 34 | 35 | # Issue specific configuration 36 | issues: 37 | # TODO: Consider increasing the limitPerRun once we are satisfied with the bot's performance 38 | limitPerRun: 1 39 | daysUntilStale: 90 40 | daysUntilClose: 30 41 | markComment: > 42 | This issue has been automatically marked as stale because it has not had activity in the 43 | last 90 days. It will be closed in the next 30 days unless it is tagged "help wanted" or other activity 44 | occurs. Thank you for your contributions. 45 | closeComment: > 46 | This issue has been automatically closed because it has not had activity in the 47 | last month and a half. If this issue is still valid, please ping a maintainer and ask them to label it as "help wanted". 48 | Thank you for your contributions. 49 | -------------------------------------------------------------------------------- /.github/vert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex # Exit on error; debugging enabled. 4 | set -o pipefail # Fail a pipe if any sub-command fails. 5 | 6 | # not makes sure the command passed to it does not exit with a return code of 0. 7 | not() { 8 | # This is required instead of the earlier (! $COMMAND) because subshells and 9 | # pipefail don't work the same on Darwin as in Linux. 10 | ! "$@" 11 | } 12 | 13 | die() { 14 | echo "$@" >&2 15 | exit 1 16 | } 17 | 18 | fail_on_output() { 19 | tee /dev/stderr | not read 20 | } 21 | 22 | # Check to make sure it's safe to modify the user's git repo. 23 | git status --porcelain | fail_on_output 24 | 25 | # Undo any edits made by this script. 26 | cleanup() { 27 | git reset --hard HEAD 28 | } 29 | trap cleanup EXIT 30 | 31 | PATH="${HOME}/go/bin:${GOROOT}/bin:${PATH}" 32 | go version 33 | 34 | if [[ "$1" = "-install" ]]; then 35 | # Install the pinned versions as defined in module tools. 36 | pushd ./test/tools 37 | go install \ 38 | golang.org/x/lint/golint \ 39 | golang.org/x/tools/cmd/goimports \ 40 | honnef.co/go/tools/cmd/staticcheck \ 41 | github.com/client9/misspell/cmd/misspell 42 | popd 43 | exit 0 44 | elif [[ "$#" -ne 0 ]]; then 45 | die "Unknown argument(s): $*" 46 | fi 47 | 48 | # - Ensure all source files contain a copyright message. 49 | not git grep -L "\(Copyright (C) [0-9]\{4,\} THL A29 Limited, a Tencent company. All rights reserved.\)\|DO NOT EDIT" -- '*.go' 50 | 51 | ## - Make sure all tests use leakcheck via Teardown. 52 | #not grep -r 'func Test[^(]' test/*.go 53 | 54 | # - Do not import x/net/context. 55 | git grep -l 'x/net/context' -- "*.go" | not grep -v ".pb.go" 56 | 57 | misspell -error . 58 | 59 | # - gofmt, goimports, golint (with exceptions for generated code), go vet, 60 | # go mod tidy. 61 | # Perform these checks on each module inside polaris-go. 62 | #for MOD_FILE in $(find . -name 'go.mod'); do 63 | # MOD_DIR=$(dirname ${MOD_FILE}) 64 | # pushd ${MOD_DIR} 65 | # go vet -all ./... | fail_on_output 66 | # #gofmt -s -d -l . 2>&1 | fail_on_output 67 | # #goimports -l . 2>&1 | not grep -vE "\.pb\.go" 68 | # #golint ./... 2>&1 | not grep -vE "\.pb\.go" 69 | # 70 | # go mod tidy 71 | # git status --porcelain 2>&1 | fail_on_output || 72 | # ( 73 | # git status 74 | # git --no-pager diff 75 | # exit 1 76 | # ) 77 | # popd 78 | #done 79 | 80 | # - Collection of static analysis checks 81 | # 82 | # TODO(dfawley): don't use deprecated functions in examples or first-party 83 | # plugins. 84 | #SC_OUT="$(mktemp)" 85 | #staticcheck -go 1.9 -checks 'inherit,-ST1015' ./... > "${SC_OUT}" || true 86 | ## Error if anything other than deprecation warnings are printed. 87 | #not grep -v "is deprecated:.*SA1019" "${SC_OUT}" 88 | 89 | ## - special golint on package comments. 90 | #lint_package_comment_per_package() { 91 | # # Number of files in this go package. 92 | # fileCount=$(go list -f '{{len .GoFiles}}' $1) 93 | # if [ ${fileCount} -eq 0 ]; then 94 | # return 0 95 | # fi 96 | # # Number of package errors generated by golint. 97 | # lintPackageCommentErrorsCount=$(golint --min_confidence 0 $1 | grep -c "should have a package comment") 98 | # # golint complains about every file that's missing the package comment. If the 99 | # # number of files for this package is greater than the number of errors, there's 100 | # # at least one file with package comment, good. Otherwise, fail. 101 | # if [ ${fileCount} -le ${lintPackageCommentErrorsCount} ]; then 102 | # echo "Package $1 (with ${fileCount} files) is missing package comment" 103 | # return 1 104 | # fi 105 | #} 106 | #lint_package_comment() { 107 | # set +ex 108 | # 109 | # count=0 110 | # for i in $(go list ./...); do 111 | # lint_package_comment_per_package "$i" 112 | # ((count += $?)) 113 | # done 114 | # 115 | # set -ex 116 | # return $count 117 | #} 118 | #lint_package_comment 119 | 120 | echo SUCCESS 121 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: DockerImage 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: Release Polaris Sidecar Docker Image 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [linux] 14 | goarch: [amd64] 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: "1.20" 23 | 24 | - name: Get version 25 | id: get_version 26 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v1 30 | 31 | - name: Log in to Docker Hub 32 | uses: docker/login-action@v1 33 | with: 34 | username: ${{ secrets.POLARIS_DOCKER_NAME }} 35 | password: ${{ secrets.POLARIS_DOCKER_PASSWORD }} 36 | 37 | - name: Build 38 | id: build 39 | env: 40 | GOOS: ${{ matrix.goos }} 41 | GOARCH: ${{ matrix.goarch }} 42 | DOCKER_REPOSITORY: polarismesh/polaris-sidecar 43 | DOCKER_TAG: ${{ steps.get_version.outputs.VERSION }} 44 | run: | 45 | ls -lstrh 46 | bash build_docker.sh ${DOCKER_TAG} 47 | -------------------------------------------------------------------------------- /.github/workflows/package.yaml: -------------------------------------------------------------------------------- 1 | name: Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: Release polaris 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [linux] 14 | goarch: [amd64, arm64] 15 | exclude: 16 | - goos: windows 17 | goarch: arm64 18 | - goos: linux 19 | goarch: arm64 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: "1.20" 28 | 29 | - name: Get version 30 | id: get_version 31 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 32 | 33 | - name: Build 34 | id: build 35 | env: 36 | GOOS: ${{ matrix.goos }} 37 | GOARCH: ${{ matrix.goarch }} 38 | VERSION: ${{ steps.get_version.outputs.VERSION }} 39 | run: | 40 | echo "version is $VERSION" 41 | bash build_vm.sh $VERSION 42 | PACKAGE_NAME=$(ls | grep polaris-sidecar-local*.zip | sed -n '1p') 43 | echo ::set-output name=name::${PACKAGE_NAME} 44 | 45 | - name: Upload asset 46 | uses: actions/upload-release-asset@v1 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | with: 50 | upload_url: ${{ github.event.release.upload_url }} 51 | asset_path: ./${{ steps.build.outputs.name }} 52 | asset_name: ${{ steps.build.outputs.name }} 53 | asset_content_type: application/gzip 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: Release polaris 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | goos: [linux, windows, darwin] 14 | goarch: [amd64] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Get version 21 | id: get_version 22 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: "1.20" 28 | 29 | - name: Build 30 | id: build 31 | env: 32 | GOOS: ${{ matrix.goos }} 33 | GOARCH: ${{ matrix.goarch }} 34 | VERSION: ${{ steps.get_version.outputs.VERSION }} 35 | run: | 36 | echo "version is $VERSION" 37 | bash build.sh $VERSION 38 | PACKAGE_NAME=$(ls | grep polaris-sidecar-release*.zip | sed -n '1p') 39 | echo ::set-output name=name::${PACKAGE_NAME} 40 | 41 | - name: Upload asset 42 | uses: actions/upload-release-asset@v1 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | with: 46 | upload_url: ${{ github.event.release.upload_url }} 47 | asset_path: ./${{ steps.build.outputs.name }} 48 | asset_name: ${{ steps.build.outputs.name }} 49 | asset_content_type: application/gzip 50 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | # Always force the use of Go modules 13 | env: 14 | GO111MODULE: on 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | # Setup the environment. 22 | - name: Setup Go 23 | uses: actions/setup-go@v2 24 | with: 25 | go-version: "1.20" 26 | # Checkout latest code 27 | - name: Checkout repo 28 | uses: actions/checkout@v2 29 | 30 | # Compile 31 | - name: Build server 32 | run: bash build.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | polaris/ 2 | 3 | .idea/ 4 | 5 | vendor/* 6 | 7 | nohup.out 8 | 9 | polaris-sidecar 10 | 11 | test/cover.out 12 | test/index.html 13 | deploy/test.sh 14 | *.log* 15 | 16 | logs/ 17 | 18 | .vscode 19 | 20 | style_tool/ 21 | goimports-reviser 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献 2 | 3 | 我们十分期待您的贡献,无论是提问题、修复问题还是提交一个新的特性。 4 | 5 | 如果您想提交一个新特性,建议先在 issue 中讨论,避免重复开发。 6 | 7 | ## 工作流 8 | 9 | 1. `fork` polaris-sidecar 仓库到你的个人账户空间 10 | 11 | 2. `clone`个人账户下的`polaris-sidecar`仓库到本地。如下修改`` 为你的`TGit`账户 12 | 13 | ```bash 14 | git clone git@git.code.oa.com:/polaris-sidecar.git # 使用ssh(推荐) 15 | git clone http://git.code.oa.com//polaris-sidecar.git # 或者使用http 16 | ``` 17 | 18 | 3. 添加一个名字为`upstream`的`remote`来指向polaris-sidecar官方仓库,并更新 19 | 20 | ```bash 21 | git remote add upstream git@git.code.oa.com:polaris/polaris-sidecar.git # 使用ssh(推荐) 22 | git remote add upstream http://github.com/polarismesh/polaris-sidecar.git # 或者使用http 23 | git fetch upstream 24 | ``` 25 | 26 | 4. 基于官方仓库的master分支创建一个新分支 27 | 28 | ```bash 29 | git checkout -b feature-xxx upstream/master 30 | ``` 31 | 32 | 5. 在新分支完成代码修改并提交 33 | 34 | ```bash 35 | git commit -a 36 | ``` 37 | 38 | 6. 可以经常`rebase`你的代码修改 39 | 40 | ```bash 41 | git pull --rebase 42 | ``` 43 | 44 | 7. 当修改完成后,使用`rebase`对`commit`进行整理 45 | 46 | ```bash 47 | git rebase -i upstream/master 48 | ``` 49 | 50 | 这个命令会打开你的编辑器,并允许你合并和重排你的提交。 51 | 52 | 8. 提交合并请求 53 | 54 | 使用如下命令将修改推送到你fork的项目中 55 | 56 | ```bash 57 | git push origin feature-xxx 58 | ``` 59 | 60 | 打开浏览器,到polaris官方仓库页面创建新的合并请求。源分支选择项目:`/polaris-sidecar`和分支:`feature-xxx`。 61 | 目标分支选择项目`polaris/polaris-sidecar`和分支:`master`。单击比较两分支,进入提交合并请求页面。 62 | 63 | - 为你的合并请求填写一个有意义的名字 64 | - 在描述中解释你的修改和你的合并请求所解决的问题。 65 | 66 | 提交合并请求时标题使用以下分类进行开头: 67 | 68 | - `fix:`:表示修复Bug 69 | - `feat:`:表示添加特性 70 | - `docs`:修改文档时使用 71 | - `test`:更新测试用例 72 | - `refactor`:代码重构 73 | 74 | 9. 处理蓝盾/CodeCC检查问题 75 | 76 | 提交代码后,会触发蓝盾配置的MR检查。如果CI流水线未成功,则点击详情查看流水线失败是否和代码有关。 77 | 如和代码提交有关,则代码提交者需要根据CI提示的问题进行相应修改。 78 | 79 | 注:如果无法查看,则按照页面提示申请权限 80 | 81 | 10. 处理CodeCC检查/Code Review提出的问题 82 | 83 | 重复步骤5到步骤7来解决Code Review提出的问题。然后再次提交你的修改: 84 | 85 | ```bash 86 | git push origin [--force] feature-xxx 87 | ``` 88 | 89 | `--force`选项只有在你进行`rebase`导致提交历史被更改时使用。 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN sed -i 's!http://dl-cdn.alpinelinux.org/!https://mirrors.tencent.com/!g' /etc/apk/repositories 4 | 5 | RUN set -eux && \ 6 | apk add bind-tools && \ 7 | apk add busybox-extras && \ 8 | apk add findutils && \ 9 | apk add tcpdump && \ 10 | apk add tzdata && \ 11 | apk add curl && \ 12 | apk add bash && \ 13 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 14 | echo "Asia/Shanghai" > /etc/timezone && \ 15 | date 16 | 17 | WORKDIR /data 18 | 19 | RUN chmod -R a+rw /data 20 | 21 | COPY polaris-sidecar /data/polaris-sidecar 22 | 23 | RUN chmod +x /data/polaris-sidecar 24 | 25 | ENTRYPOINT ["/data/polaris-sidecar", "start"] 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Tencent is pleased to support the open source community by making Polaris available. 2 | # 3 | # Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 4 | # 5 | # Licensed under the BSD 3-Clause License (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://opensource.org/licenses/BSD-3-Clause 10 | # 11 | # Unless required by applicable law or agreed to in writing, software distributed 12 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | # specific language governing permissions and limitations under the License. 15 | 16 | # VERSION defines the project version for the build. 17 | # Update this value when you upgrade the version file of your project. 18 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 19 | # - use the VERSION as arg of the build target (e.g make build VERSION=0.0.2) 20 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 21 | VERSION ?= $(shell cat version 2>/dev/null) 22 | 23 | # IMAGE_TAG defines the image tag for the docker build. 24 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 25 | # - use the IMAGE_TAG as arg of the build-docker target (e.g make build-docker IMAGE_TAG=v0.0.2) 26 | # - use environment variables to overwrite this value (e.g export IMAGE_TAG=v0.0.2) 27 | IMAGE_TAG ?= $(VERSION) 28 | 29 | ARCH ?= "amd64" 30 | 31 | all: build 32 | 33 | ##@ General 34 | 35 | # The help target prints out all targets with their descriptions organized 36 | # beneath their categories. The categories are represented by '##@' and the 37 | # target descriptions by '##'. The awk commands is responsible for reading the 38 | # entire set of makefiles included in this invocation, looking for lines of the 39 | # file as xyz: ## something, and then pretty-format the target and help. Then, 40 | # if there's a line with ##@ something, that gets pretty-printed as a category. 41 | # More info on the usage of ANSI control characters for terminal formatting: 42 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 43 | # More info on the awk command: 44 | # http://linuxcommand.org/lc3_adv_awk.php 45 | 46 | help: ## Display this help. 47 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 48 | 49 | 50 | ##@ Build 51 | 52 | .PHONY: build 53 | build: ## Build binary and tarball. 54 | bash ./build.sh $(VERSION) $(ARCH) 55 | 56 | .PHONY: build-docker 57 | build-docker: ## Build polaris-server docker images. 58 | bash ./build_docker.sh $(IMAGE_TAG) 59 | 60 | .PHONY: clean 61 | clean: ## Clean polaris-server make data. 62 | @rm -rf polaris-sidecar-release_* 63 | @rm -rf polaris-sideacr-arm64 64 | @rm -rf polaris-sidecar-amd64 65 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Polaris Sidecar 2 | 3 | [English](./README.md) | 中文 4 | 5 | ## 介绍 6 | 7 | polaris-sidecar 作为 polaris 的本地边车代理,提供两个可选功能模式: 8 | 9 | - 本地 DNS:使用 DNS 解析的方式访问北极星上的服务 10 | - 服务网格:通过劫持流量的方式实现服务发现和治理,开发侵入性低 11 | 12 | 用户可以选择其中一种模式进行接入polaris-sidecar,本文档介绍如何在虚拟机或者容器环境中安装和使用 polaris-sidecar。 13 | 14 | ## 本地DNS模式 15 | 16 | ### 架构图 17 | 18 | ![架构图](./docs/image/polaris_architecture.png) 19 | 20 | ### 提供功能 21 | 22 | - 基于DNS的服务发现能力:直接通过域名```.```进行拉取服务实例地址列表。 23 | - 故障节点剔除能力:自动剔除不健康和隔离实例,保障业务可靠性。 24 | - 标签路由能力:通过配置标签,通过标签筛选并返回满足标签规则的服务实例地址列表。 25 | 26 | ### 当前支持的DNS请求类型 27 | 28 | - A/AAAA 29 | - SRV 30 | 31 | ### 安装说明 32 | 33 | #### 前提条件 34 | 35 | - 在安装和使用 polaris-sidecar 之前,需要先安装北极星服务端,[北极星服务端安装文档](https://polarismesh.cn/docs/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%AE%89%E8%A3%85/)。 36 | 37 | #### 虚拟机环境下安装 38 | 39 | 1. 虚拟机安装过程需要使用root用户或者具有超级管理员权限的用户来执行,并且确保53(udp/tcp)端口没有被占用。 40 | 2. 需要从[Releases](https://github.com/polarismesh/polaris-sidecar/releases)下载最新版本的安装包。 41 | 3. 上传安装包到虚拟机环境中,并进行解压,进入解压后的目录。 42 | 43 | ``` 44 | unzip polaris-sidecar-release_$version.$os.$arch.zip 45 | ``` 46 | 47 | 4. 修改polaris-sidecar.yaml,写入北极星服务端的地址,端口号使用8091(GRPC端口)。 48 | 49 | ``` 50 | polaris 51 | addresses: 52 | - 127.0.0.1:8091 53 | ``` 54 | 55 | 5. 进入解压后的目录,执行tool/start.sh进行启动,然后执行tool/p.sh查看进程是否启动成功。 56 | 57 | ``` 58 | # bash tool/start.sh 59 | # bash ./tool/p.sh 60 | root 15318 1 0 Jan22 ? 00:07:50 ./polaris-sidecar start 61 | ``` 62 | 63 | 6. 修改/etc/resolv.conf,在文件中添加```nameserver 127.0.0.1```,并且添加到所有的nameserver记录前面,如下: 64 | 65 | ``` 66 | ; generated by /usr/sbin/dhclient-script 67 | nameserver 127.0.0.1 68 | nameserver x.x.x.x 69 | ``` 70 | 71 | 7. 验证安装,使用格式为```.```的域名进行访问,可以获得服务的IP地址。 72 | 73 | ``` 74 | # dig polaris.checker.polaris 75 | 76 | ; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.2 <<>> polaris.checker.polaris 77 | ;; global options: +cmd 78 | ;; Got answer: 79 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10696 80 | ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 81 | ;; WARNING: recursion requested but not available 82 | 83 | ;; OPT PSEUDOSECTION: 84 | ; EDNS: version: 0, flags:; udp: 4096 85 | ;; QUESTION SECTION: 86 | ;polaris.checker.polaris. IN A 87 | 88 | ;; ANSWER SECTION: 89 | polaris.checker.polaris. 10 IN AAAA ::ffff:1.1.1.1 90 | 91 | ;; Query time: 0 msec 92 | ;; SERVER: 127.0.0.1#53(127.0.0.1) 93 | ;; WHEN: Wed Jan 26 00:21:34 CST 2022 94 | ;; MSG SIZE rcvd: 127 95 | ``` 96 | 97 | 备注:如果需要使用域名的方式进行服务发现,则必须保证命名空间和服务名在北极星上都是以全小写字母进行注册,否则会寻址失败。 98 | 99 | #### 容器环境下安装 100 | 101 | polaris-sidecar镜像是归档到dockerhub中,需要确保部署的环境网络可以访问dockerhub公有镜像仓库。 102 | 103 | 1. 参考 [polaris-controller 文档](https://github.com/polarismesh/polaris-controller/blob/main/README.md) 104 | 2. 验证安装,通过执行一个小型的job来进行dns解析的验证: 105 | 106 | ```shell 107 | $ kubectl apply --filename deploy/job/job.yaml 108 | ``` 109 | 3. 部署job之后的POD详细如下 110 | ![deploy_job](./docs/image/deploy_job.png) 111 | 4. job运行完后,可以通过查询POD日志确认运行情况。默认情况下,会输出成功的服务DNS查询结果,如果出现错误,则DNS配置可能出现问题。 112 | 113 | ``` 114 | # dig polaris.checker.polaris 115 | 116 | ; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.2 <<>> polaris.checker.polaris 117 | ;; global options: +cmd 118 | ;; Got answer: 119 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10696 120 | ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 121 | ;; WARNING: recursion requested but not available 122 | 123 | ;; OPT PSEUDOSECTION: 124 | ; EDNS: version: 0, flags:; udp: 4096 125 | ;; QUESTION SECTION: 126 | ;polaris.checker.polaris. IN A 127 | 128 | ;; ANSWER SECTION: 129 | polaris.checker.polaris. 10 IN AAAA ::ffff:1.1.1.1 130 | 131 | ;; Query time: 0 msec 132 | ;; SERVER: 127.0.0.1#53(127.0.0.1) 133 | ;; WHEN: Wed Jan 26 00:21:34 CST 2022 134 | ;; MSG SIZE rcvd: 127 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polaris Sidecar 2 | 3 | English | [中文](./README-zh.md) 4 | 5 | ## Introduce 6 | 7 | Polaris-Sidecar as Polaris's local bike agent, providing two optional functional modes: 8 | 9 | - Local DNS: Use DNS parsing to access the services on the polaris 10 | - Service Grid: Realize service discovery and governance by hijacking traffic, and develop invasiveness 11 | 12 | Users can select one of the modes to access Polaris-Sidecar. This document describes how to install and use Polaris-Sidecar in a virtual machine or container environment. 13 | 14 | ## Local DNS mode 15 | 16 | ### Architecture 17 | 18 | ![Architecture](./docs/image/polaris_architecture.png) 19 | 20 | ### Function 21 | 22 | - DNS-based service discovery capabilities: Directly pass the domain name ```.``` to pull the service instance address list. 23 | - Fault nodes eliminate the ability: automatically eliminate unhealthy and isolation instances to ensure business reliability. 24 | - Label Routing Ability: By configuring tags, filtering and returning a list of service instance addresses that meet label rules. 25 | 26 | ### Supported DNS question type 27 | 28 | - A/AAAA 29 | - SRV 30 | 31 | ### Installation Notes 32 | 33 | #### Precondition 34 | 35 | - Before installing and using Polaris-Sidecar, you need to install the Northern Polarite server, please refer to [Polaris Star server installation document](https://polarismesh.cn/docs/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%AE%89%E8%A3%85/). 36 | 37 | #### Install in a virtual machine environment 38 | 39 | 1. The virtual machine installation process requires a root user or a user with super administrator privileges to be executed, and ensure that the 53 (UDP / TCP) port is not occupied. 40 | 2. Need to download the latest version of the installation package from [Release](https://github.com/polarismesh/polaris-sidecar/releases). 41 | 3. Upload installation packages into the virtual machine environment, and decompress, enter the decompressed directory. 42 | 43 | ``` 44 | unzip polaris-sidecar-release_$version.$os.$arch.zip 45 | ``` 46 | 47 | 4. Modify polaris-sidecar.yaml, write the address of the polaris server, port number uses 8091 (GRPC port). 48 | 49 | ``` 50 | polaris 51 | addresses: 52 | - 127.0.0.1:8091 53 | ``` 54 | 55 | 5. Enter the decompressed directory, perform tool/start.sh to start, then perform tool/p.sh to view the process whether it is successful. 56 | 57 | ``` 58 | # bash tool/start.sh 59 | # bash ./tool/p.sh 60 | root 15318 1 0 Jan22 ? 00:07:50 ./polaris-sidecar start 61 | ``` 62 | 63 | 6. Modify /etc/resolv.conf, add ```Nameserver 127.0.0.0.1```, and add it to all Nameserver records, as follows: 64 | 65 | ``` 66 | ; generated by /usr/sbin/dhclient-script 67 | nameserver 127.0.0.1 68 | nameserver x.x.x.x 69 | ``` 70 | 71 | 7. Verify the installation, you can get the IP address of the service. 72 | 73 | ``` 74 | # dig polaris.checker.polaris 75 | 76 | ; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.2 <<>> polaris.checker.polaris 77 | ;; global options: +cmd 78 | ;; Got answer: 79 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10696 80 | ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 81 | ;; WARNING: recursion requested but not available 82 | 83 | ;; OPT PSEUDOSECTION: 84 | ; EDNS: version: 0, flags:; udp: 4096 85 | ;; QUESTION SECTION: 86 | ;polaris.checker.polaris. IN A 87 | 88 | ;; ANSWER SECTION: 89 | polaris.checker.polaris. 10 IN AAAA ::ffff:1.1.1.1 90 | 91 | ;; Query time: 0 msec 92 | ;; SERVER: 127.0.0.1#53(127.0.0.1) 93 | ;; WHEN: Wed Jan 26 00:21:34 CST 2022 94 | ;; MSG SIZE rcvd: 127 95 | ``` 96 | 97 | Remarks: If you need to use the domain name to discover, you must ensure that the namespace and service name are registered with a full lowercase on the Northern Star, otherwise it will be addressed. 98 | 99 | #### Container environment installation 100 | 101 | Polaris-Sidecar mirroring is archived into dockerhub, requiring a deployed environmental network to access DockerHub public mirror warehouse. 102 | 103 | 1. Refer to [polaris-controller Document](https://github.com/polarismesh/polaris-controller/blob/main/README.md) 104 | 2. Verify the installation, by performing a small JOB to perform DNS resolution verification: 105 | 106 | ```shell 107 | $ kubectl apply --filename deploy/job/job.yaml 108 | ``` 109 | 3. The POD after deploying Job is detailed below 110 | ![deploy_job](./docs/image/deploy_job.png) 111 | 4. After the Job is running, you can confirm the operation by querying the POD log.By default, successful service DNS query results will be output. If an error occurs, the DNS configuration may have problems. 112 | 113 | ``` 114 | # dig polaris.checker.polaris 115 | 116 | ; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.2 <<>> polaris.checker.polaris 117 | ;; global options: +cmd 118 | ;; Got answer: 119 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10696 120 | ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 121 | ;; WARNING: recursion requested but not available 122 | 123 | ;; OPT PSEUDOSECTION: 124 | ; EDNS: version: 0, flags:; udp: 4096 125 | ;; QUESTION SECTION: 126 | ;polaris.checker.polaris. IN A 127 | 128 | ;; ANSWER SECTION: 129 | polaris.checker.polaris. 10 IN AAAA ::ffff:1.1.1.1 130 | 131 | ;; Query time: 0 msec 132 | ;; SERVER: 127.0.0.1#53(127.0.0.1) 133 | ;; WHEN: Wed Jan 26 00:21:34 CST 2022 134 | ;; MSG SIZE rcvd: 127 135 | ``` 136 | -------------------------------------------------------------------------------- /bootstrap/agent.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making CL5 available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package bootstrap 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "net" 24 | "net/http" 25 | "net/http/pprof" 26 | "os" 27 | "os/signal" 28 | "strings" 29 | 30 | "github.com/polarismesh/polaris-sidecar/bootstrap/config" 31 | "github.com/polarismesh/polaris-sidecar/envoy/metrics" 32 | "github.com/polarismesh/polaris-sidecar/envoy/rls" 33 | "github.com/polarismesh/polaris-sidecar/pkg/client" 34 | debughttp "github.com/polarismesh/polaris-sidecar/pkg/http" 35 | "github.com/polarismesh/polaris-sidecar/pkg/log" 36 | "github.com/polarismesh/polaris-sidecar/resolver" 37 | mtlsAgent "github.com/polarismesh/polaris-sidecar/security/mtls/agent" 38 | ) 39 | 40 | // Agent provide the listener to dns server 41 | type Agent struct { 42 | config *config.SidecarConfig 43 | dnsSvrs *resolver.Server 44 | mtlsAgent *mtlsAgent.Agent 45 | metricServer *metrics.Server 46 | rlsSvr *rls.RateLimitServer 47 | 48 | debugSvr *http.Server 49 | } 50 | 51 | // Start the main agent routines 52 | func Start(configFile string, bootConfig *config.BootConfig) { 53 | agent, err := newAgent(configFile, bootConfig) 54 | if err != nil { 55 | fmt.Printf("[ERROR] loadConfig fail: %v\n", err) 56 | os.Exit(-1) 57 | } 58 | ctx, cancel := context.WithCancel(context.Background()) 59 | errCh := make(chan error) 60 | go func() { 61 | var err error 62 | err = agent.Start(ctx) 63 | if nil != err { 64 | errCh <- err 65 | } 66 | }() 67 | runMainLoop(cancel, errCh) 68 | } 69 | 70 | // RunMainLoop sidecar server main loop 71 | func runMainLoop(cancel context.CancelFunc, errCh chan error) { 72 | ch := make(chan os.Signal, 1) 73 | signal.Notify(ch, signals...) 74 | for { 75 | select { 76 | case s := <-ch: 77 | log.Infof("catch signal(%+v), stop sidecar server", s) 78 | cancel() 79 | return 80 | case err := <-errCh: 81 | log.Errorf("catch sidecar server err: %s", err.Error()) 82 | return 83 | } 84 | } 85 | } 86 | 87 | func newAgent(configFile string, bootConfig *config.BootConfig) (*Agent, error) { 88 | var err error 89 | polarisAgent := &Agent{} 90 | polarisAgent.config, err = config.ParseYamlConfig(configFile, bootConfig) 91 | if nil != err { 92 | log.Errorf("[agent] fail to parse sidecar config, err: %v", err) 93 | return nil, err 94 | } 95 | // 初始化日志打印 96 | if err := log.Configure(polarisAgent.config.Logger); err != nil { 97 | return nil, err 98 | } 99 | log.Infof("[agent] success to init log config") 100 | log.Infof("[agent] finished to parse sidecar config, current active config is \n%s", *polarisAgent.config) 101 | 102 | client.InitSDKContext(&client.Config{ 103 | Addresses: polarisAgent.config.PolarisConfig.Adddresses, 104 | Metrics: &client.Metrics{ 105 | Port: polarisAgent.config.Metrics.Port, 106 | Type: polarisAgent.config.Metrics.Type, 107 | IP: polarisAgent.config.Bind, 108 | Interval: polarisAgent.config.Metrics.Interval, 109 | Address: polarisAgent.config.Metrics.Address, 110 | }, 111 | LocationConfigImpl: polarisAgent.config.PolarisConfig.Location, 112 | }) 113 | 114 | mux := http.NewServeMux() 115 | polarisAgent.debugSvr = &http.Server{ 116 | Handler: mux, 117 | } 118 | 119 | if err := polarisAgent.buildDns(configFile); err != nil { 120 | return nil, err 121 | } 122 | if err := polarisAgent.buildSecurity(configFile); err != nil { 123 | return nil, err 124 | } 125 | if err := polarisAgent.buildEnvoyMetrics(configFile); err != nil { 126 | return nil, err 127 | } 128 | if err := polarisAgent.buildEnvoyRls(configFile); err != nil { 129 | return nil, err 130 | } 131 | return polarisAgent, nil 132 | } 133 | 134 | func (p *Agent) buildSecurity(configFile string) error { 135 | if p.config.MTLS != nil && p.config.MTLS.Enable { 136 | log.Info("create mtls agent") 137 | agent, err := mtlsAgent.New(mtlsAgent.Option{ 138 | CAServer: p.config.MTLS.CAServer, 139 | }) 140 | if err != nil { 141 | return err 142 | } 143 | p.mtlsAgent = agent 144 | } 145 | return nil 146 | } 147 | 148 | func (p *Agent) buildEnvoyMetrics(configFile string) error { 149 | if p.config.Metrics.Enable { 150 | log.Infof("create metric server") 151 | p.metricServer = metrics.NewServer(p.config.Namespace, p.config.Metrics.Port) 152 | } 153 | return nil 154 | } 155 | 156 | func (p *Agent) buildEnvoyRls(configFile string) error { 157 | if p.config.RateLimit == nil || !p.config.RateLimit.Enable { 158 | return nil 159 | } 160 | log.Infof("create ratelimit server") 161 | conf := &rls.Config{ 162 | Network: strings.ToLower(p.config.RateLimit.Network), 163 | TLSInfo: p.config.RateLimit.TLSInfo, 164 | } 165 | if conf.Network == "tcp" { 166 | conf.Address = fmt.Sprintf("%s:%d", p.config.Bind, p.config.RateLimit.BindPort) 167 | } 168 | rlsSvr, err := rls.New(p.config.Namespace, conf) 169 | if err != nil { 170 | return err 171 | } 172 | p.rlsSvr = rlsSvr 173 | return nil 174 | } 175 | 176 | func (p *Agent) buildDns(configFile string) error { 177 | svr, err := resolver.NewServers(&resolver.ResolverConfig{ 178 | BindLocalhost: p.config.BindLocalhost(), 179 | BindIP: p.config.Bind, 180 | BindPort: uint32(p.config.Port), 181 | Recurse: p.config.Recurse, 182 | Resolvers: p.config.Resolvers, 183 | }) 184 | if err != nil { 185 | return err 186 | } 187 | p.dnsSvrs = svr 188 | p.registerDebugeHandler(svr.Debugger()) 189 | return nil 190 | } 191 | 192 | func (p *Agent) registerDebugeHandler(handlers []debughttp.DebugHandler) { 193 | mux := p.debugSvr.Handler.(*http.ServeMux) 194 | for i := range handlers { 195 | handler := handlers[i] 196 | if len(handler.Path) == 0 { 197 | continue 198 | } 199 | mux.HandleFunc(handler.Path, handler.Handler) 200 | } 201 | } 202 | 203 | // Start the agent 204 | func (p *Agent) Start(ctx context.Context) error { 205 | var recvErrCounts int 206 | errChan := make(chan error) 207 | 208 | if p.config.Debugger.Enable { 209 | go func() { 210 | ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", p.config.Bind, p.config.Debugger.Port)) 211 | if err != nil { 212 | errChan <- err 213 | return 214 | } 215 | 216 | mux := p.debugSvr.Handler.(*http.ServeMux) 217 | mux.HandleFunc("/sidecar/health/readiness", func(resp http.ResponseWriter, _ *http.Request) { 218 | resp.WriteHeader(http.StatusOK) 219 | }) 220 | mux.HandleFunc("/sidecar/health/liveness", func(resp http.ResponseWriter, _ *http.Request) { 221 | resp.WriteHeader(http.StatusOK) 222 | }) 223 | mux.HandleFunc("/debug/pprof/", pprof.Index) 224 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 225 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 226 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 227 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 228 | 229 | if err := p.debugSvr.Serve(ln); err != nil { 230 | errChan <- err 231 | } 232 | }() 233 | } 234 | if p.dnsSvrs != nil { 235 | go func() { 236 | log.Info("start dns server") 237 | errCh := p.dnsSvrs.Run(ctx) 238 | for { 239 | select { 240 | case <-ctx.Done(): 241 | return 242 | case err := <-errCh: 243 | errChan <- err 244 | } 245 | } 246 | }() 247 | } 248 | if p.mtlsAgent != nil { 249 | go func() { 250 | log.Info("start mtls agent") 251 | errChan <- p.mtlsAgent.Run(ctx) 252 | }() 253 | } 254 | if p.metricServer != nil { 255 | go func() { 256 | log.Info("start metric server") 257 | err := p.metricServer.Start(ctx) 258 | if nil != err { 259 | errChan <- err 260 | } 261 | }() 262 | } 263 | if p.rlsSvr != nil { 264 | go func() { 265 | log.Info("start ratelimit server") 266 | err := p.rlsSvr.Run(ctx) 267 | if nil != err { 268 | errChan <- err 269 | } 270 | }() 271 | } 272 | 273 | for { 274 | select { 275 | case err := <-errChan: 276 | if nil != err { 277 | return err 278 | } 279 | recvErrCounts++ 280 | if recvErrCounts == 2 { 281 | return nil 282 | } 283 | case <-ctx.Done(): 284 | return nil 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /bootstrap/config/config_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package config 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "testing" 24 | ) 25 | 26 | func TestParseLabels(t *testing.T) { 27 | labels := "xx:yy,xx1:yy1,xx2:yy2" 28 | var values map[string]string 29 | values = parseLabels(labels) 30 | fmt.Printf("values are %v\n", values) 31 | } 32 | 33 | const testCfg = "" + 34 | "bind: ${SIDECAR_BIND}\n" + 35 | "port: ${SIDECAR_PORT}\n" + 36 | "resolvers:\n " + 37 | "- name: dnsagent\n " + 38 | "dns_ttl: 10\n " + 39 | "enable: true\n " + 40 | "suffix: \".\"\n " + 41 | "- name: meshproxy\n " + 42 | " dns_ttl: 120\n " + 43 | "enable: false\n " + 44 | "option:\n " + 45 | "registry_host: 127.0.0.1\n " + 46 | "registry_port: ${LISTEN_PORT}\n " + 47 | "reload_interval_sec: 2\n " + 48 | "dns_answer_ip: ${aswip}" 49 | 50 | const testAnswerIP = "127.0.0.8" 51 | 52 | func TestParseYamlConfig(t *testing.T) { 53 | err := os.Setenv("aswip", testAnswerIP) 54 | if nil != err { 55 | t.Fatal(err) 56 | } 57 | cfg := &SidecarConfig{} 58 | err = parseYamlContent([]byte(testCfg), cfg) 59 | if nil != err { 60 | t.Fatal(err) 61 | } 62 | result := cfg.Resolvers[1].Option["dns_answer_ip"] 63 | if result != testAnswerIP { 64 | t.Fatal("answer ip should be " + testAnswerIP) 65 | } 66 | 67 | } 68 | 69 | const value = "this is a ${animal}, today is ${today}" 70 | 71 | func TestReplaceEnv(t *testing.T) { 72 | err := os.Setenv("animal", "cat") 73 | if nil != err { 74 | t.Fatal(err) 75 | } 76 | nextValue := os.ExpandEnv(value) 77 | fmt.Println("nextValue is " + nextValue) 78 | 79 | } 80 | -------------------------------------------------------------------------------- /bootstrap/config/consts.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package config 19 | 20 | import ( 21 | "os" 22 | "strconv" 23 | "strings" 24 | 25 | "github.com/polarismesh/polaris-sidecar/pkg/log" 26 | ) 27 | 28 | const ( 29 | EnvPolarisAddress = "POLARIS_ADDRESS" 30 | EnvSidecarBind = "SIDECAR_BIND" 31 | EnvSidecarPort = "SIDECAR_PORT" 32 | EnvSidecarNamespace = "SIDECAR_NAMESPACE" 33 | EnvSidecarRecurseEnable = "SIDECAR_RECURSE_ENABLE" 34 | EnvSidecarRecurseTimeout = "SIDECAR_RECURSE_TIMEOUT" 35 | EnvSidecarLogRotateOutputPath = "SIDECAR_LOG_ROTATE_OUTPUT_PATH" 36 | EnvSidecarLogErrorRotateOutputPath = "SIDECAR_LOG_ERROR_ROTATE_OUTPUT_PATH" 37 | EnvSidecarLogRotationMaxSize = "SIDECAR_LOG_ROTATION_MAX_SIZE" 38 | EnvSidecarLogRotationMaxBackups = "SIDECAR_LOG_ROTATION_MAX_BACKUPS" 39 | EnvSidecarLogRotationMaxAge = "SIDECAR_LOG_ROTATION_MAX_AGE" 40 | EnvSidecarLogLevel = "SIDECAR_LOG_LEVEL" 41 | EnvSidecarDnsTtl = "SIDECAR_DNS_TTL" 42 | EnvSidecarDnsEnable = "SIDECAR_DNS_ENABLE" 43 | EnvSidecarDnsSuffix = "SIDECAR_DNS_SUFFIX" 44 | EnvSidecarDnsRouteLabels = "SIDECAR_DNS_ROUTE_LABELS" 45 | EnvSidecarMeshTtl = "SIDECAR_MESH_TTL" 46 | EnvSidecarMeshEnable = "SIDECAR_MESH_ENABLE" 47 | EnvSidecarMeshReloadInterval = "SIDECAR_MESH_RELOAD_INTERVAL" 48 | EnvSidecarMeshAnswerIp = "SIDECAR_MESH_ANSWER_IP" 49 | EnvSidecarMtlsEnable = "SIDECAR_MTLS_ENABLE" 50 | EnvSidecarMtlsCAServer = "SIDECAR_MTLS_CA_SERVER" 51 | EnvSidecarRLSEnable = "SIDECAR_RLS_ENABLE" 52 | EnvSidecarMetricEnable = "SIDECAR_METRIC_ENABLE" 53 | EnvSidecarMetricListenPort = "SIDECAR_METRIC_LISTEN_PORT" 54 | ) 55 | 56 | func getEnvStringValue(envName string, defaultValue string) string { 57 | envValue := os.Getenv(envName) 58 | if len(envValue) > 0 { 59 | return envValue 60 | } 61 | return defaultValue 62 | } 63 | 64 | func getEnvStringsValue(envName string, defaultValues []string) []string { 65 | envValue := os.Getenv(envName) 66 | if len(envValue) > 0 { 67 | return strings.Split(envValue, ",") 68 | } 69 | return defaultValues 70 | } 71 | 72 | func getEnvIntValue(envName string, defaultValue int) int { 73 | envValue := os.Getenv(envName) 74 | if len(envValue) > 0 { 75 | intValue, err := strconv.Atoi(envValue) 76 | if nil != err { 77 | log.Errorf("[agent] fail to parse env %s, value %s to int, err: %v", envName, envValue, err) 78 | return defaultValue 79 | } 80 | return intValue 81 | } 82 | return defaultValue 83 | } 84 | 85 | func getEnvBoolValue(envName string, defaultValue bool) bool { 86 | envValue := os.Getenv(envName) 87 | if len(envValue) > 0 { 88 | boolValue, err := strconv.ParseBool(envValue) 89 | if nil != err { 90 | log.Errorf("[agent] fail to parse env %s, value %s to bool, err: %v", envName, envValue, err) 91 | return defaultValue 92 | } 93 | return boolValue 94 | } 95 | return defaultValue 96 | } 97 | -------------------------------------------------------------------------------- /bootstrap/signals_darwin.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package bootstrap 19 | 20 | import ( 21 | "os" 22 | "syscall" 23 | ) 24 | 25 | var signals = []os.Signal{ 26 | syscall.SIGINT, syscall.SIGTERM, 27 | syscall.SIGSEGV, syscall.SIGUSR1, 28 | } 29 | -------------------------------------------------------------------------------- /bootstrap/signals_linux.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package bootstrap 19 | 20 | import ( 21 | "os" 22 | "syscall" 23 | ) 24 | 25 | var signals = []os.Signal{ 26 | syscall.SIGINT, syscall.SIGTERM, 27 | syscall.SIGSEGV, syscall.SIGUSR1, 28 | } 29 | -------------------------------------------------------------------------------- /bootstrap/signals_windows.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package bootstrap 19 | 20 | import ( 21 | "os" 22 | "syscall" 23 | ) 24 | 25 | var signals = []os.Signal{ 26 | syscall.SIGINT, syscall.SIGTERM, 27 | syscall.SIGSEGV, 28 | } 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ $# -gt 0 ]; then 6 | version="$1" 7 | else 8 | current=`date "+%Y-%m-%d %H:%M:%S"` 9 | timeStamp=`date -d "$current" +%s` 10 | currentTimeStamp=$(((timeStamp*1000+10#`date "+%N"`/1000000)/1000)) 11 | version="$currentTimeStamp" 12 | fi 13 | workdir=$(dirname $(realpath $0)) 14 | bin_name="polaris-sidecar" 15 | if [ "${GOOS}" == "" ]; then 16 | GOOS=$(go env GOOS) 17 | fi 18 | if [ "${GOARCH}" == "" ]; then 19 | GOARCH=$(go env GOARCH) 20 | fi 21 | folder_name="polaris-sidecar-release_${version}.${GOOS}.${GOARCH}" 22 | pkg_name="${folder_name}.zip" 23 | if [ "${GOOS}" == "windows" ]; then 24 | bin_name="polaris-sidecar.exe" 25 | fi 26 | echo "GOOS is ${GOOS}, GOARCH is ${GOARCH}, binary name is ${bin_name}" 27 | 28 | cd $workdir 29 | 30 | # 清理环境 31 | rm -rf ${folder_name} 32 | rm -f "${pkg_name}" 33 | 34 | # 编译 35 | rm -f ${bin_name} 36 | 37 | # 禁止 CGO_ENABLED 参数打开 38 | export CGO_ENABLED=0 39 | 40 | build_date=$(date "+%Y%m%d.%H%M%S") 41 | package="github.com/polarismesh/polaris-sidecar/version" 42 | GOARCH=${GOARCH} GOOS=${GOOS} go build -o ${bin_name} -ldflags="-X ${package}.Version=${version} -X ${package}.BuildDate=${build_date}" 43 | 44 | # 设置程序为可执行 45 | chmod +x ${bin_name} 46 | 47 | # 打包 48 | mkdir -p ${folder_name} 49 | cp ${bin_name} ${folder_name} 50 | cp polaris-sidecar.yaml ${folder_name} 51 | cp -r tool ${folder_name}/ 52 | zip -r "${pkg_name}" ${folder_name} 53 | #md5sum ${pkg_name} > "${pkg_name}.md5sum" 54 | 55 | if [[ $(uname -a | grep "Darwin" | wc -l) -eq 1 ]]; then 56 | md5 ${pkg_name} >"${pkg_name}.md5sum" 57 | else 58 | md5sum ${pkg_name} >"${pkg_name}.md5sum" 59 | fi 60 | -------------------------------------------------------------------------------- /build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DOCKER_IMAGE_RPO="${DOCKER_RPO}" 6 | if [[ "${DOCKER_RPO}" == "" ]]; then 7 | DOCKER_IMAGE_RPO="polarismesh" 8 | fi 9 | 10 | if [ $# != 1 ]; then 11 | echo "e.g.: bash $0 v1.0" 12 | exit 1 13 | fi 14 | 15 | docker_tag=$1 16 | 17 | echo "docker repository : ${DOCKER_IMAGE_RPO}/polaris-sidecar, tag : ${docker_tag}" 18 | 19 | bash build.sh ${docker_tag} 20 | 21 | if [ $? != 0 ]; then 22 | echo "build polaris-sidecar failed" 23 | exit 1 24 | fi 25 | 26 | docker build --network=host -t ${DOCKER_IMAGE_RPO}/polaris-sidecar:${docker_tag} ./ 27 | docker push ${DOCKER_IMAGE_RPO}/polaris-sidecar:${docker_tag} 28 | 29 | pre_release=$(echo ${docker_tag} | egrep "(alpha|beta|rc)" | wc -l) 30 | if [ ${pre_release} == 0 ]; then 31 | docker tag ${DOCKER_IMAGE_RPO}/polaris-sidecar:${docker_tag} ${DOCKER_IMAGE_RPO}/polaris-sidecar:latest 32 | docker push ${DOCKER_IMAGE_RPO}/polaris-sidecar:latest 33 | fi 34 | -------------------------------------------------------------------------------- /build_vm.sh: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | 3 | set -e 4 | 5 | if [ $# -gt 0 ]; then 6 | version="$1" 7 | else 8 | current=`date "+%Y-%m-%d %H:%M:%S"` 9 | timeStamp=`date -d "$current" +%s` 10 | currentTimeStamp=$(((timeStamp*1000+10#`date "+%N"`/1000000)/1000)) 11 | version="$currentTimeStamp" 12 | fi 13 | workdir=$(dirname $(realpath $0)) 14 | bin_name="polaris-sidecar" 15 | if [ "${GOOS}" == "" ]; then 16 | GOOS=$(go env GOOS) 17 | fi 18 | if [ "${GOARCH}" == "" ]; then 19 | GOARCH=$(go env GOARCH) 20 | fi 21 | 22 | # 先构建部署包 23 | bash build.sh ${version} 24 | 25 | package_name="polaris-sidecar-local_${version}.${GOOS}.${GOARCH}.zip" 26 | folder_name="polaris-sidecar-install" 27 | 28 | mkdir -p ${folder_name} 29 | 30 | deploy_package=$(ls -lstrh | awk '{print $10}' | grep -E "^polaris-sidecar-release.+zip$") 31 | 32 | mv ${deploy_package} ${folder_name} 33 | cp ./deploy/vm/*.sh ${folder_name} 34 | 35 | zip -r "${package_name}" ${folder_name} 36 | rm -rf ${folder_name} 37 | rm -rf polaris-sidecar-release_* 38 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package cmd 19 | 20 | import ( 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | var ( 25 | rootCmd = &cobra.Command{ 26 | Use: "polaris-sidecar", 27 | Short: "polaris sidecar", 28 | Long: "polaris sidecar", 29 | SilenceUsage: true, 30 | } 31 | ) 32 | 33 | /** 34 | * @brief 初始化命令行工具 35 | */ 36 | func init() { 37 | rootCmd.AddCommand(startCmd) 38 | rootCmd.AddCommand(versionCmd) 39 | } 40 | 41 | /** 42 | * @brief 执行命令行解析 43 | */ 44 | func Execute() { 45 | rootCmd.Execute() 46 | } 47 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | 14 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 15 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations under the License. 17 | */ 18 | 19 | package cmd 20 | 21 | import ( 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/polarismesh/polaris-sidecar/bootstrap" 25 | "github.com/polarismesh/polaris-sidecar/bootstrap/config" 26 | ) 27 | 28 | var ( 29 | configFilePath = "" 30 | 31 | bootConfig config.BootConfig 32 | 33 | startCmd = &cobra.Command{ 34 | Use: "start", 35 | Short: "start running", 36 | Long: "start running", 37 | Run: func(c *cobra.Command, args []string) { 38 | bootstrap.Start(configFilePath, &bootConfig) 39 | }, 40 | } 41 | ) 42 | 43 | /** 44 | * @brief 解析命令参数 45 | */ 46 | func init() { 47 | startCmd.PersistentFlags().StringVarP( 48 | &configFilePath, "config-file", "c", "polaris-sidecar.yaml", "config file path") 49 | 50 | startCmd.PersistentFlags().StringVarP( 51 | &bootConfig.Bind, "bind", "b", "", "polaris sidecar bind host") 52 | 53 | startCmd.PersistentFlags().IntVarP( 54 | &bootConfig.Port, "port", "p", 0, "polaris sidecar listen port") 55 | 56 | startCmd.PersistentFlags().StringVarP( 57 | &bootConfig.LogLevel, "log-level", "l", "", "polaris sidecar logger level") 58 | 59 | startCmd.PersistentFlags().StringVarP(&bootConfig.RecurseEnabled, 60 | "recurse-enabled", "r", "", "polaris sidecar recurse enabled") 61 | 62 | startCmd.PersistentFlags().StringVarP(&bootConfig.ResolverDnsAgentEnabled, 63 | "dnsagent-enabled", "d", "", "polaris sidecar resolver dnsagent enabled") 64 | 65 | startCmd.PersistentFlags().StringVarP(&bootConfig.ResolverDnsAgentRouteLabels, 66 | "dnsagent-route-labels", "o", "", "polaris sidecar resolver dnsagent route lables") 67 | 68 | startCmd.PersistentFlags().StringVarP(&bootConfig.ResolverMeshProxyEnabled, 69 | "meshproxy-enabled", "m", "", "polaris sidecar resolver mesh proxy enabled") 70 | } 71 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package cmd 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/polarismesh/polaris-sidecar/version" 26 | ) 27 | 28 | var ( 29 | versionCmd = &cobra.Command{ 30 | Use: "version", 31 | Short: "print version", 32 | Long: "print version", 33 | Run: func(c *cobra.Command, args []string) { 34 | fmt.Printf("version: %v\n", version.Get()) 35 | }, 36 | } 37 | ) 38 | -------------------------------------------------------------------------------- /deploy/k8s/configmap-polaris-sidecar.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: polaris-sidecar-config 5 | data: 6 | polaris-sidecar.yaml: |- 7 | bind: 0.0.0.0 8 | port: 53 9 | namespace: default 10 | polaris: 11 | addresses: 12 | - 127.0.0.1 13 | recurse: 14 | enable: false 15 | timeoutSec: 1 16 | mtls: 17 | enable: false 18 | logger: 19 | output_paths: 20 | - stdout 21 | error_output_paths: 22 | - stderr 23 | rotate_output_path: logs/polaris-sidecar.log 24 | error_rotate_output_path: logs/polaris-sidecar-error.log 25 | rotation_max_size: 100 26 | rotation_max_backups: 10 27 | rotation_max_age: 7 28 | output_level: info 29 | resolvers: 30 | - name: dnsagent 31 | dns_ttl: 10 32 | enable: true 33 | suffix: "." 34 | # option: 35 | # route_labels: "key:value,key:value" 36 | - name: meshproxy 37 | dns_ttl: 120 38 | enable: false 39 | option: 40 | reload_interval_sec: 30 41 | dns_answer_ip: 10.4.4.4 42 | recursion_available: true 43 | -------------------------------------------------------------------------------- /deploy/k8s/deployment-dnsagent.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: polaris-sidecar-dns 6 | labels: 7 | app: polaris-sidecar-dns 8 | spec: 9 | type: LoadBalancer 10 | ports: 11 | - port: 53 12 | protocol: TCP 13 | name: dns-tcp-server 14 | targetPort: 53 15 | - port: 53 16 | protocol: UDP 17 | name: dns-udp-server 18 | targetPort: 53 19 | selector: 20 | app: polaris-sidecar-dns 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | labels: 26 | app: polaris-sidecar-dns 27 | name: polaris-sidecar-dns 28 | spec: 29 | replicas: 1 30 | selector: 31 | matchLabels: 32 | app: polaris-sidecar-dns 33 | template: 34 | metadata: 35 | labels: 36 | app: polaris-sidecar-dns 37 | spec: 38 | containers: 39 | - image: polarismesh/polaris-sidecar:latest 40 | imagePullPolicy: Always 41 | name: polaris-sidecar 42 | command: ["./polaris-sidecar"] 43 | args: 44 | - start 45 | - -p 46 | - "53" 47 | - -r 48 | - "false" 49 | - -d 50 | - "true" 51 | - -m 52 | - "false" 53 | resources: 54 | limits: 55 | cpu: 100m 56 | memory: 500Mi 57 | requests: 58 | cpu: 100m 59 | memory: 500Mi 60 | securityContext: 61 | allowPrivilegeEscalation: false 62 | capabilities: 63 | add: 64 | - NET_ADMIN 65 | - NET_RAW 66 | drop: 67 | - ALL 68 | privileged: false 69 | readOnlyRootFilesystem: false 70 | runAsGroup: 0 71 | runAsNonRoot: false 72 | runAsUser: 0 73 | volumeMounts: 74 | - mountPath: /data/polaris.yaml 75 | name: polaris-client-config 76 | subPath: polaris.yaml 77 | - mountPath: /data/polaris-sidecar.yaml 78 | name: polaris-sidecar-config 79 | subPath: polaris-sidecar.yaml 80 | terminationMessagePath: /dev/termination-log 81 | terminationMessagePolicy: File 82 | restartPolicy: Always 83 | volumes: 84 | - configMap: 85 | defaultMode: 420 86 | name: polaris-client-config 87 | name: polaris-client-config 88 | - configMap: 89 | defaultMode: 420 90 | name: polaris-sidecar-config 91 | name: polaris-sidecar-config 92 | -------------------------------------------------------------------------------- /deploy/k8s/job-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: dns 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: dns 10 | image: anubhavmishra/tiny-tools 11 | command: ['dig', 'polaris.checker.polaris'] 12 | restartPolicy: Never 13 | backoffLimit: 4 -------------------------------------------------------------------------------- /deploy/vm/install-linux.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | dns_back_dir="./" 4 | polaris_server_addr="" 5 | while getopts ':b:s:h' OPT; do #选项后面的冒号表示该选项需要参数 6 | case $OPT in 7 | b) 8 | dns_back_dir="$OPTARG" 9 | ;; 10 | s) 11 | polaris_server_addr="$OPTARG" 12 | ;; 13 | h) 14 | echo "./install-linux.sh -s \${polaris_server_addr} -d \${dns_back_dir}" 15 | exit 0 16 | ;; 17 | ?) #当有不认识的选项的时候arg为? 18 | echo "./install-linux.sh -s \${polaris_server_addr} -d \${dns_back_dir}" 19 | exit 1 20 | ;; 21 | esac 22 | done 23 | 24 | if [[ "${polaris_server_addr}" == "" ]]; then 25 | echo "[ERROR] need set polaris_server_addr, eg: ./install-linux.sh -s \${polaris_server_addr}" 26 | exit 1 27 | fi 28 | 29 | echo "[INFO] input param: dns_backdir = ${dns_back_dir}" 30 | echo "[INFO] input param: polaris_server_addr = ${polaris_server_addr}" 31 | 32 | function install_polaris_sidecar() { 33 | echo -e "[INFO] install polaris sidecar ... " 34 | ps -ef | grep polaris-sidecar | grep -v grep 35 | local polaris_sidecar_num=$(ps -ef | grep polaris-sidecar | grep -v grep | wc -l) 36 | if [ ${polaris_sidecar_num} -ge 1 ]; then 37 | echo -e "[ERROR] polaris-sidecar is running, exit" 38 | exit -1 39 | fi 40 | 41 | local polaris_sidecar_pkg_num=$(find . -name "polaris-sidecar-release*.zip" | wc -l) 42 | if [ ${polaris_sidecar_pkg_num} != 1 ]; then 43 | echo -e "[ERROR] number of polaris sidecar package not equals to 1, exit" 44 | exit -1 45 | fi 46 | 47 | local target_polaris_sidecar_pkg=$(find . -name "polaris-sidecar-release*.zip") 48 | local polaris_sidecar_dirname=$(basename ${target_polaris_sidecar_pkg} .zip) 49 | if [ -e ${polaris_sidecar_dirname} ]; then 50 | echo -e "[INFO] ${polaris_sidecar_dirname} has exists, now remove it" 51 | rm -rf ${polaris_sidecar_dirname} 52 | fi 53 | unzip ${target_polaris_sidecar_pkg} >/dev/null 54 | pushd ${polaris_sidecar_dirname} 55 | 56 | # 修改 polaris_server_addr 地址 57 | export POLARIS_ADDRESS="${polaris_server_addr}" 58 | echo "POLARIS_ADDRESS=${POLARIS_ADDRESS}" 59 | /bin/bash ./tool/start.sh 60 | echo -e "[INFO] install polaris sidecar success" 61 | popd 62 | } 63 | 64 | function write_dns_conf() { 65 | ## 修改机器的 /etc/resolv.conf 文件 66 | old_cnf_str="" 67 | if [ -f "/etc/resolv.conf" ]; then 68 | current=$(date "+%Y-%m-%d %H:%M:%S") 69 | timeStamp=$(date -d "$current" +%s) 70 | currentTimeStamp=$(((timeStamp * 1000 + 10#$(date "+%N") / 1000000) / 1000)) 71 | version="$currentTimeStamp" 72 | cp /etc/resolv.conf ${dns_back_dir}/resolv.conf.bak_${version} 73 | fi 74 | 75 | echo "# polaris-sidecar resolv.conf" >/etc/resolv.conf 76 | echo "# This file is automatically generated." >>/etc/resolv.conf 77 | echo "" 78 | echo "nameserver 127.0.0.1" >>/etc/resolv.conf 79 | echo "" >>/etc/resolv.conf 80 | echo "" >>/etc/resolv.conf 81 | echo "# old resolv.conf" >>/etc/resolv.conf 82 | cat ${dns_back_dir}/resolv.conf.bak_${version} | while read line; do 83 | echo "[DEBUG] ${line}" 84 | echo ${line} >>/etc/resolv.conf 85 | done 86 | } 87 | 88 | # 安装北极星 sidecar 89 | install_polaris_sidecar 90 | if [[ $? != 0 ]]; then 91 | exit 1 92 | fi 93 | # 写入 dns_conf 文件 94 | write_dns_conf 95 | -------------------------------------------------------------------------------- /deploy/vm/uninstall-linux.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | dns_back_dir="./" 4 | while getopts "back_dir" arg; do #选项后面的冒号表示该选项需要参数 5 | case ${arg} in 6 | back_dir) 7 | dns_back_dir="${arg}" 8 | ;; 9 | ?) #当有不认识的选项的时候arg为? 10 | echo "unkonw argument" 11 | exit 1 12 | ;; 13 | esac 14 | done 15 | 16 | echo "[INFO] input param: dns_backdir = ${dns_back_dir}" 17 | 18 | function uninstall_polaris_sidecar() { 19 | echo -e "[INFO] start to stop polaris-sidecar process" 20 | local ret=$(ps -ef | grep polaris-sidecar | awk '{print $2}' | xargs kill -15) 21 | echo -e "[INFO] kill polaris-sidecar ret=${ret}" 22 | } 23 | 24 | function rollback_dns_conf() { 25 | echo "[INFO] start rollback /etc/resolv.conf..." 26 | if [ ! -d "${dns_back_dir}" ]; then 27 | echo "[ERROR] resolv.conf back_dir not exist" 28 | exit 1 29 | fi 30 | 31 | # 找到最新的一个 resolv.conf 备份文件 32 | local last_back_file=$(ls -lstrh "${dns_back_dir}" | grep resolv.conf.bak_ | sort | tail -1 | awk '{print $NF}') 33 | 34 | echo "" >/etc/resolv.conf 35 | cat "${dns_back_dir}/${last_back_file}" | while read line; do 36 | echo "[DEBUG] ${line}" 37 | echo "${line}" >>/etc/resolv.conf 38 | done 39 | } 40 | 41 | # 停止 polaris-sidecar 进程 42 | uninstall_polaris_sidecar 43 | # 恢复 /etc/resolv.conf 配置文件 44 | rollback_dns_conf 45 | -------------------------------------------------------------------------------- /docs/image/deploy_job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polarismesh/polaris-sidecar/2133bf349eb694e15a45d854c011dbc3c76a075e/docs/image/deploy_job.png -------------------------------------------------------------------------------- /docs/image/polaris_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polarismesh/polaris-sidecar/2133bf349eb694e15a45d854c011dbc3c76a075e/docs/image/polaris_architecture.png -------------------------------------------------------------------------------- /envoy/metrics/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package metrics 19 | 20 | import "time" 21 | 22 | type MetricConfig struct { 23 | // to enable the metrics server 24 | Enable bool `yaml:"enable"` 25 | // port listen for metric message 26 | Port int `yaml:"port"` 27 | // Type metrics data report type pull/push 28 | Type string `yaml:"type"` 29 | // Interval if use push, need set report interval metrics data to pushgateway 30 | Interval time.Duration `yaml:"interval"` 31 | // Address if use push, need set report metrics data to pushgateway server 32 | Address string `yaml:"address"` 33 | } 34 | -------------------------------------------------------------------------------- /envoy/metrics/server.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package metrics 19 | 20 | import ( 21 | "context" 22 | "encoding/json" 23 | "io" 24 | "io/ioutil" 25 | "net/http" 26 | "regexp" 27 | "strings" 28 | "time" 29 | 30 | adminv3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 31 | "github.com/golang/protobuf/jsonpb" 32 | "github.com/golang/protobuf/ptypes/wrappers" 33 | "github.com/polarismesh/polaris-go" 34 | "github.com/polarismesh/polaris-go/pkg/model" 35 | "github.com/polarismesh/polaris-go/pkg/model/pb" 36 | "github.com/polarismesh/specification/source/go/api/v1/service_manage" 37 | 38 | "github.com/polarismesh/polaris-sidecar/pkg/client" 39 | "github.com/polarismesh/polaris-sidecar/pkg/log" 40 | ) 41 | 42 | type Server struct { 43 | port int 44 | consumer polaris.ConsumerAPI 45 | namespace string 46 | } 47 | 48 | func NewServer(namespace string, port int) *Server { 49 | srv := &Server{ 50 | namespace: namespace, 51 | port: port, 52 | } 53 | return srv 54 | } 55 | 56 | const ticketDuration = 30 * time.Second 57 | 58 | func (s *Server) Start(ctx context.Context) error { 59 | var err error 60 | s.consumer, err = client.GetConsumerAPI() 61 | if nil != err { 62 | return err 63 | } 64 | ticker := time.NewTicker(ticketDuration) 65 | defer ticker.Stop() 66 | values := make(map[InstanceMetricKey]*InstanceMetricValue) 67 | for { 68 | select { 69 | case <-ticker.C: 70 | s.reportMetricByCluster(values) 71 | case <-ctx.Done(): 72 | log.Errorf("Server metric service stopped") 73 | s.consumer.Destroy() 74 | return nil 75 | } 76 | } 77 | } 78 | 79 | func (s *Server) getClusterStats() *StatsObject { 80 | resp, err := http.Get("http://127.0.0.1:15000/stats?format=json") 81 | if nil != err { 82 | if err == io.EOF { 83 | return nil 84 | } 85 | log.Warnf("[Metric] metric server received stat http error %s", err) 86 | return nil 87 | } 88 | defer resp.Body.Close() 89 | body, err := ioutil.ReadAll(resp.Body) 90 | if nil != err { 91 | log.Warnf("[Metric] fail to read all text from stat body stream, error: %s", err) 92 | return nil 93 | } 94 | statsObject := &StatsObject{} 95 | err = json.Unmarshal(body, statsObject) 96 | if nil != err { 97 | log.Warnf("[Metric] fail to unmarshal stat response text %s to cluster, error %s", string(body), err) 98 | return nil 99 | } 100 | return statsObject 101 | } 102 | 103 | const clusterRegex = "^cluster\\..+\\.upstream_rq_time$" 104 | 105 | func (s *Server) parseUpstreamDelay() map[string]float64 { 106 | var retValues = make(map[string]float64) 107 | statsObject := s.getClusterStats() 108 | if nil == statsObject { 109 | return retValues 110 | } 111 | if nil == statsObject || len(statsObject.Stats) == 0 { 112 | return retValues 113 | } 114 | for _, stat := range statsObject.Stats { 115 | if nil == stat.Histograms { 116 | continue 117 | } 118 | computedQuantiles := stat.Histograms.ComputedQuantiles 119 | if len(computedQuantiles) == 0 { 120 | continue 121 | } 122 | for _, computedQuantile := range computedQuantiles { 123 | matches, err := regexp.Match(clusterRegex, []byte(computedQuantile.Name)) 124 | if nil != err { 125 | log.Warnf("fail to match regex %s by %s, err: %v", clusterRegex, computedQuantile.Name, err) 126 | continue 127 | } 128 | if !matches { 129 | continue 130 | } 131 | var svcName string = computedQuantile.Name[strings.Index(computedQuantile.Name, ".")+1:] 132 | svcName = svcName[0:strings.LastIndex(svcName, ".")] 133 | values := computedQuantile.Values 134 | if len(values) < 3 { 135 | continue 136 | } 137 | // get the P50 value to perform as average 138 | value := values[2].Cumulative 139 | if nil == value { 140 | continue 141 | } 142 | switch v := value.(type) { 143 | case float64: 144 | retValues[svcName] = v 145 | case int: 146 | retValues[svcName] = float64(v) 147 | } 148 | 149 | } 150 | } 151 | return retValues 152 | } 153 | 154 | func (s *Server) reportMetricByCluster(values map[InstanceMetricKey]*InstanceMetricValue) { 155 | resp, err := http.Get("http://127.0.0.1:15000/clusters?format=json") 156 | if nil != err { 157 | if err == io.EOF { 158 | return 159 | } 160 | log.Warnf("[Metric] metric server received clusters http error %s", err) 161 | return 162 | } 163 | defer resp.Body.Close() 164 | body, err := ioutil.ReadAll(resp.Body) 165 | if nil != err { 166 | log.Warnf("[Metric] fail to read all text from body stream, error: %s", err) 167 | return 168 | } 169 | clusters := &adminv3.Clusters{} 170 | err = jsonpb.UnmarshalString(string(body), clusters) 171 | if nil != err { 172 | log.Warnf("[Metric] fail to unmarshal response text %s to cluster, error %s", string(body), err) 173 | return 174 | } 175 | delayValues := s.parseUpstreamDelay() 176 | log.Debugf("[Metric] parsed upstream delay is %v", delayValues) 177 | clusterStatuses := clusters.GetClusterStatuses() 178 | if len(clusterStatuses) > 0 { 179 | for _, clusterStatus := range clusterStatuses { 180 | clusterName := clusterStatus.GetName() 181 | hostStatuses := clusterStatus.GetHostStatuses() 182 | if len(hostStatuses) == 0 { 183 | continue 184 | } 185 | for _, hostStatus := range hostStatuses { 186 | address := hostStatus.GetAddress() 187 | if nil == address { 188 | continue 189 | } 190 | socketAddress := address.GetSocketAddress() 191 | if nil == socketAddress { 192 | continue 193 | } 194 | metricKey := InstanceMetricKey{ 195 | ClusterName: clusterName, Host: socketAddress.GetAddress(), Port: socketAddress.GetPortValue()} 196 | metricValue := &InstanceMetricValue{} 197 | stats := hostStatus.GetStats() 198 | if len(stats) > 0 { 199 | for _, stat := range stats { 200 | if stat.GetName() == "rq_total" { 201 | metricValue.RqTotal = stat.GetValue() 202 | } else if stat.GetName() == "rq_success" { 203 | metricValue.RqSuccess = stat.GetValue() 204 | } else if stat.GetName() == "rq_error" { 205 | metricValue.RqError = stat.GetValue() 206 | } 207 | } 208 | } 209 | subMetricValue := &InstanceMetricValue{} 210 | latestValue, ok := values[metricKey] 211 | if !ok { 212 | subMetricValue = metricValue 213 | } else { 214 | if metricValue.RqTotal > latestValue.RqTotal { 215 | subMetricValue.RqTotal = metricValue.RqTotal - latestValue.RqTotal 216 | } 217 | if metricValue.RqSuccess > latestValue.RqSuccess { 218 | subMetricValue.RqSuccess = metricValue.RqSuccess - latestValue.RqSuccess 219 | } 220 | if metricValue.RqError > latestValue.RqError { 221 | subMetricValue.RqError = metricValue.RqError - latestValue.RqError 222 | } 223 | } 224 | values[metricKey] = metricValue 225 | s.reportMetrics(metricKey, subMetricValue, delayValues[metricKey.ClusterName]) 226 | } 227 | } 228 | } 229 | } 230 | 231 | func (s *Server) reportMetrics(metricKey InstanceMetricKey, subMetricValue *InstanceMetricValue, delay float64) { 232 | log.Debugf("start to report metric data %s, metric key %s, delay %v", *subMetricValue, metricKey, delay) 233 | for i := 0; i < int(subMetricValue.RqSuccess); i++ { 234 | s.reportStatus(metricKey, model.RetSuccess, 200, delay) 235 | } 236 | for i := 0; i < int(subMetricValue.RqError); i++ { 237 | s.reportStatus(metricKey, model.RetFail, 500, delay) 238 | } 239 | } 240 | 241 | func (s *Server) reportStatus(metricKey InstanceMetricKey, retStatus model.RetStatus, code int32, delay float64) { 242 | callResult := &polaris.ServiceCallResult{} 243 | callResult.SetRetStatus(retStatus) 244 | namingInstance := &service_manage.Instance{} 245 | namingInstance.Service = &wrappers.StringValue{Value: metricKey.ClusterName} 246 | namingInstance.Namespace = &wrappers.StringValue{Value: s.namespace} 247 | namingInstance.Host = &wrappers.StringValue{Value: metricKey.Host} 248 | namingInstance.Port = &wrappers.UInt32Value{Value: metricKey.Port} 249 | instance := pb.NewInstanceInProto(namingInstance, 250 | &model.ServiceKey{Namespace: s.namespace, Service: metricKey.ClusterName}, nil) 251 | callResult.SetCalledInstance(instance) 252 | callResult.SetRetCode(code) 253 | callResult.SetDelay(time.Duration(delay) * time.Millisecond) 254 | if err := s.consumer.UpdateServiceCallResult(callResult); nil != err { 255 | log.Warnf("[Metric] fail to update service call result for %s, err: %v", metricKey, err) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /envoy/metrics/stat_object.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package metrics 19 | 20 | import "fmt" 21 | 22 | type InstanceMetricKey struct { 23 | ClusterName string 24 | Host string 25 | Port uint32 26 | } 27 | 28 | func (i InstanceMetricKey) String() string { 29 | return fmt.Sprintf("ClusterName %s, Host %s, Port %d", i.ClusterName, i.Host, i.Port) 30 | } 31 | 32 | type InstanceMetricValue struct { 33 | RqSuccess uint64 34 | RqError uint64 35 | RqTotal uint64 36 | } 37 | 38 | func (i InstanceMetricValue) String() string { 39 | return fmt.Sprintf("RqSuccess %d, RqError %d, RqTotal %d", i.RqSuccess, i.RqError, i.RqTotal) 40 | } 41 | 42 | type StatsObject struct { 43 | Stats []*Stat `json:"stats"` 44 | } 45 | 46 | type Stat struct { 47 | Name string `json:"name"` 48 | Value interface{} `json:"value"` 49 | Histograms *Histograms `json:"histograms"` 50 | } 51 | 52 | type Histograms struct { 53 | ComputedQuantiles []*ComputedQuantiles `json:"computed_quantiles"` 54 | SupportedQuantiles []float64 `json:"supported_quantiles"` 55 | } 56 | 57 | type CumulativeObject struct { 58 | Cumulative interface{} `json:"cumulative"` 59 | Interval interface{} `json:"interval"` 60 | } 61 | 62 | type ComputedQuantiles struct { 63 | Name string `json:"name"` 64 | Values []*CumulativeObject `json:"values"` 65 | } 66 | -------------------------------------------------------------------------------- /envoy/rls/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package rls 19 | 20 | type Config struct { 21 | Enable bool `yaml:"enable"` 22 | Network string `yaml:"network"` 23 | Address string `yaml:"address"` 24 | BindPort uint32 `yaml:"port"` 25 | TLSInfo *TLSInfo `yaml:"tls_info"` 26 | } 27 | 28 | func (c *Config) init() { 29 | if c.Network == "unix" && c.Address == "" { 30 | c.Address = DefaultRLSAddress 31 | } 32 | } 33 | 34 | const DefaultRLSAddress = "/tmp/polaris-sidecar/ratelimit/rls.sock" 35 | 36 | // TLSInfo tls 配置信息 37 | type TLSInfo struct { 38 | // CertFile 服务端证书文件 39 | CertFile string `yaml:"cert_file"` 40 | // KeyFile CertFile 的密钥 key 文件 41 | KeyFile string `yaml"json:"key_file"` 42 | } 43 | 44 | // IsEmpty 检查 tls 配置信息是否为空 当证书和密钥同时存在时才不为空 45 | func (t *TLSInfo) IsEmpty() bool { 46 | if t == nil { 47 | return true 48 | } 49 | if t.CertFile != "" && t.KeyFile != "" { 50 | return false 51 | } 52 | return true 53 | } 54 | -------------------------------------------------------------------------------- /envoy/rls/server.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package rls 19 | 20 | import ( 21 | "context" 22 | "net" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | 27 | v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/common/ratelimit/v3" 28 | pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v3" 29 | "github.com/polarismesh/polaris-go" 30 | "github.com/polarismesh/polaris-go/pkg/model" 31 | "github.com/polarismesh/polaris-sidecar/pkg/client" 32 | "github.com/polarismesh/polaris-sidecar/pkg/log" 33 | "go.uber.org/zap" 34 | "google.golang.org/grpc" 35 | "google.golang.org/grpc/credentials" 36 | ) 37 | 38 | func New(namespace string, conf *Config) (*RateLimitServer, error) { 39 | conf.init() 40 | return &RateLimitServer{ 41 | namespace: namespace, 42 | conf: conf, 43 | }, nil 44 | } 45 | 46 | type RateLimitServer struct { 47 | namespace string 48 | conf *Config 49 | ln net.Listener 50 | grpcSvr *grpc.Server 51 | limiter polaris.LimitAPI 52 | } 53 | 54 | func (svr *RateLimitServer) Run(ctx context.Context) error { 55 | if svr.conf.Network == "unix" { 56 | if err := os.MkdirAll(filepath.Dir(svr.conf.Address), os.ModePerm); err != nil { 57 | return err 58 | } 59 | } 60 | ln, err := net.Listen(svr.conf.Network, svr.conf.Address) 61 | if err != nil { 62 | return err 63 | } 64 | svr.ln = ln 65 | svr.limiter, err = client.GetLimitAPI() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // 指定使用服务端证书创建一个 TLS credentials 71 | var creds credentials.TransportCredentials 72 | if !svr.conf.TLSInfo.IsEmpty() { 73 | creds, err = credentials.NewServerTLSFromFile(svr.conf.TLSInfo.CertFile, svr.conf.TLSInfo.KeyFile) 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | // 设置 grpc server options 79 | opts := []grpc.ServerOption{} 80 | if creds != nil { 81 | // 指定使用 TLS credentials 82 | opts = append(opts, grpc.Creds(creds)) 83 | } 84 | server := grpc.NewServer(opts...) 85 | pb.RegisterRateLimitServiceServer(server, svr) 86 | return server.Serve(ln) 87 | } 88 | 89 | func (svr *RateLimitServer) Destroy() { 90 | if svr.grpcSvr != nil { 91 | svr.grpcSvr.GracefulStop() 92 | } 93 | if svr.ln != nil { 94 | _ = svr.ln.Close() 95 | } 96 | } 97 | 98 | const MaxUint32 = uint32(1<<32 - 1) 99 | 100 | func (svr *RateLimitServer) ShouldRateLimit(ctx context.Context, req *pb.RateLimitRequest) (*pb.RateLimitResponse, error) { 101 | log.Info("[envoy-rls] receive ratelimit request", zap.Any("req", req)) 102 | acquireQuota := req.GetHitsAddend() 103 | if acquireQuota == 0 { 104 | acquireQuota = 1 105 | } 106 | 107 | quotaReq, err := svr.buildQuotaRequest(req.GetDomain(), acquireQuota, req.GetDescriptors()) 108 | if err != nil { 109 | log.Error("[envoy-rls] build ratelimit quota request", zap.Error(err)) 110 | return nil, err 111 | } 112 | future, err := svr.limiter.GetQuota(quotaReq) 113 | if err != nil { 114 | log.Error("[envoy-rls] get quota", zap.Error(err)) 115 | return nil, err 116 | } 117 | resp := future.Get() 118 | 119 | overallCode := pb.RateLimitResponse_OK 120 | if resp.Code == model.QuotaResultLimited { 121 | overallCode = pb.RateLimitResponse_OVER_LIMIT 122 | } 123 | 124 | descriptorStatus := make([]*pb.RateLimitResponse_DescriptorStatus, 0, len(req.GetDescriptors())) 125 | for range req.GetDescriptors() { 126 | descriptorStatus = append(descriptorStatus, &pb.RateLimitResponse_DescriptorStatus{ 127 | Code: overallCode, 128 | }) 129 | } 130 | 131 | rlsRsp := &pb.RateLimitResponse{ 132 | OverallCode: overallCode, 133 | Statuses: descriptorStatus, 134 | RawBody: []byte(resp.Info), 135 | } 136 | log.Info("[envoy-rls] send envoy rls response", zap.Any("rsp", rlsRsp)) 137 | return rlsRsp, nil 138 | } 139 | 140 | func (svr *RateLimitServer) buildQuotaRequest(domain string, acquireQuota uint32, 141 | descriptors []*v3.RateLimitDescriptor) (polaris.QuotaRequest, error) { 142 | 143 | req := polaris.NewQuotaRequest() 144 | for i := range descriptors { 145 | descriptor := descriptors[i] 146 | for _, entry := range descriptor.GetEntries() { 147 | if entry.GetKey() == ":path" { 148 | req.SetMethod(entry.GetValue()) 149 | continue 150 | } 151 | req.AddArgument(model.BuildArgumentFromLabel(entry.GetKey(), entry.GetValue())) 152 | } 153 | } 154 | 155 | if strings.HasSuffix(domain, "."+svr.namespace) { 156 | domain = strings.TrimSuffix(domain, "."+svr.namespace) 157 | } 158 | req.SetNamespace(svr.namespace) 159 | req.SetService(domain) 160 | req.SetToken(acquireQuota) 161 | log.Info("[envoy-rls] build polaris quota request", zap.Any("param", req)) 162 | return req, nil 163 | } 164 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/polarismesh/polaris-sidecar 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/envoyproxy/go-control-plane v0.11.1 7 | github.com/golang/protobuf v1.5.3 8 | github.com/hashicorp/go-multierror v1.1.1 9 | github.com/intel-go/cpuid v0.0.0-20220614022739-219e067757cb 10 | github.com/miekg/dns v1.1.55 11 | github.com/natefinch/lumberjack v2.0.0+incompatible 12 | github.com/polarismesh/polaris-go v1.5.6 13 | github.com/polarismesh/specification v1.4.1 14 | github.com/spf13/cobra v1.7.0 15 | github.com/stretchr/testify v1.8.4 16 | go.uber.org/zap v1.26.0 17 | google.golang.org/grpc v1.60.1 18 | google.golang.org/protobuf v1.32.0 19 | gopkg.in/yaml.v2 v2.4.0 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect 25 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 26 | github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/dlclark/regexp2 v1.10.0 // indirect 29 | github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect 30 | github.com/google/uuid v1.5.0 // indirect 31 | github.com/hashicorp/errwrap v1.1.0 // indirect 32 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 33 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 34 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 35 | github.com/mitchellh/go-homedir v1.1.0 // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/pkg/errors v0.9.1 // indirect 38 | github.com/pmezard/go-difflib v1.0.0 // indirect 39 | github.com/prometheus/client_golang v1.18.0 // indirect 40 | github.com/prometheus/client_model v0.5.0 // indirect 41 | github.com/prometheus/common v0.45.0 // indirect 42 | github.com/prometheus/procfs v0.12.0 // indirect 43 | github.com/spaolacci/murmur3 v1.1.0 // indirect 44 | github.com/spf13/pflag v1.0.5 // indirect 45 | go.uber.org/multierr v1.11.0 // indirect 46 | golang.org/x/mod v0.11.0 // indirect 47 | golang.org/x/net v0.19.0 // indirect 48 | golang.org/x/sys v0.16.0 // indirect 49 | golang.org/x/text v0.14.0 // indirect 50 | golang.org/x/tools v0.10.0 // indirect 51 | google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect 52 | google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect 53 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /import-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Tencent is pleased to support the open source community by making Polaris available. 3 | # 4 | # Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | # 6 | # Licensed under the BSD 3-Clause License (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://opensource.org/licenses/BSD-3-Clause 11 | # 12 | # Unless required by applicable law or agreed to in writing, software distributed 13 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations under the License. 16 | 17 | # 格式化 go.mod 18 | go mod tidy -compat=1.17 19 | 20 | # 处理 go imports 的格式化 21 | rm -rf style_tool 22 | rm -rf goimports-reviser 23 | 24 | mkdir -p style_tool 25 | 26 | cd style_tool 27 | 28 | is_arm=$(/usr/bin/uname -m | grep "arm|aarch64" | wc -l) 29 | goimports_target_file="goimports-reviser_3.3.1_linux_amd64.tar.gz" 30 | 31 | if [ "$(uname)" == "Darwin" ]; then 32 | if [ "${is_arm}" == "1" ]; then 33 | goimports_target_file="goimports-reviser_3.3.1_darwin_arm64.tar.gz" 34 | else 35 | goimports_target_file="goimports-reviser_3.3.1_darwin_amd64.tar.gz" 36 | fi 37 | fi 38 | 39 | wget "https://github.com/incu6us/goimports-reviser/releases/download/v3.3.1/${goimports_target_file}" 40 | tar -zxvf ${goimports_target_file} 41 | mv goimports-reviser ../ 42 | 43 | cd ../ 44 | 45 | # 处理 go 代码格式化 46 | go fmt ./... 47 | 48 | find . -name "*.go" -type f | grep -v .pb.go | grep -v test/tools/tools.go | grep -v ./plugin.go | 49 | xargs -I {} ./goimports-reviser -rm-unused -format {} -project-name github.com/polarismesh/polaris-sidecar 50 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making CL5 available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package main 19 | 20 | import ( 21 | "github.com/polarismesh/polaris-sidecar/cmd" 22 | _ "github.com/polarismesh/polaris-sidecar/resolver/dnsagent" 23 | _ "github.com/polarismesh/polaris-sidecar/resolver/meshproxy" 24 | ) 25 | 26 | // main entry 27 | func main() { 28 | cmd.Execute() 29 | } 30 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package client 19 | 20 | import ( 21 | "errors" 22 | "strconv" 23 | "sync" 24 | 25 | "github.com/polarismesh/polaris-go" 26 | "github.com/polarismesh/polaris-go/api" 27 | "github.com/polarismesh/polaris-go/pkg/config" 28 | "github.com/polarismesh/polaris-go/plugin/metrics/prometheus" 29 | ) 30 | 31 | var ( 32 | lock sync.Mutex 33 | SDKContext api.SDKContext 34 | ) 35 | 36 | func InitSDKContext(conf *Config) error { 37 | lock.Lock() 38 | defer lock.Unlock() 39 | if SDKContext != nil { 40 | return nil 41 | } 42 | sdkCfg := config.NewDefaultConfiguration(conf.Addresses) 43 | sdkCfg.Consumer.CircuitBreaker.SetEnable(false) 44 | if conf.Metrics != nil { 45 | sdkCfg.Global.StatReporter.SetEnable(true) 46 | sdkCfg.Global.StatReporter.SetChain([]string{"prometheus"}) 47 | sdkCfg.Global.StatReporter.SetPluginConfig("prometheus", &prometheus.Config{ 48 | Type: conf.Metrics.Type, 49 | IP: conf.Metrics.IP, 50 | PortStr: strconv.FormatInt(int64(conf.Metrics.Port), 10), 51 | Interval: conf.Metrics.Interval, 52 | Address: conf.Metrics.Address, 53 | }) 54 | } 55 | if conf.LocationConfigImpl != nil { 56 | sdkCfg.Global.Location = conf.LocationConfigImpl 57 | } 58 | sdkCtx, err := polaris.NewSDKContextByConfig(sdkCfg) 59 | if err != nil { 60 | return err 61 | } 62 | SDKContext = sdkCtx 63 | return nil 64 | } 65 | 66 | func GetConsumerAPI() (polaris.ConsumerAPI, error) { 67 | if SDKContext == nil { 68 | return nil, errors.New("polaris SDKContext is nil") 69 | } 70 | return polaris.NewConsumerAPIByContext(SDKContext), nil 71 | } 72 | 73 | func GetProviderAPI() (polaris.ProviderAPI, error) { 74 | if SDKContext == nil { 75 | return nil, errors.New("polaris SDKContext is nil") 76 | } 77 | return polaris.NewProviderAPIByContext(SDKContext), nil 78 | } 79 | 80 | func GetLimitAPI() (polaris.LimitAPI, error) { 81 | if SDKContext == nil { 82 | return nil, errors.New("polaris SDKContext is nil") 83 | } 84 | return polaris.NewLimitAPIByContext(SDKContext), nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/client/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package client 19 | 20 | import ( 21 | "time" 22 | 23 | "github.com/polarismesh/polaris-go/pkg/config" 24 | ) 25 | 26 | type Config struct { 27 | Addresses []string `yaml:"addresses"` 28 | Metrics *Metrics 29 | LocationConfigImpl *config.LocationConfigImpl 30 | } 31 | 32 | type Metrics struct { 33 | // port listen for metric message 34 | Port int `yaml:"port"` 35 | // Type metrics data report type pull/push 36 | Type string `yaml:"type"` 37 | // IP if use pull, need open Prometheus HttpServer 38 | IP string `yaml:"-"` 39 | // Interval if use push, need set report interval metrics data to pushgateway 40 | Interval time.Duration `yaml:"interval"` 41 | // Address if use push, need set report metrics data to pushgateway server 42 | Address string `yaml:"address"` 43 | } 44 | -------------------------------------------------------------------------------- /pkg/http/debug.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package http 19 | 20 | import "net/http" 21 | 22 | type DebugHandler struct { 23 | Path string 24 | Handler http.HandlerFunc 25 | } 26 | -------------------------------------------------------------------------------- /pkg/log/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | // Once configured, this package intercepts the output of the standard golang "log" package as well as anything 19 | // sent to the global zap logger (zap.L()). 20 | package log 21 | 22 | import ( 23 | "fmt" 24 | "net/url" 25 | "os" 26 | "path/filepath" 27 | "time" 28 | 29 | "github.com/hashicorp/go-multierror" 30 | "github.com/natefinch/lumberjack" 31 | "go.uber.org/zap" 32 | "go.uber.org/zap/zapcore" 33 | ) 34 | 35 | // none is used to disable logging output as well as to disable stack tracing. 36 | const none zapcore.Level = 100 37 | 38 | var levelToZap = map[Level]zapcore.Level{ 39 | DebugLevel: zapcore.DebugLevel, 40 | InfoLevel: zapcore.InfoLevel, 41 | WarnLevel: zapcore.WarnLevel, 42 | ErrorLevel: zapcore.ErrorLevel, 43 | FatalLevel: zapcore.FatalLevel, 44 | NoneLevel: none, 45 | } 46 | 47 | // functions that can be replaced in a test setting 48 | type patchTable struct { 49 | write func(ent zapcore.Entry, fields []zapcore.Field) error 50 | sync func() error 51 | exitProcess func(code int) 52 | errorSink zapcore.WriteSyncer 53 | } 54 | 55 | func init() { 56 | // use our defaults for starters so that logging works even before everything is fully configured 57 | _ = Configure(DefaultOptions()) 58 | } 59 | 60 | // prepZap is a utility function used by the Configure function. 61 | func prepZap(options *Options) ([]zapcore.Core, zapcore.Core, zapcore.WriteSyncer, error) { 62 | encCfg := zapcore.EncoderConfig{ 63 | TimeKey: "time", 64 | LevelKey: "level", 65 | NameKey: "scope", 66 | CallerKey: "caller", 67 | MessageKey: "msg", 68 | StacktraceKey: "stack", 69 | LineEnding: zapcore.DefaultLineEnding, 70 | EncodeLevel: zapcore.LowercaseLevelEncoder, 71 | EncodeCaller: zapcore.ShortCallerEncoder, 72 | EncodeDuration: zapcore.StringDurationEncoder, 73 | EncodeTime: formatDate, 74 | } 75 | 76 | var enc zapcore.Encoder 77 | if options.JSONEncoding { 78 | enc = zapcore.NewJSONEncoder(encCfg) 79 | } else { 80 | enc = zapcore.NewConsoleEncoder(encCfg) 81 | } 82 | 83 | var rotateSink zapcore.WriteSyncer 84 | if len(options.RotateOutputPath) > 0 { 85 | rotateSink = zapcore.AddSync(&lumberjack.Logger{ 86 | Filename: options.RotateOutputPath, 87 | MaxSize: options.RotationMaxSize, 88 | MaxBackups: options.RotationMaxBackups, 89 | MaxAge: options.RotationMaxAge, 90 | }) 91 | } 92 | 93 | err := createPathIfNotExist(options.ErrorOutputPaths...) 94 | if err != nil { 95 | return nil, nil, nil, err 96 | } 97 | errSink, closeErrorSink, err := zap.Open(options.ErrorOutputPaths...) 98 | if err != nil { 99 | return nil, nil, nil, err 100 | } 101 | 102 | var outputSink zapcore.WriteSyncer 103 | if len(options.OutputPaths) > 0 { 104 | err := createPathIfNotExist(options.OutputPaths...) 105 | if err != nil { 106 | return nil, nil, nil, err 107 | } 108 | outputSink, _, err = zap.Open(options.OutputPaths...) 109 | if err != nil { 110 | closeErrorSink() 111 | return nil, nil, nil, err 112 | } 113 | } 114 | 115 | var sink zapcore.WriteSyncer 116 | if rotateSink != nil && outputSink != nil { 117 | sink = zapcore.NewMultiWriteSyncer(outputSink, rotateSink) 118 | } else if rotateSink != nil { 119 | sink = rotateSink 120 | } else if outputSink != nil { 121 | sink = outputSink 122 | } else { 123 | sink = zapcore.AddSync(os.Stdout) 124 | } 125 | 126 | var enabler zap.LevelEnablerFunc = func(lvl zapcore.Level) bool { 127 | switch lvl { 128 | case zapcore.FatalLevel: 129 | return defaultScope.FatalEnabled() 130 | case zapcore.ErrorLevel: 131 | return defaultScope.ErrorEnabled() 132 | case zapcore.WarnLevel: 133 | return defaultScope.WarnEnabled() 134 | case zapcore.InfoLevel: 135 | return defaultScope.InfoEnabled() 136 | } 137 | return defaultScope.DebugEnabled() 138 | } 139 | 140 | var errCore zapcore.Core 141 | if len(options.ErrorRotateOutputPath) > 0 { 142 | errRotateSink := zapcore.AddSync(&lumberjack.Logger{ 143 | Filename: options.ErrorRotateOutputPath, 144 | MaxSize: options.RotationMaxSize, 145 | MaxBackups: options.RotationMaxBackups, 146 | MaxAge: options.RotationMaxAge, 147 | }) 148 | errCore = zapcore.NewCore(enc, errRotateSink, zap.NewAtomicLevelAt(zapcore.ErrorLevel)) 149 | } 150 | 151 | cores := make([]zapcore.Core, 0) 152 | cores = append(cores, zapcore.NewCore(enc, sink, zap.NewAtomicLevelAt(zapcore.DebugLevel))) 153 | if nil != errCore { 154 | cores = append(cores, errCore) 155 | } 156 | return cores, zapcore.NewCore(enc, sink, enabler), errSink, nil 157 | } 158 | 159 | func formatDate(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 160 | //t = t.UTC() 不用utc时间 161 | year, month, day := t.Date() 162 | hour, minute, second := t.Clock() 163 | micros := t.Nanosecond() / 1000 164 | 165 | buf := make([]byte, 27) 166 | 167 | buf[0] = byte((year/1000)%10) + '0' 168 | buf[1] = byte((year/100)%10) + '0' 169 | buf[2] = byte((year/10)%10) + '0' 170 | buf[3] = byte(year%10) + '0' 171 | buf[4] = '-' 172 | buf[5] = byte((month)/10) + '0' 173 | buf[6] = byte((month)%10) + '0' 174 | buf[7] = '-' 175 | buf[8] = byte((day)/10) + '0' 176 | buf[9] = byte((day)%10) + '0' 177 | buf[10] = 'T' 178 | buf[11] = byte((hour)/10) + '0' 179 | buf[12] = byte((hour)%10) + '0' 180 | buf[13] = ':' 181 | buf[14] = byte((minute)/10) + '0' 182 | buf[15] = byte((minute)%10) + '0' 183 | buf[16] = ':' 184 | buf[17] = byte((second)/10) + '0' 185 | buf[18] = byte((second)%10) + '0' 186 | buf[19] = '.' 187 | buf[20] = byte((micros/100000)%10) + '0' 188 | buf[21] = byte((micros/10000)%10) + '0' 189 | buf[22] = byte((micros/1000)%10) + '0' 190 | buf[23] = byte((micros/100)%10) + '0' 191 | buf[24] = byte((micros/10)%10) + '0' 192 | buf[25] = byte((micros)%10) + '0' 193 | buf[26] = 'Z' 194 | 195 | enc.AppendString(string(buf)) 196 | } 197 | 198 | func updateScopes(typeName string, options *Options, cores []zapcore.Core, errSink zapcore.WriteSyncer) error { 199 | scope := FindScope(typeName) 200 | if scope == nil { 201 | return fmt.Errorf("unknown logger name '%s' specified", typeName) 202 | } 203 | 204 | // update the output levels of all listed scopes 205 | outPutLevel, ok := stringToLevel[options.OutputLevel] 206 | if !ok { 207 | return fmt.Errorf("unknown outPutLevel '%s' specified", options.OutputLevel) 208 | } 209 | scope.SetOutputLevel(outPutLevel) 210 | 211 | // update the stack tracing levels of all listed scopes 212 | stackTraceLevel, ok := stringToLevel[options.StacktraceLevel] 213 | if !ok { 214 | return fmt.Errorf("unknown stackTraceLevel '%s' specified", options.StacktraceLevel) 215 | } 216 | scope.SetStackTraceLevel(stackTraceLevel) 217 | 218 | // update patchTable 219 | pt := patchTable{ 220 | write: func(ent zapcore.Entry, fields []zapcore.Field) error { 221 | var errs error 222 | for _, core := range cores { 223 | if core.Enabled(ent.Level) { 224 | if err := core.Write(ent, fields); nil != err { 225 | errs = multierror.Append(errs, err) 226 | } 227 | } 228 | } 229 | if ent.Level == zapcore.FatalLevel { 230 | scope.getPathTable().exitProcess(1) 231 | } 232 | 233 | return errs 234 | }, 235 | sync: func() error { 236 | var errs error 237 | for _, core := range cores { 238 | if err := core.Sync(); nil != err { 239 | errs = multierror.Append(errs, err) 240 | } 241 | } 242 | return errs 243 | }, 244 | exitProcess: os.Exit, 245 | errorSink: errSink, 246 | } 247 | scope.pt.Store(&pt) 248 | 249 | // update the caller location setting of all listed scopes 250 | scope.SetLogCallers(options.LogCaller) 251 | 252 | return nil 253 | } 254 | 255 | // nolint: staticcheck 256 | // You typically call this once at process startup. 257 | // Configure Once this call returns, the logging system is ready to accept data. 258 | func Configure(options *Options) error { 259 | setDefaultOption(options) 260 | cores, captureCore, errSink, err := prepZap(options) 261 | if err != nil { 262 | return err 263 | } 264 | 265 | if err = updateScopes(DefaultLoggerName, options, cores, errSink); err != nil { 266 | return err 267 | } 268 | 269 | opts := []zap.Option{ 270 | zap.ErrorOutput(errSink), 271 | zap.AddCallerSkip(1), 272 | } 273 | 274 | if defaultScope.GetLogCallers() { 275 | opts = append(opts, zap.AddCaller()) 276 | } 277 | 278 | l := defaultScope.GetStackTraceLevel() 279 | if l != NoneLevel { 280 | opts = append(opts, zap.AddStacktrace(levelToZap[l])) 281 | } 282 | 283 | captureLogger := zap.New(captureCore, opts...) 284 | 285 | // capture global zap logging and force it through our logger 286 | _ = zap.ReplaceGlobals(captureLogger) 287 | 288 | // capture standard golang "log" package output and force it through our logger 289 | _ = zap.RedirectStdLog(captureLogger) 290 | return nil 291 | } 292 | 293 | // setDefaultOption 设置日志配置的默认值 294 | func setDefaultOption(options *Options) { 295 | if options.RotationMaxSize == 0 { 296 | options.RotationMaxSize = defaultRotationMaxSize 297 | } 298 | if options.RotationMaxAge == 0 { 299 | options.RotationMaxAge = defaultRotationMaxAge 300 | } 301 | if options.RotationMaxBackups == 0 { 302 | options.RotationMaxBackups = defaultRotationMaxBackups 303 | } 304 | if options.OutputLevel == "" { 305 | options.OutputLevel = levelToString[defaultOutputLevel] 306 | } 307 | if options.StacktraceLevel == "" { 308 | options.StacktraceLevel = levelToString[defaultStacktraceLevel] 309 | } 310 | // 默认打开 311 | options.LogCaller = true 312 | } 313 | 314 | // Sync flushes any buffered log entries. 315 | // Processes should normally take care to call Sync before exiting. 316 | func Sync() error { 317 | return defaultScope.getPathTable().sync() 318 | } 319 | 320 | // createPathIfNotExist 如果判断为本地文件,检查目录是否存在,不存在创建父级目录 321 | func createPathIfNotExist(paths ...string) error { 322 | for _, path := range paths { 323 | u, err := url.Parse(path) 324 | if err != nil { 325 | return fmt.Errorf("can't parse %q as a URL: %v", path, err) 326 | } 327 | if (u.Scheme == "" || u.Scheme == "file") && u.Path != "stdout" && u.Path != "stderr" { 328 | dir := filepath.Dir(u.Path) 329 | err := os.MkdirAll(dir, os.ModePerm) 330 | if err != nil { 331 | return fmt.Errorf("can't create %q directory: %v", dir, err) 332 | } 333 | } 334 | } 335 | return nil 336 | } 337 | -------------------------------------------------------------------------------- /pkg/log/default.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package log 19 | 20 | import ( 21 | "fmt" 22 | 23 | "go.uber.org/zap/zapcore" 24 | ) 25 | 26 | var defaultScope = RegisterScope(DefaultLoggerName, "Unscoped logging messages.", 0) 27 | 28 | // Fatal outputs a message at fatal level. 29 | func Fatal(msg string, fields ...zapcore.Field) { 30 | if defaultScope.GetOutputLevel() >= FatalLevel { 31 | defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, msg, fields) 32 | } 33 | } 34 | 35 | // Fatala uses fmt.Sprint to construct and log a message at fatal level. 36 | func Fatala(args ...interface{}) { 37 | if defaultScope.GetOutputLevel() >= FatalLevel { 38 | defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, fmt.Sprint(args...), nil) 39 | } 40 | } 41 | 42 | // Fatalf uses fmt.Sprintf to construct and log a message at fatal level. 43 | func Fatalf(template string, args ...interface{}) { 44 | if defaultScope.GetOutputLevel() >= FatalLevel { 45 | msg := template 46 | if len(args) > 0 { 47 | msg = fmt.Sprintf(template, args...) 48 | } 49 | defaultScope.emit(zapcore.FatalLevel, defaultScope.GetStackTraceLevel() >= FatalLevel, msg, nil) 50 | } 51 | } 52 | 53 | // FatalEnabled returns whether output of messages using this scope is currently enabled for fatal-level output. 54 | func FatalEnabled() bool { 55 | return defaultScope.GetOutputLevel() >= FatalLevel 56 | } 57 | 58 | // Error outputs a message at error level. 59 | func Error(msg string, fields ...zapcore.Field) { 60 | if defaultScope.GetOutputLevel() >= ErrorLevel { 61 | defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, msg, fields) 62 | } 63 | } 64 | 65 | // Errora uses fmt.Sprint to construct and log a message at error level. 66 | func Errora(args ...interface{}) { 67 | if defaultScope.GetOutputLevel() >= ErrorLevel { 68 | defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) 69 | } 70 | } 71 | 72 | // Errorf uses fmt.Sprintf to construct and log a message at error level. 73 | func Errorf(template string, args ...interface{}) { 74 | if defaultScope.GetOutputLevel() >= ErrorLevel { 75 | msg := template 76 | if len(args) > 0 { 77 | msg = fmt.Sprintf(template, args...) 78 | } 79 | defaultScope.emit(zapcore.ErrorLevel, defaultScope.GetStackTraceLevel() >= ErrorLevel, msg, nil) 80 | } 81 | } 82 | 83 | // ErrorEnabled returns whether output of messages using this scope is currently enabled for error-level output. 84 | func ErrorEnabled() bool { 85 | return defaultScope.GetOutputLevel() >= ErrorLevel 86 | } 87 | 88 | // Warn outputs a message at warn level. 89 | func Warn(msg string, fields ...zapcore.Field) { 90 | if defaultScope.GetOutputLevel() >= WarnLevel { 91 | defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, msg, fields) 92 | } 93 | } 94 | 95 | // Warna uses fmt.Sprint to construct and log a message at warn level. 96 | func Warna(args ...interface{}) { 97 | if defaultScope.GetOutputLevel() >= WarnLevel { 98 | defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, fmt.Sprint(args...), nil) 99 | } 100 | } 101 | 102 | // Warnf uses fmt.Sprintf to construct and log a message at warn level. 103 | func Warnf(template string, args ...interface{}) { 104 | if defaultScope.GetOutputLevel() >= WarnLevel { 105 | msg := template 106 | if len(args) > 0 { 107 | msg = fmt.Sprintf(template, args...) 108 | } 109 | defaultScope.emit(zapcore.WarnLevel, defaultScope.GetStackTraceLevel() >= WarnLevel, msg, nil) 110 | } 111 | } 112 | 113 | // WarnEnabled returns whether output of messages using this scope is currently enabled for warn-level output. 114 | func WarnEnabled() bool { 115 | return defaultScope.GetOutputLevel() >= WarnLevel 116 | } 117 | 118 | // Info outputs a message at info level. 119 | func Info(msg string, fields ...zapcore.Field) { 120 | if defaultScope.GetOutputLevel() >= InfoLevel { 121 | defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, msg, fields) 122 | } 123 | } 124 | 125 | // Infoa uses fmt.Sprint to construct and log a message at info level. 126 | func Infoa(args ...interface{}) { 127 | if defaultScope.GetOutputLevel() >= InfoLevel { 128 | defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, fmt.Sprint(args...), nil) 129 | } 130 | } 131 | 132 | // Infof uses fmt.Sprintf to construct and log a message at info level. 133 | func Infof(template string, args ...interface{}) { 134 | if defaultScope.GetOutputLevel() >= InfoLevel { 135 | msg := template 136 | if len(args) > 0 { 137 | msg = fmt.Sprintf(template, args...) 138 | } 139 | defaultScope.emit(zapcore.InfoLevel, defaultScope.GetStackTraceLevel() >= InfoLevel, msg, nil) 140 | } 141 | } 142 | 143 | // InfoEnabled returns whether output of messages using this scope is currently enabled for info-level output. 144 | func InfoEnabled() bool { 145 | return defaultScope.GetOutputLevel() >= InfoLevel 146 | } 147 | 148 | // Debug outputs a message at debug level. 149 | func Debug(msg string, fields ...zapcore.Field) { 150 | if defaultScope.GetOutputLevel() >= DebugLevel { 151 | defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, msg, fields) 152 | } 153 | } 154 | 155 | // Debuga uses fmt.Sprint to construct and log a message at debug level. 156 | func Debuga(args ...interface{}) { 157 | if defaultScope.GetOutputLevel() >= DebugLevel { 158 | defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, fmt.Sprint(args...), nil) 159 | } 160 | } 161 | 162 | // Debugf uses fmt.Sprintf to construct and log a message at debug level. 163 | func Debugf(template string, args ...interface{}) { 164 | if defaultScope.GetOutputLevel() >= DebugLevel { 165 | msg := template 166 | if len(args) > 0 { 167 | msg = fmt.Sprintf(template, args...) 168 | } 169 | defaultScope.emit(zapcore.DebugLevel, defaultScope.GetStackTraceLevel() >= DebugLevel, msg, nil) 170 | } 171 | } 172 | 173 | // DebugEnabled returns whether output of messages using this scope is currently enabled for debug-level output. 174 | func DebugEnabled() bool { 175 | return defaultScope.GetOutputLevel() >= DebugLevel 176 | } 177 | -------------------------------------------------------------------------------- /pkg/log/options.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package log 19 | 20 | import ( 21 | "errors" 22 | ) 23 | 24 | const ( 25 | DefaultLoggerName = "default" 26 | defaultOutputLevel = InfoLevel 27 | defaultStacktraceLevel = NoneLevel 28 | defaultOutputPath = "stdout" 29 | defaultErrorOutputPath = "stderr" 30 | defaultRotationMaxAge = 7 31 | defaultRotationMaxSize = 100 32 | defaultRotationMaxBackups = 10 33 | ) 34 | 35 | // Level is an enumeration of all supported log levels. 36 | type Level int 37 | 38 | const ( 39 | // NoneLevel disables logging 40 | NoneLevel Level = iota 41 | // FatalLevel enables fatal level logging 42 | FatalLevel 43 | // ErrorLevel enables error level logging 44 | ErrorLevel 45 | // WarnLevel enables warn level logging 46 | WarnLevel 47 | // InfoLevel enables info level logging 48 | InfoLevel 49 | // DebugLevel enables debug level logging 50 | DebugLevel 51 | ) 52 | 53 | var levelToString = map[Level]string{ 54 | DebugLevel: "debug", 55 | InfoLevel: "info", 56 | WarnLevel: "warn", 57 | ErrorLevel: "error", 58 | FatalLevel: "fatal", 59 | NoneLevel: "none", 60 | } 61 | 62 | var stringToLevel = map[string]Level{ 63 | "debug": DebugLevel, 64 | "info": InfoLevel, 65 | "warn": WarnLevel, 66 | "error": ErrorLevel, 67 | "fatal": FatalLevel, 68 | "none": NoneLevel, 69 | } 70 | 71 | // Options defines the set of options supported by logging package. 72 | type Options struct { 73 | // OutputPaths is a list of file system paths to write the log data to. 74 | // The special values stdout and stderr can be used to output to the 75 | // standard I/O streams. This defaults to stdout. 76 | OutputPaths []string `yaml:"output_paths"` 77 | 78 | // ErrorOutputPaths is a list of file system paths to write logger errors to. 79 | // The special values stdout and stderr can be used to output to the 80 | // standard I/O streams. This defaults to stderr. 81 | ErrorOutputPaths []string `yaml:"error_output_paths"` 82 | 83 | // RotateOutputPath is the path to a rotating log file. This file should 84 | // be automatically rotated over time, based on the rotation parameters such 85 | // as RotationMaxSize and RotationMaxAge. The default is to not rotate. 86 | // 87 | // This path is used as a foundational path. This is where log output is normally 88 | // saved. When a rotation needs to take place because the file got too big or too 89 | // old, then the file is renamed by appending a timestamp to the name. Such renamed 90 | // files are called backups. Once a backup has been created, 91 | // output resumes to this path. 92 | RotateOutputPath string `yaml:"rotate_output_path"` 93 | 94 | // RotateOutputPath is the path to a rotating error log file. This file should 95 | // be automatically rotated over time, based on the rotation parameters such 96 | // as RotationMaxSize and RotationMaxAge. The default is to not rotate. 97 | // 98 | // This path is used as a foundational path. This is where log output is normally 99 | // saved. When a rotation needs to take place because the file got too big or too 100 | // old, then the file is renamed by appending a timestamp to the name. Such renamed 101 | // files are called backups. Once a backup has been created, 102 | // output resumes to this path. 103 | ErrorRotateOutputPath string `yaml:"error_rotate_output_path"` 104 | 105 | // RotationMaxSize is the maximum size in megabytes of a log file before it gets 106 | // rotated. It defaults to 100 megabytes. 107 | RotationMaxSize int `yaml:"rotation_max_size"` 108 | 109 | // RotationMaxAge is the maximum number of days to retain old log files based on the 110 | // timestamp encoded in their filename. Note that a day is defined as 24 111 | // hours and may not exactly correspond to calendar days due to daylight 112 | // savings, leap seconds, etc. The default is to remove log files 113 | // older than 30 days. 114 | RotationMaxAge int `yaml:"rotation_max_age"` 115 | 116 | // RotationMaxBackups is the maximum number of old log files to retain. The default 117 | // is to retain at most 1000 logs. 118 | RotationMaxBackups int `yaml:"rotation_max_backups"` 119 | 120 | // JSONEncoding controls whether the log is formatted as JSON. 121 | JSONEncoding bool `yaml:"json_encoding"` 122 | 123 | OutputLevel string `yaml:"output_level"` 124 | StacktraceLevel string `yaml:"stacktrace_level"` 125 | LogCaller bool `yaml:"log_caller"` 126 | } 127 | 128 | // DefaultOptions returns a new set of options, initialized to the defaults 129 | func DefaultOptions() *Options { 130 | return &Options{ 131 | OutputPaths: []string{defaultOutputPath}, 132 | ErrorOutputPaths: []string{defaultErrorOutputPath}, 133 | RotationMaxSize: defaultRotationMaxSize, 134 | RotationMaxAge: defaultRotationMaxAge, 135 | RotationMaxBackups: defaultRotationMaxBackups, 136 | OutputLevel: levelToString[defaultOutputLevel], 137 | StacktraceLevel: levelToString[defaultStacktraceLevel], 138 | } 139 | } 140 | 141 | // SetOutputLevel sets the minimum log output level for a given scope. 142 | func (o *Options) SetOutputLevel(level string) error { 143 | _, exist := stringToLevel[level] 144 | if !exist { 145 | return errors.New("invalid log level") 146 | } 147 | o.OutputLevel = level 148 | return nil 149 | } 150 | 151 | // GetOutputLevel returns the minimum log output level for a given scope. 152 | func (o *Options) GetOutputLevel() Level { 153 | return stringToLevel[o.OutputLevel] 154 | } 155 | 156 | // SetStackTraceLevel sets the minimum stack tracing level for a given scope. 157 | func (o *Options) SetStacktraceLevel(level string) error { 158 | _, exist := stringToLevel[level] 159 | if !exist { 160 | return errors.New("invalid stack trace level") 161 | } 162 | o.StacktraceLevel = level 163 | return nil 164 | } 165 | 166 | // GetStackTraceLevel returns the minimum stack tracing level for a given scope. 167 | func (o *Options) GetStacktraceLevel() Level { 168 | return stringToLevel[o.StacktraceLevel] 169 | } 170 | -------------------------------------------------------------------------------- /pkg/log/scope.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package log 19 | 20 | import ( 21 | "fmt" 22 | "runtime" 23 | "strings" 24 | "sync" 25 | "sync/atomic" 26 | "time" 27 | 28 | "go.uber.org/zap" 29 | "go.uber.org/zap/zapcore" 30 | ) 31 | 32 | // Scope let's you log data for an area of code, enabling the user full control over 33 | // the level of logging output produced. 34 | type Scope struct { 35 | // immutable, set at creation 36 | name string 37 | nameToEmit string 38 | description string 39 | callerSkip int 40 | 41 | // set by the Configure method and adjustable dynamically 42 | outputLevel atomic.Value 43 | stackTraceLevel atomic.Value 44 | logCallers atomic.Value 45 | pt atomic.Value 46 | } 47 | 48 | var scopes = make(map[string]*Scope) 49 | 50 | var lock = sync.RWMutex{} 51 | 52 | // RegisterScope registers a new logging scope. If the same name is used multiple times 53 | // for a single process, the same Scope struct is returned. 54 | // 55 | // Scope names cannot include colons, commas, or periods. 56 | func RegisterScope(name string, description string, callerSkip int) *Scope { 57 | if strings.ContainsAny(name, ":,.") { 58 | panic(fmt.Sprintf("scope name %s is invalid, it cannot contain colons, commas, or periods", name)) 59 | } 60 | 61 | lock.Lock() 62 | defer lock.Unlock() 63 | 64 | s, ok := scopes[name] 65 | if !ok { 66 | s = &Scope{ 67 | name: name, 68 | description: description, 69 | callerSkip: callerSkip, 70 | } 71 | s.SetOutputLevel(InfoLevel) 72 | s.SetStackTraceLevel(NoneLevel) 73 | s.SetLogCallers(false) 74 | 75 | if name != DefaultLoggerName { 76 | s.nameToEmit = name 77 | } 78 | 79 | scopes[name] = s 80 | } 81 | 82 | return s 83 | } 84 | 85 | // FindScope returns a previously registered scope, or nil if the named scope wasn't previously registered 86 | func FindScope(scope string) *Scope { 87 | lock.RLock() 88 | defer lock.RUnlock() 89 | 90 | s := scopes[scope] 91 | return s 92 | } 93 | 94 | // Scopes returns a snapshot of the currently defined set of scopes 95 | func Scopes() map[string]*Scope { 96 | lock.RLock() 97 | defer lock.RUnlock() 98 | 99 | s := make(map[string]*Scope, len(scopes)) 100 | for k, v := range scopes { 101 | s[k] = v 102 | } 103 | 104 | return s 105 | } 106 | 107 | // Fatal outputs a message at fatal level. 108 | func (s *Scope) Fatal(msg string, fields ...zapcore.Field) { 109 | if s.GetOutputLevel() >= FatalLevel { 110 | s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, msg, fields) 111 | } 112 | } 113 | 114 | // Fatala uses fmt.Sprint to construct and log a message at fatal level. 115 | func (s *Scope) Fatala(args ...interface{}) { 116 | if s.GetOutputLevel() >= FatalLevel { 117 | s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, fmt.Sprint(args...), nil) 118 | } 119 | } 120 | 121 | // Fatalf uses fmt.Sprintf to construct and log a message at fatal level. 122 | func (s *Scope) Fatalf(template string, args ...interface{}) { 123 | if s.GetOutputLevel() >= FatalLevel { 124 | msg := template 125 | if len(args) > 0 { 126 | msg = fmt.Sprintf(template, args...) 127 | } 128 | s.emit(zapcore.FatalLevel, s.GetStackTraceLevel() >= FatalLevel, msg, nil) 129 | } 130 | } 131 | 132 | // FatalEnabled returns whether output of messages using this scope is currently enabled for fatal-level output. 133 | func (s *Scope) FatalEnabled() bool { 134 | return s.GetOutputLevel() >= FatalLevel 135 | } 136 | 137 | // Error outputs a message at error level. 138 | func (s *Scope) Error(msg string, fields ...zapcore.Field) { 139 | if s.GetOutputLevel() >= ErrorLevel { 140 | s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) 141 | } 142 | } 143 | 144 | // Errora uses fmt.Sprint to construct and log a message at error level. 145 | func (s *Scope) Errora(args ...interface{}) { 146 | if s.GetOutputLevel() >= ErrorLevel { 147 | s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) 148 | } 149 | } 150 | 151 | // Errorf uses fmt.Sprintf to construct and log a message at error level. 152 | func (s *Scope) Errorf(template string, args ...interface{}) { 153 | if s.GetOutputLevel() >= ErrorLevel { 154 | msg := template 155 | if len(args) > 0 { 156 | msg = fmt.Sprintf(template, args...) 157 | } 158 | s.emit(zapcore.ErrorLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) 159 | } 160 | } 161 | 162 | // ErrorEnabled returns whether output of messages using this scope is currently enabled for error-level output. 163 | func (s *Scope) ErrorEnabled() bool { 164 | return s.GetOutputLevel() >= ErrorLevel 165 | } 166 | 167 | // Warn outputs a message at warn level. 168 | func (s *Scope) Warn(msg string, fields ...zapcore.Field) { 169 | if s.GetOutputLevel() >= WarnLevel { 170 | s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) 171 | } 172 | } 173 | 174 | // Warna uses fmt.Sprint to construct and log a message at warn level. 175 | func (s *Scope) Warna(args ...interface{}) { 176 | if s.GetOutputLevel() >= WarnLevel { 177 | s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) 178 | } 179 | } 180 | 181 | // Warnf uses fmt.Sprintf to construct and log a message at warn level. 182 | func (s *Scope) Warnf(template string, args ...interface{}) { 183 | if s.GetOutputLevel() >= WarnLevel { 184 | msg := template 185 | if len(args) > 0 { 186 | msg = fmt.Sprintf(template, args...) 187 | } 188 | s.emit(zapcore.WarnLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) 189 | } 190 | } 191 | 192 | // WarnEnabled returns whether output of messages using this scope is currently enabled for warn-level output. 193 | func (s *Scope) WarnEnabled() bool { 194 | return s.GetOutputLevel() >= WarnLevel 195 | } 196 | 197 | // Info outputs a message at info level. 198 | func (s *Scope) Info(msg string, fields ...zapcore.Field) { 199 | if s.GetOutputLevel() >= InfoLevel { 200 | s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) 201 | } 202 | } 203 | 204 | // Infoa uses fmt.Sprint to construct and log a message at info level. 205 | func (s *Scope) Infoa(args ...interface{}) { 206 | if s.GetOutputLevel() >= InfoLevel { 207 | s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) 208 | } 209 | } 210 | 211 | // Infof uses fmt.Sprintf to construct and log a message at info level. 212 | func (s *Scope) Infof(template string, args ...interface{}) { 213 | if s.GetOutputLevel() >= InfoLevel { 214 | msg := template 215 | if len(args) > 0 { 216 | msg = fmt.Sprintf(template, args...) 217 | } 218 | s.emit(zapcore.InfoLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) 219 | } 220 | } 221 | 222 | // InfoEnabled returns whether output of messages using this scope is currently enabled for info-level output. 223 | func (s *Scope) InfoEnabled() bool { 224 | return s.GetOutputLevel() >= InfoLevel 225 | } 226 | 227 | // Debug outputs a message at debug level. 228 | func (s *Scope) Debug(msg string, fields ...zapcore.Field) { 229 | if s.GetOutputLevel() >= DebugLevel { 230 | s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, fields) 231 | } 232 | } 233 | 234 | // Debuga uses fmt.Sprint to construct and log a message at debug level. 235 | func (s *Scope) Debuga(args ...interface{}) { 236 | if s.GetOutputLevel() >= DebugLevel { 237 | s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, fmt.Sprint(args...), nil) 238 | } 239 | } 240 | 241 | // Debugf uses fmt.Sprintf to construct and log a message at debug level. 242 | func (s *Scope) Debugf(template string, args ...interface{}) { 243 | if s.GetOutputLevel() >= DebugLevel { 244 | msg := template 245 | if len(args) > 0 { 246 | msg = fmt.Sprintf(template, args...) 247 | } 248 | s.emit(zapcore.DebugLevel, s.GetStackTraceLevel() >= ErrorLevel, msg, nil) 249 | } 250 | } 251 | 252 | // DebugEnabled returns whether output of messages using this scope is currently enabled for debug-level output. 253 | func (s *Scope) DebugEnabled() bool { 254 | return s.GetOutputLevel() >= DebugLevel 255 | } 256 | 257 | // Name returns this scope's name. 258 | func (s *Scope) Name() string { 259 | return s.name 260 | } 261 | 262 | // Description returns this scope's description 263 | func (s *Scope) Description() string { 264 | return s.description 265 | } 266 | 267 | func (s *Scope) getPathTable() *patchTable { 268 | val := s.pt.Load() 269 | if val == nil { 270 | return nil 271 | } 272 | return val.(*patchTable) 273 | } 274 | 275 | const callerSkipOffset = 2 276 | 277 | func (s *Scope) emit(level zapcore.Level, dumpStack bool, msg string, fields []zapcore.Field) { 278 | e := zapcore.Entry{ 279 | Message: msg, 280 | Level: level, 281 | Time: time.Now(), 282 | LoggerName: s.nameToEmit, 283 | } 284 | 285 | if s.GetLogCallers() { 286 | e.Caller = zapcore.NewEntryCaller(runtime.Caller(s.callerSkip + callerSkipOffset)) 287 | } 288 | 289 | if dumpStack { 290 | e.Stack = zap.Stack("").String 291 | } 292 | 293 | pt := s.getPathTable() 294 | if pt != nil && pt.write != nil { 295 | if err := pt.write(e, fields); err != nil { 296 | _, _ = fmt.Fprintf(pt.errorSink, "%v log write error: %v\n", time.Now(), err) 297 | _ = pt.errorSink.Sync() 298 | } 299 | } 300 | } 301 | 302 | // SetOutputLevel adjusts the output level associated with the scope. 303 | func (s *Scope) SetOutputLevel(l Level) { 304 | s.outputLevel.Store(l) 305 | } 306 | 307 | // GetOutputLevel returns the output level associated with the scope. 308 | func (s *Scope) GetOutputLevel() Level { 309 | return s.outputLevel.Load().(Level) 310 | } 311 | 312 | // SetStackTraceLevel adjusts the stack tracing level associated with the scope. 313 | func (s *Scope) SetStackTraceLevel(l Level) { 314 | s.stackTraceLevel.Store(l) 315 | } 316 | 317 | // GetStackTraceLevel returns the stack tracing level associated with the scope. 318 | func (s *Scope) GetStackTraceLevel() Level { 319 | return s.stackTraceLevel.Load().(Level) 320 | } 321 | 322 | // SetLogCallers adjusts the output level associated with the scope. 323 | func (s *Scope) SetLogCallers(logCallers bool) { 324 | s.logCallers.Store(logCallers) 325 | } 326 | 327 | // GetLogCallers returns the output level associated with the scope. 328 | func (s *Scope) GetLogCallers() bool { 329 | return s.logCallers.Load().(bool) 330 | } 331 | -------------------------------------------------------------------------------- /polaris-sidecar.yaml: -------------------------------------------------------------------------------- 1 | logger: 2 | output_paths: 3 | - stdout 4 | error_output_paths: 5 | - stderr 6 | rotate_output_path: logs/polaris-sidecar.log 7 | error_rotate_output_path: logs/polaris-sidecar-error.log 8 | rotation_max_size: 100 9 | rotation_max_backups: 10 10 | rotation_max_age: 7 11 | output_level: info 12 | debugger: 13 | enable: false 14 | port: 30000 15 | polaris: 16 | addresses: 17 | - ${POLARIS_ADDRESS} 18 | # 地址提供插件,用于获取当前SDK所在的地域信息 19 | location: 20 | providers: 21 | - type: local 22 | region: ${REGION} 23 | zone: ${ZONE} 24 | campus: ${CAMPUS} 25 | # - type: remoteHttp 26 | # region: http://127.0.0.1/region 27 | # zone: http://127.0.0.1/zone 28 | # campus: http://127.0.0.1/campus 29 | bind: 0.0.0.0 30 | port: 53 31 | namespace: default 32 | recurse: 33 | enable: false 34 | timeoutSec: 1 35 | mtls: 36 | enable: false 37 | metrics: 38 | enable: true 39 | type: pull 40 | metricPort: 0 41 | ratelimit: 42 | enable: true 43 | network: unix 44 | resolvers: 45 | - name: dnsagent 46 | dns_ttl: 10 47 | enable: true 48 | suffix: "." 49 | # option: 50 | # route_labels: "key:value,key:value" 51 | - name: meshproxy 52 | dns_ttl: 120 53 | enable: false 54 | option: 55 | reload_interval_sec: 30 56 | dns_answer_ip: 10.4.4.4 57 | recursion_available: true 58 | -------------------------------------------------------------------------------- /resolver/dns_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package resolver 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | func Test_dnsHandler_preprocess(t *testing.T) { 25 | type fields struct { 26 | searchNames []string 27 | } 28 | type args struct { 29 | qname string 30 | } 31 | tests := []struct { 32 | name string 33 | fields fields 34 | args args 35 | want string 36 | }{ 37 | { 38 | name: "", 39 | fields: fields{ 40 | searchNames: []string{"polaris-system.svc.cluster.local.", "svc.cluster.local.", "cluster.local."}, 41 | }, 42 | args: args{ 43 | qname: "polaris.polaris-system.polaris-system.svc.cluster.local.", 44 | }, 45 | want: "polaris.polaris-system.", 46 | }, 47 | { 48 | name: "", 49 | fields: fields{ 50 | searchNames: []string{"polaris-system.svc.cluster.local.", "svc.cluster.local.", "cluster.local."}, 51 | }, 52 | args: args{ 53 | qname: "polaris.polaris-system.", 54 | }, 55 | want: "polaris.polaris-system.", 56 | }, 57 | { 58 | name: "", 59 | fields: fields{ 60 | searchNames: []string{"polaris-system.svc.cluster.local.", "svc.cluster.local.", "cluster.local."}, 61 | }, 62 | args: args{ 63 | qname: "polaris.polaris-system.svc.cluster.local.", 64 | }, 65 | want: "polaris.", 66 | }, 67 | { 68 | name: "", 69 | fields: fields{ 70 | searchNames: []string{"svc.cluster.local.", "polaris-system.svc.cluster.local.", "cluster.local."}, 71 | }, 72 | args: args{ 73 | qname: "polaris.polaris-system.svc.cluster.local.", 74 | }, 75 | want: "polaris.polaris-system.", 76 | }, 77 | } 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | d := &dnsServer{ 81 | searchNames: tt.fields.searchNames, 82 | } 83 | if got := d.Preprocess(tt.args.qname); got != tt.want { 84 | t.Errorf("dnsHandler.preprocess() = %v, want %v", got, tt.want) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /resolver/dnsagent/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package dnsagent 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | "strings" 24 | ) 25 | 26 | type resolverConfig struct { 27 | RouteLabelsMap map[string]string `json:"-"` 28 | RouteLabels string `json:"route_labels"` 29 | } 30 | 31 | func parseLabels(value string) map[string]string { 32 | values := make(map[string]string) 33 | if len(value) == 0 { 34 | return values 35 | } 36 | tokens := strings.Split(value, ",") 37 | for _, token := range tokens { 38 | idx := strings.Index(token, ":") 39 | if idx < 0 { 40 | values[token] = token 41 | } else { 42 | values[token[0:idx]] = token[idx+1:] 43 | } 44 | } 45 | return values 46 | } 47 | 48 | func parseOptions(options map[string]interface{}) (*resolverConfig, error) { 49 | config := &resolverConfig{} 50 | if len(options) == 0 { 51 | return config, nil 52 | } 53 | jsonBytes, err := json.Marshal(options) 54 | if nil != err { 55 | return nil, fmt.Errorf("fail to marshal %s config entry, err is %v", name, err) 56 | } 57 | if err = json.Unmarshal(jsonBytes, config); nil != err { 58 | return nil, fmt.Errorf("fail to unmarshal %s config entry, err is %v", name, err) 59 | } 60 | config.RouteLabelsMap = parseLabels(config.RouteLabels) 61 | return config, nil 62 | } 63 | -------------------------------------------------------------------------------- /resolver/dnsagent/plugin.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package dnsagent 19 | 20 | import ( 21 | "context" 22 | "encoding/hex" 23 | "fmt" 24 | "net" 25 | "strings" 26 | 27 | "github.com/miekg/dns" 28 | "github.com/polarismesh/polaris-go" 29 | "github.com/polarismesh/polaris-go/pkg/model" 30 | "go.uber.org/zap" 31 | 32 | "github.com/polarismesh/polaris-sidecar/pkg/client" 33 | debughttp "github.com/polarismesh/polaris-sidecar/pkg/http" 34 | "github.com/polarismesh/polaris-sidecar/pkg/log" 35 | "github.com/polarismesh/polaris-sidecar/resolver" 36 | ) 37 | 38 | func init() { 39 | resolver.Register(&resolverDiscovery{}) 40 | } 41 | 42 | const name = resolver.PluginNameDnsAgent 43 | 44 | type resolverDiscovery struct { 45 | consumer polaris.ConsumerAPI 46 | suffix string 47 | dnsTtl int 48 | config *resolverConfig 49 | namespace string 50 | } 51 | 52 | // Name will return the name to resolver 53 | func (r *resolverDiscovery) Name() string { 54 | return name 55 | } 56 | 57 | // Initialize will init the resolver on startup 58 | func (r *resolverDiscovery) Initialize(c *resolver.ConfigEntry) error { 59 | var err error 60 | r.config, err = parseOptions(c.Option) 61 | if nil != err { 62 | return err 63 | } 64 | r.consumer, err = client.GetConsumerAPI() 65 | if nil != err { 66 | return err 67 | } 68 | if strings.HasSuffix(c.Suffix, resolver.Quota) { 69 | r.suffix = c.Suffix 70 | } else { 71 | r.suffix = c.Suffix + resolver.Quota 72 | } 73 | r.dnsTtl = c.DnsTtl 74 | r.namespace = c.Namespace 75 | return err 76 | } 77 | 78 | // Start the plugin runnable 79 | func (r *resolverDiscovery) Start(context.Context) { 80 | 81 | } 82 | 83 | func (r *resolverDiscovery) Debugger() []debughttp.DebugHandler { 84 | return []debughttp.DebugHandler{} 85 | } 86 | 87 | // Destroy will destroy the resolver on shutdown 88 | func (r *resolverDiscovery) Destroy() { 89 | if nil != r.consumer { 90 | r.consumer.Destroy() 91 | } 92 | } 93 | 94 | func canDoResolve(qType uint16) bool { 95 | if qType == dns.TypeA { 96 | return true 97 | } 98 | if qType == dns.TypeAAAA { 99 | return true 100 | } 101 | if qType == dns.TypeSRV { 102 | return true 103 | } 104 | 105 | return false 106 | } 107 | 108 | // ServeDNS is like dns.Handler except ServeDNS may return an rcode 109 | // and/or error. 110 | // If ServeDNS writes to the response body, it should return a status 111 | // code. Resolvers assumes *no* reply has yet been written if the status 112 | // code is one of the following: 113 | // 114 | // * SERVFAIL (dns.RcodeServerFailure) 115 | // 116 | // * REFUSED (dns.RecodeRefused) 117 | // 118 | // * NOTIMP (dns.RcodeNotImplemented) 119 | func (r *resolverDiscovery) ServeDNS(ctx context.Context, question dns.Question, qname string) *dns.Msg { 120 | if !canDoResolve(question.Qtype) { 121 | return nil 122 | } 123 | 124 | msg := &dns.Msg{} 125 | labels := dns.SplitDomainName(qname) 126 | for i := range labels { 127 | if labels[i] == "_addr" { 128 | ret, err := hex.DecodeString(labels[i-1]) 129 | if err != nil { 130 | log.Error("decode ip str fail", zap.String("domain", qname), zap.Error(err)) 131 | return nil 132 | } 133 | rr := r.markRecord(question, net.IP(ret), nil) 134 | msg.Answer = append(msg.Answer, rr) 135 | return msg 136 | } 137 | } 138 | 139 | instances, err := r.lookupFromPolaris(qname, r.namespace) 140 | if err != nil { 141 | return nil 142 | } 143 | if instances == nil { 144 | return nil 145 | } 146 | 147 | //do reorder and unique 148 | for i := range instances { 149 | ins := instances[i] 150 | rr := r.markRecord(question, net.ParseIP(ins.GetHost()), ins) 151 | msg.Answer = append(msg.Answer, rr) 152 | } 153 | 154 | msg.Authoritative = true 155 | msg.Rcode = dns.RcodeSuccess 156 | 157 | return msg 158 | } 159 | 160 | func (r *resolverDiscovery) lookupFromPolaris(qname string, currentNs string) ([]model.Instance, error) { 161 | svcKey := resolver.ParseQname(qname, r.suffix, currentNs) 162 | if nil == svcKey { 163 | return nil, nil 164 | } 165 | request := &polaris.GetOneInstanceRequest{} 166 | request.Namespace = svcKey.Namespace 167 | request.Service = svcKey.Service 168 | if len(r.config.RouteLabelsMap) > 0 { 169 | request.SourceService = &model.ServiceInfo{Metadata: r.config.RouteLabelsMap} 170 | } 171 | resp, err := r.consumer.GetOneInstance(request) 172 | if nil != err { 173 | log.Errorf("[discovery] fail to lookup service %s, err: %v", *svcKey, err) 174 | return nil, err 175 | } 176 | 177 | return resp.GetInstances(), nil 178 | } 179 | 180 | func encodeIPAsFqdn(ip net.IP, svcKey model.ServiceKey) string { 181 | respDomain := fmt.Sprintf("%s._addr.%s.%s", hex.EncodeToString(ip), svcKey.Service, svcKey.Namespace) 182 | return dns.Fqdn(respDomain) 183 | } 184 | 185 | func (r *resolverDiscovery) markRecord(question dns.Question, address net.IP, ins model.Instance) dns.RR { 186 | 187 | var rr dns.RR 188 | 189 | qname := question.Name 190 | 191 | switch question.Qtype { 192 | case dns.TypeA: 193 | rr = &dns.A{ 194 | Hdr: dns.RR_Header{Name: qname, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: uint32(r.dnsTtl)}, 195 | A: address, 196 | } 197 | case dns.TypeSRV: 198 | if ins == nil { 199 | return rr 200 | } 201 | 202 | rr = &dns.SRV{ 203 | Hdr: dns.RR_Header{Name: qname, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: uint32(r.dnsTtl)}, 204 | Priority: uint16(ins.GetPriority()), 205 | Weight: uint16(ins.GetWeight()), 206 | Port: uint16(ins.GetPort()), 207 | Target: encodeIPAsFqdn(address, ins.GetInstanceKey().ServiceKey), 208 | } 209 | case dns.TypeAAAA: 210 | rr = &dns.AAAA{ 211 | Hdr: dns.RR_Header{Name: qname, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: uint32(r.dnsTtl)}, 212 | AAAA: address, 213 | } 214 | } 215 | return rr 216 | } 217 | -------------------------------------------------------------------------------- /resolver/dnsagent/plugin_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package dnsagent 19 | 20 | import ( 21 | "encoding/hex" 22 | "net" 23 | "testing" 24 | 25 | "github.com/miekg/dns" 26 | "github.com/polarismesh/polaris-go/pkg/model" 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func Test_encodeIPAsFqdn(t *testing.T) { 31 | type args struct { 32 | ip net.IP 33 | svcKey *model.ServiceKey 34 | } 35 | tests := []struct { 36 | name string 37 | args args 38 | want string 39 | }{ 40 | { 41 | name: "test-ipv4", 42 | args: args{ 43 | ip: net.ParseIP("127.0.0.1"), 44 | svcKey: &model.ServiceKey{ 45 | Namespace: "default", 46 | Service: "sidecar", 47 | }, 48 | }, 49 | want: "", 50 | }, 51 | { 52 | name: "test-ipv6", 53 | args: args{ 54 | ip: net.ParseIP("1050:0000:0000:0000:0005:0600:300c:326b"), 55 | svcKey: &model.ServiceKey{ 56 | Namespace: "default", 57 | Service: "sidecar", 58 | }, 59 | }, 60 | want: "", 61 | }, 62 | { 63 | name: "test-ipv6", 64 | args: args{ 65 | ip: net.ParseIP("1050:0:0:0:5:600:300c:326b"), 66 | svcKey: &model.ServiceKey{ 67 | Namespace: "default", 68 | Service: "sidecar", 69 | }, 70 | }, 71 | want: "", 72 | }, 73 | { 74 | name: "test-ipv6", 75 | args: args{ 76 | ip: net.ParseIP("ff06::c3"), 77 | svcKey: &model.ServiceKey{ 78 | Namespace: "default", 79 | Service: "sidecar", 80 | }, 81 | }, 82 | want: "", 83 | }, 84 | { 85 | name: "test-ipv6", 86 | args: args{ 87 | ip: net.ParseIP("::ffff:192.1.56.10"), 88 | svcKey: &model.ServiceKey{ 89 | Namespace: "default", 90 | Service: "sidecar", 91 | }, 92 | }, 93 | want: "", 94 | }, 95 | } 96 | for _, tt := range tests { 97 | t.Run(tt.name, func(t *testing.T) { 98 | got := encodeIPAsFqdn(tt.args.ip, *tt.args.svcKey) 99 | t.Log(got) 100 | 101 | ipStr := dns.SplitDomainName(got)[0] 102 | 103 | ret, err := hex.DecodeString(ipStr) 104 | assert.NoError(t, err) 105 | 106 | t.Log(net.IP(ret)) 107 | 108 | assert.Equal(t, tt.args.ip, net.IP(ret)) 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /resolver/meshproxy/base_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/polarismesh/polaris-go" 24 | "github.com/polarismesh/polaris-go/api" 25 | "github.com/polarismesh/polaris-go/pkg/model" 26 | ) 27 | 28 | type MeshTestSuit struct { 29 | r *envoyRegistry 30 | } 31 | 32 | func newMeshTestSuit(t *testing.T, conf *resolverConfig) (*MeshTestSuit, *MockConsumerAPI) { 33 | mockConsumer := &MockConsumerAPI{} 34 | r := &envoyRegistry{ 35 | conf: conf, 36 | consumer: mockConsumer, 37 | business: "", 38 | } 39 | 40 | return &MeshTestSuit{ 41 | r: r, 42 | }, mockConsumer 43 | } 44 | 45 | type MockConsumerAPI struct { 46 | mockRetSupplier func() *model.ServicesResponse 47 | } 48 | 49 | // GetOneInstance 同步获取单个服务 50 | func (m *MockConsumerAPI) GetOneInstance(req *polaris.GetOneInstanceRequest) (*model.OneInstanceResponse, error) { 51 | return nil, nil 52 | } 53 | 54 | // GetInstances 同步获取可用的服务列表 55 | func (m *MockConsumerAPI) GetInstances(req *polaris.GetInstancesRequest) (*model.InstancesResponse, error) { 56 | return nil, nil 57 | } 58 | 59 | // GetAllInstances 同步获取完整的服务列表 60 | func (m *MockConsumerAPI) GetAllInstances(req *polaris.GetAllInstancesRequest) (*model.InstancesResponse, error) { 61 | return nil, nil 62 | } 63 | 64 | // GetRouteRule 同步获取服务路由规则 65 | func (m *MockConsumerAPI) GetRouteRule(req *polaris.GetServiceRuleRequest) (*model.ServiceRuleResponse, error) { 66 | return nil, nil 67 | } 68 | 69 | // UpdateServiceCallResult 上报服务调用结果 70 | func (m *MockConsumerAPI) UpdateServiceCallResult(req *polaris.ServiceCallResult) error { 71 | return nil 72 | } 73 | 74 | // WatchService 订阅服务消息 75 | func (m *MockConsumerAPI) WatchService(req *polaris.WatchServiceRequest) (*model.WatchServiceResponse, error) { 76 | return nil, nil 77 | } 78 | 79 | // GetServices 根据业务同步获取批量服务 80 | func (m *MockConsumerAPI) GetServices(req *polaris.GetServicesRequest) (*model.ServicesResponse, error) { 81 | return m.mockRetSupplier(), nil 82 | } 83 | 84 | // InitCalleeService 初始化服务运行中需要的被调服务 85 | func (m *MockConsumerAPI) InitCalleeService(req *polaris.InitCalleeServiceRequest) error { 86 | return nil 87 | } 88 | 89 | // WatchAllInstances 监听服务实例变更事件 90 | func (m *MockConsumerAPI) WatchAllInstances(req *polaris.WatchAllInstancesRequest) (*model.WatchAllInstancesResponse, error) { 91 | return nil, nil 92 | } 93 | 94 | // WatchAllServices 监听服务列表变更事件 95 | func (m *MockConsumerAPI) WatchAllServices(req *polaris.WatchAllServicesRequest) (*model.WatchAllServicesResponse, error) { 96 | return nil, nil 97 | } 98 | 99 | // Destroy 销毁API,销毁后无法再进行调用 100 | func (m *MockConsumerAPI) Destroy() { 101 | } 102 | 103 | // SDKContext 获取SDK上下文 104 | func (m *MockConsumerAPI) SDKContext() api.SDKContext { 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /resolver/meshproxy/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "encoding/json" 22 | "fmt" 23 | ) 24 | 25 | type resolverConfig struct { 26 | Namespace string `json:"namespace"` 27 | RegistryHost string `json:"registry_host"` 28 | RegistryPort int `json:"registry_port"` 29 | ReloadIntervalSec int `json:"reload_interval_sec"` 30 | DNSAnswerIp string `json:"dns_answer_ip"` 31 | FilterByBusiness string `json:"filter_by_business"` 32 | RecursionAvailable bool `json:"recursion_available"` 33 | } 34 | 35 | func parseOptions(options map[string]interface{}) (*resolverConfig, error) { 36 | config := &resolverConfig{} 37 | if len(options) == 0 { 38 | return config, nil 39 | } 40 | jsonBytes, err := json.Marshal(options) 41 | if nil != err { 42 | return nil, fmt.Errorf("fail to marshal %s config entry, err is %v", name, err) 43 | } 44 | if err = json.Unmarshal(jsonBytes, config); nil != err { 45 | return nil, fmt.Errorf("fail to unmarshal %s config entry, err is %v", name, err) 46 | } 47 | return config, nil 48 | } 49 | -------------------------------------------------------------------------------- /resolver/meshproxy/dns.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making CL5 available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "context" 22 | "net" 23 | "strings" 24 | "sync/atomic" 25 | 26 | "github.com/miekg/dns" 27 | 28 | "github.com/polarismesh/polaris-sidecar/pkg/log" 29 | ) 30 | 31 | type LocalDNSServer struct { 32 | // dns look up table 33 | lookupTable atomic.Value 34 | dnsTtl uint32 35 | recursionAvailable bool 36 | } 37 | 38 | func (h *LocalDNSServer) UpdateLookupTable(polarisServices map[string]struct{}, dnsResponseIp string) { 39 | lookupTable := &LookupTable{ 40 | allHosts: map[string]struct{}{}, 41 | name4: map[string][]net.IP{}, 42 | name6: map[string][]net.IP{}, 43 | dnsTtl: h.dnsTtl, 44 | } 45 | 46 | var altHosts map[string]struct{} 47 | for service := range polarisServices { 48 | altHosts = map[string]struct{}{service + ".": {}} 49 | lookupTable.buildDNSAnswers(altHosts, []net.IP{net.ParseIP(dnsResponseIp)}, nil) 50 | } 51 | h.lookupTable.Store(lookupTable) 52 | log.Infof("[mesh] updated lookup table with %d hosts, allHosts are %v", 53 | len(lookupTable.allHosts), lookupTable.allHosts) 54 | } 55 | 56 | type LookupTable struct { 57 | // This table will be first looked up to see if the host is something that we got a Nametable entry for 58 | // (i.e. came from istiod's service registry). If it is, then we will be able to confidently return 59 | // NXDOMAIN errors for AAAA records for such hosts when only A records exist (or vice versa). If the 60 | // host does not exist in this map, then we will return nil, causing the caller to query the upstream 61 | // DNS server to resolve the host. Without this map, we would end up making unnecessary upstream DNS queries 62 | // for hosts that will never resolve (e.g., AAAA for svc1.ns1.svc.cluster.local.svc.cluster.local.) 63 | allHosts map[string]struct{} 64 | 65 | // The key is a FQDN matching a DNS query (like example.com.), the value is pre-created DNS RR records 66 | // of A or AAAA type as appropriate. 67 | name4 map[string][]net.IP 68 | name6 map[string][]net.IP 69 | 70 | dnsTtl uint32 71 | } 72 | 73 | func (table *LookupTable) buildDNSAnswers(altHosts map[string]struct{}, ipv4 []net.IP, ipv6 []net.IP) { 74 | for h := range altHosts { 75 | h = strings.ToLower(h) 76 | table.allHosts[h] = struct{}{} 77 | if len(ipv4) > 0 { 78 | table.name4[h] = ipv4 79 | } 80 | if len(ipv6) > 0 { 81 | table.name6[h] = ipv6 82 | } 83 | } 84 | } 85 | 86 | // Given a host, this function first decides if the host is part of our service registry. 87 | // If it is not part of the registry, return nil so that caller queries upstream. If it is part 88 | // of registry, we will look it up in one of our tables, failing which we will return NXDOMAIN. 89 | func (table *LookupTable) lookupHost(qtype uint16, questionHost string, hostname string) ([]dns.RR, bool) { 90 | var hostFound bool 91 | if _, hostFound = table.allHosts[hostname]; !hostFound { 92 | // this is not from our registry 93 | return nil, false 94 | } 95 | 96 | var out []dns.RR 97 | var ipAnswers []dns.RR 98 | switch qtype { 99 | case dns.TypeA: 100 | ipAnswers = a(questionHost, table.name4[hostname], table.dnsTtl) 101 | case dns.TypeAAAA: 102 | ipAnswers = aaaa(questionHost, table.name6[hostname], table.dnsTtl) 103 | } 104 | 105 | if len(ipAnswers) > 0 { 106 | // We will return a chained response. In a chained response, the first entry is the cname record, 107 | // and the second one is the A/AAAA record itself. Some clients do not follow cname redirects 108 | // with additional DNS queries. Instead, they expect all the resolved records to be in the same 109 | // big DNS response (presumably assuming that a recursive DNS query should do the deed, resolve 110 | // cname et al and return the composite response). 111 | out = append(out, ipAnswers...) 112 | } 113 | return out, hostFound 114 | } 115 | 116 | func newLocalDNSServer(dnsTtl uint32, recursionAvailable bool) (*LocalDNSServer, error) { 117 | h := &LocalDNSServer{ 118 | dnsTtl: dnsTtl, 119 | recursionAvailable: recursionAvailable, 120 | } 121 | return h, nil 122 | } 123 | 124 | func (h *LocalDNSServer) ServeDNS(ctx context.Context, question *dns.Question, qname string) *dns.Msg { 125 | var response *dns.Msg 126 | lp := h.lookupTable.Load() 127 | if lp == nil { 128 | return nil 129 | } 130 | 131 | lookupTable := lp.(*LookupTable) 132 | var answers []dns.RR 133 | 134 | hostname := strings.ToLower(qname) 135 | answers, hostFound := lookupTable.lookupHost(question.Qtype, question.Name, hostname) 136 | 137 | if hostFound { 138 | response = new(dns.Msg) 139 | response.Authoritative = true 140 | // https://github.com/coredns/coredns/issues/3835 141 | response.RecursionAvailable = h.recursionAvailable 142 | response.Answer = answers 143 | response.Rcode = dns.RcodeSuccess 144 | return response 145 | } 146 | return nil 147 | } 148 | 149 | // Borrowed from https://github.com/coredns/coredns/blob/master/plugin/hosts/hosts.go 150 | // a takes a slice of net.IPs and returns a slice of A RRs. 151 | func a(host string, ips []net.IP, ttl uint32) []dns.RR { 152 | answers := make([]dns.RR, len(ips)) 153 | for i, ip := range ips { 154 | r := new(dns.A) 155 | r.Hdr = dns.RR_Header{Name: host, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl} 156 | r.A = ip 157 | answers[i] = r 158 | } 159 | return answers 160 | } 161 | 162 | // aaaa takes a slice of net.IPs and returns a slice of AAAA RRs. 163 | func aaaa(host string, ips []net.IP, ttl uint32) []dns.RR { 164 | answers := make([]dns.RR, len(ips)) 165 | for i, ip := range ips { 166 | r := new(dns.AAAA) 167 | r.Hdr = dns.RR_Header{Name: host, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl} 168 | r.AAAA = ip 169 | answers[i] = r 170 | } 171 | return answers 172 | } 173 | -------------------------------------------------------------------------------- /resolver/meshproxy/plugin.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "context" 22 | "strings" 23 | "time" 24 | 25 | "github.com/polarismesh/polaris-go" 26 | 27 | "github.com/miekg/dns" 28 | 29 | "github.com/polarismesh/polaris-sidecar/pkg/client" 30 | debughttp "github.com/polarismesh/polaris-sidecar/pkg/http" 31 | "github.com/polarismesh/polaris-sidecar/pkg/log" 32 | "github.com/polarismesh/polaris-sidecar/resolver" 33 | ) 34 | 35 | const name = resolver.PluginNameMeshProxy 36 | 37 | type resolverMesh struct { 38 | localDNSServer *LocalDNSServer 39 | config *resolverConfig 40 | registry registry 41 | suffix string 42 | consumer polaris.ConsumerAPI 43 | } 44 | 45 | // Name will return the name to resolver 46 | func (r *resolverMesh) Name() string { 47 | return name 48 | } 49 | 50 | // Initialize will init the resolver on startup 51 | func (r *resolverMesh) Initialize(c *resolver.ConfigEntry) error { 52 | var err error 53 | r.config, err = parseOptions(c.Option) 54 | if nil != err { 55 | return err 56 | } 57 | r.config.Namespace = c.Namespace 58 | r.consumer, err = client.GetConsumerAPI() 59 | if nil != err { 60 | return err 61 | } 62 | r.registry, err = newRegistry(r.config, r.consumer, r.config.FilterByBusiness) 63 | if err != nil { 64 | return err 65 | } 66 | r.suffix = c.Suffix 67 | r.localDNSServer, err = newLocalDNSServer(uint32(c.DnsTtl), r.config.RecursionAvailable) 68 | if nil != err { 69 | return err 70 | } 71 | return err 72 | } 73 | 74 | // Destroy will destroy the resolver on shutdown 75 | func (r *resolverMesh) Destroy() { 76 | 77 | } 78 | 79 | // ServeDNS is like dns.Handler except ServeDNS may return an rcode 80 | // and/or error. 81 | // If ServeDNS writes to the response body, it should return a status 82 | // code. Resolvers assumes *no* reply has yet been written if the status 83 | // code is one of the following: 84 | // 85 | // * SERVFAIL (dns.RcodeServerFailure) 86 | // 87 | // * REFUSED (dns.RecodeRefused) 88 | // 89 | // * NOTIMP (dns.RcodeNotImplemented) 90 | func (r *resolverMesh) ServeDNS(ctx context.Context, question dns.Question, qname string) *dns.Msg { 91 | _, matched := resolver.MatchSuffix(qname, r.suffix) 92 | if !matched { 93 | log.Infof("[Mesh] suffix not matched for name %s, suffix %s", qname, r.suffix) 94 | return nil 95 | } 96 | ret := r.localDNSServer.ServeDNS(ctx, &question, qname) 97 | if ret != nil { 98 | return ret 99 | } 100 | // 可能这个时候 qname 只有服务名称,这里手动补充 Namespace 信息 101 | if strings.HasSuffix(qname, resolver.Quota) { 102 | qname = qname[0 : len(qname)-1] 103 | } 104 | qname = qname + "." + r.config.Namespace + "." 105 | ret = r.localDNSServer.ServeDNS(ctx, &question, qname) 106 | if ret == nil { 107 | log.Infof("[Mesh] host not found for name %s", qname) 108 | } 109 | return ret 110 | } 111 | 112 | func (r *resolverMesh) Start(ctx context.Context) { 113 | interval := time.Duration(r.config.ReloadIntervalSec) * time.Second 114 | 115 | go func() { 116 | ticker := time.NewTicker(interval) 117 | defer ticker.Stop() 118 | var currentServices map[string]struct{} 119 | var nextServices map[string]struct{} 120 | var changed bool 121 | 122 | nextServices, changed = r.doReload(currentServices) 123 | if changed { 124 | currentServices = nextServices 125 | } 126 | for { 127 | select { 128 | case <-ticker.C: 129 | nextServices, changed = r.doReload(currentServices) 130 | if changed { 131 | currentServices = nextServices 132 | } 133 | case <-ctx.Done(): 134 | return 135 | } 136 | } 137 | }() 138 | } 139 | 140 | func (r *resolverMesh) Debugger() []debughttp.DebugHandler { 141 | return []debughttp.DebugHandler{} 142 | } 143 | 144 | func (r *resolverMesh) doReload(currentServices map[string]struct{}) (map[string]struct{}, bool) { 145 | services, err := r.registry.GetCurrentNsService() 146 | if err != nil { 147 | log.Errorf("[mesh] error to get services, err: %v", err) 148 | return nil, false 149 | } 150 | if ifServiceListChanged(currentServices, services) { 151 | r.localDNSServer.UpdateLookupTable(services, r.config.DNSAnswerIp) 152 | return services, true 153 | } 154 | return nil, false 155 | } 156 | 157 | func ifServiceListChanged(currentServices, newNsServices map[string]struct{}) bool { 158 | if len(currentServices) != len(newNsServices) { 159 | return true 160 | } 161 | if len(currentServices) == 0 { 162 | return false 163 | } 164 | for svc := range currentServices { 165 | if _, ok := newNsServices[svc]; !ok { 166 | return true 167 | } 168 | } 169 | return false 170 | } 171 | 172 | func init() { 173 | resolver.Register(&resolverMesh{}) 174 | } 175 | -------------------------------------------------------------------------------- /resolver/meshproxy/plugin_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | func Test_resolverMesh_ServeDNS(t *testing.T) { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /resolver/meshproxy/registry_proxy.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making CL5 available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package meshproxy 19 | 20 | import ( 21 | "github.com/polarismesh/polaris-go" 22 | 23 | "github.com/polarismesh/polaris-sidecar/pkg/log" 24 | ) 25 | 26 | type registry interface { 27 | GetCurrentNsService() (map[string]struct{}, error) 28 | } 29 | 30 | func newRegistry(conf *resolverConfig, consumer polaris.ConsumerAPI, business string) (registry, error) { 31 | r := &envoyRegistry{ 32 | conf: conf, 33 | consumer: consumer, 34 | business: business, 35 | } 36 | return r, nil 37 | } 38 | 39 | type envoyRegistry struct { 40 | conf *resolverConfig 41 | consumer polaris.ConsumerAPI 42 | business string 43 | } 44 | 45 | func (r *envoyRegistry) GetCurrentNsService() (map[string]struct{}, error) { 46 | var services map[string]struct{} 47 | req := &polaris.GetServicesRequest{} 48 | req.Business = r.business 49 | resp, err := r.consumer.GetServices(&polaris.GetServicesRequest{}) 50 | if nil != err { 51 | log.Errorf("[Mesh] fail to request services from polaris, %v", err) 52 | return nil, err 53 | } 54 | if len(resp.Value) == 0 { 55 | log.Infof("[Mesh] services is empty") 56 | return services, nil 57 | } 58 | services = make(map[string]struct{}, len(resp.GetValue())) 59 | for _, svc := range resp.GetValue() { 60 | // 这里必须全匹配的模式存储 61 | services[svc.Service+"."+svc.Namespace] = struct{}{} 62 | } 63 | return services, nil 64 | } 65 | -------------------------------------------------------------------------------- /resolver/resolver.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package resolver 19 | 20 | import ( 21 | "context" 22 | 23 | "github.com/miekg/dns" 24 | debughttp "github.com/polarismesh/polaris-sidecar/pkg/http" 25 | ) 26 | 27 | const ( 28 | // PluginNameDnsAgent dns-agent plugin identity 29 | PluginNameDnsAgent = "dnsagent" 30 | // PluginNameMeshProxy mesh-proxy plugin identity 31 | PluginNameMeshProxy = "meshproxy" 32 | ) 33 | 34 | type ResolverConfig struct { 35 | BindLocalhost bool 36 | BindIP string 37 | BindPort uint32 38 | Recurse *RecurseConfig 39 | Resolvers []*ConfigEntry 40 | } 41 | 42 | // RecurseConfig recursor name resolve config 43 | type RecurseConfig struct { 44 | Enable bool `yaml:"enable"` 45 | TimeoutSec int `yaml:"timeoutSec"` 46 | NameServers []string `yaml:"name_servers"` 47 | } 48 | 49 | // ConfigEntry: resolver plugin config entry 50 | type ConfigEntry struct { 51 | Name string `yaml:"name"` 52 | Suffix string `yaml:"suffix"` 53 | DnsTtl int `yaml:"dns_ttl"` 54 | Enable bool `yaml:"enable"` 55 | Option map[string]interface{} `yaml:"option"` 56 | Namespace string `yaml:"-"` 57 | } 58 | 59 | // NamingResolver resolver interface 60 | type NamingResolver interface { 61 | // Name will return the name to resolver 62 | Name() string 63 | // Initialize will init the resolver on startup 64 | Initialize(c *ConfigEntry) error 65 | // Start the plugin runnable 66 | Start(context.Context) 67 | // Destroy will destroy the resolver on shutdown 68 | Destroy() 69 | // ServeDNS is like dns.Handler except ServeDNS may return an response or nil 70 | ServeDNS(context.Context, dns.Question, string) *dns.Msg 71 | // Debugger 72 | Debugger() []debughttp.DebugHandler 73 | } 74 | 75 | var resolvers = map[string]NamingResolver{} 76 | 77 | // Register naming resolver 78 | func Register(namingResolver NamingResolver) { 79 | resolvers[namingResolver.Name()] = namingResolver 80 | } 81 | 82 | // NameResolver get the resolver by name 83 | func NameResolver(name string) NamingResolver { 84 | return resolvers[name] 85 | } 86 | -------------------------------------------------------------------------------- /resolver/server.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package resolver 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "net" 24 | "os" 25 | "strconv" 26 | "strings" 27 | "time" 28 | 29 | "github.com/miekg/dns" 30 | debughttp "github.com/polarismesh/polaris-sidecar/pkg/http" 31 | "github.com/polarismesh/polaris-sidecar/pkg/log" 32 | ) 33 | 34 | const ( 35 | etcResolvConfPath = "/etc/resolv.conf" 36 | ) 37 | 38 | func IsFile(path string) bool { 39 | s, err := os.Stat(path) 40 | if err != nil { 41 | return false 42 | } 43 | return !s.IsDir() 44 | } 45 | 46 | func parseResolvConf(bindLocalhost bool) ([]string, []string) { 47 | if !IsFile(etcResolvConfPath) { 48 | return nil, nil 49 | } 50 | dnsConfig, err := dns.ClientConfigFromFile(etcResolvConfPath) 51 | if err != nil { 52 | log.Errorf("[agent] failed to load /etc/resolv.conf: %v", err) 53 | return nil, nil 54 | } 55 | var searchNames []string 56 | var nameservers []string 57 | if dnsConfig != nil { 58 | for _, search := range dnsConfig.Search { 59 | searchNames = append(searchNames, search+".") 60 | } 61 | 62 | for _, server := range dnsConfig.Servers { 63 | if server == "127.0.0.1" && bindLocalhost { 64 | continue 65 | } 66 | nameservers = append(nameservers, server) 67 | } 68 | } 69 | return nameservers, searchNames 70 | } 71 | 72 | func NewServers(conf *ResolverConfig) (*Server, error) { 73 | resolvers := make([]NamingResolver, 0, len(conf.Resolvers)) 74 | for _, resolverCfg := range conf.Resolvers { 75 | if !resolverCfg.Enable { 76 | log.Infof("[agent] resolver %s is not enabled", resolverCfg.Name) 77 | continue 78 | } 79 | name := resolverCfg.Name 80 | handler := NameResolver(name) 81 | if nil == handler { 82 | log.Errorf("[agent] resolver %s is not found", resolverCfg.Name) 83 | return nil, fmt.Errorf("fail to lookup resolver %s, consider it's not registered", name) 84 | } 85 | if err := handler.Initialize(resolverCfg); nil != err { 86 | for _, initHandler := range resolvers { 87 | initHandler.Destroy() 88 | } 89 | log.Errorf("[agent] fail to init resolver %s, err: %v", resolverCfg.Name, err) 90 | return nil, err 91 | } 92 | log.Infof("[agent] finished to init resolver %s", resolverCfg.Name) 93 | resolvers = append(resolvers, handler) 94 | } 95 | 96 | nameservers, searchNames := parseResolvConf(conf.BindLocalhost) 97 | log.Infof("[agent] finished to parse /etc/resolv.conf, nameservers %s, search %s", nameservers, searchNames) 98 | if len(conf.Recurse.NameServers) == 0 { 99 | conf.Recurse.NameServers = nameservers 100 | } 101 | recurseAddresses := make([]string, 0, len(conf.Recurse.NameServers)) 102 | for _, nameserver := range conf.Recurse.NameServers { 103 | recurseAddresses = append(recurseAddresses, fmt.Sprintf("%s:53", nameserver)) 104 | } 105 | udpServer := &dns.Server{ 106 | Addr: conf.BindIP + ":" + strconv.FormatUint(uint64(conf.BindPort), 10), Net: "udp", 107 | Handler: buildDNSServer( 108 | "udp", 109 | resolvers, 110 | searchNames, 111 | time.Duration(conf.Recurse.TimeoutSec)*time.Second, 112 | recurseAddresses, 113 | conf.Recurse.Enable, 114 | ), 115 | } 116 | tcpServer := &dns.Server{ 117 | Addr: conf.BindIP + ":" + strconv.FormatUint(uint64(conf.BindPort), 10), Net: "tcp", 118 | Handler: buildDNSServer( 119 | "tcp", 120 | resolvers, 121 | searchNames, 122 | time.Duration(conf.Recurse.TimeoutSec)*time.Second, 123 | recurseAddresses, 124 | conf.Recurse.Enable, 125 | ), 126 | } 127 | 128 | return &Server{ 129 | dnsSvrs: []*dns.Server{udpServer, tcpServer}, 130 | resolvers: resolvers, 131 | }, nil 132 | } 133 | 134 | type Server struct { 135 | dnsSvrs []*dns.Server 136 | resolvers []NamingResolver 137 | } 138 | 139 | func (svr *Server) Run(ctx context.Context) <-chan error { 140 | for _, handler := range svr.resolvers { 141 | handler.Start(ctx) 142 | log.Infof("[agent] success to start resolver %s", handler.Name()) 143 | } 144 | errChan := make(chan error) 145 | for i := range svr.dnsSvrs { 146 | go func(dnsSvr *dns.Server) { 147 | log.Infof("[agent] success to start dns server %s %s", dnsSvr.Addr, dnsSvr.Net) 148 | errChan <- dnsSvr.ListenAndServe() 149 | }(svr.dnsSvrs[i]) 150 | } 151 | return errChan 152 | } 153 | 154 | func (svr *Server) Debugger() []debughttp.DebugHandler { 155 | ret := make([]debughttp.DebugHandler, 0, 8) 156 | for i := range svr.resolvers { 157 | ret = append(ret, svr.resolvers[i].Debugger()...) 158 | } 159 | return ret 160 | } 161 | 162 | func (svr *Server) Destroy() error { 163 | for _, handler := range svr.resolvers { 164 | handler.Destroy() 165 | } 166 | return nil 167 | } 168 | 169 | func buildDNSServer(protocol string, 170 | resolvers []NamingResolver, 171 | searchNames []string, 172 | recursorTimeout time.Duration, 173 | recursors []string, 174 | recurseEnable bool) *dnsServer { 175 | return &dnsServer{ 176 | protocol: protocol, 177 | resolvers: resolvers, 178 | searchNames: searchNames, 179 | recursorTimeout: recursorTimeout, 180 | recursors: recursors, 181 | recurseEnable: recurseEnable, 182 | } 183 | } 184 | 185 | type dnsServer struct { 186 | protocol string 187 | resolvers []NamingResolver 188 | searchNames []string 189 | recursorTimeout time.Duration 190 | recursors []string 191 | recurseEnable bool 192 | } 193 | 194 | func (d *dnsServer) Preprocess(qname string) string { 195 | if len(d.searchNames) == 0 { 196 | return qname 197 | } 198 | 199 | var matched bool 200 | 201 | for { 202 | for _, searchName := range d.searchNames { 203 | if strings.HasSuffix(qname, searchName) { 204 | matched = true 205 | qname = qname[:len(qname)-len(searchName)] 206 | } 207 | } 208 | 209 | if !matched { 210 | break 211 | } 212 | matched = false 213 | } 214 | 215 | return qname 216 | } 217 | 218 | func (d *dnsServer) sendDnsCode(w dns.ResponseWriter, r *dns.Msg, code int) { 219 | msg := &dns.Msg{} 220 | msg.SetReply(r) 221 | msg.RecursionDesired = true 222 | msg.RecursionAvailable = true 223 | msg.Rcode = code 224 | msg.Truncate(size(d.protocol, r)) 225 | if edns := r.IsEdns0(); edns != nil { 226 | setEDNS(r, msg, true) 227 | } 228 | err := w.WriteMsg(msg) 229 | if nil != err { 230 | log.Errorf("[agent] fail to write dns response message, err: %v", err) 231 | } 232 | } 233 | 234 | func (d *dnsServer) sendDnsResponse(w dns.ResponseWriter, r *dns.Msg, msg *dns.Msg) { 235 | msg.SetReply(r) 236 | msg.Truncate(size(d.protocol, r)) 237 | if edns := r.IsEdns0(); edns != nil { 238 | setEDNS(r, msg, true) 239 | } 240 | err := w.WriteMsg(msg) 241 | if nil != err { 242 | log.Errorf("[agent] fail to write dns response message, err: %v", err) 243 | } 244 | } 245 | 246 | // ServeDNS handler callback 247 | func (d *dnsServer) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { 248 | // questions length is 0, send refused 249 | if len(req.Question) == 0 { 250 | d.sendDnsCode(w, req, dns.RcodeRefused) 251 | } 252 | // questions type we only accept 253 | question := req.Question[0] 254 | qname := d.Preprocess(question.Name) 255 | log.Infof("[agent] input question name %s, after Preprocess name %s", question.Name, qname) 256 | ctx := context.WithValue(context.Background(), ContextProtocol, d.protocol) 257 | var resp *dns.Msg 258 | for _, handler := range d.resolvers { 259 | resp = handler.ServeDNS(ctx, question, qname) 260 | if nil != resp { 261 | log.Infof("[agent] request %v, response for %s is %v", req, question.Name, resp) 262 | d.sendDnsResponse(w, req, resp) 263 | return 264 | } 265 | } 266 | d.handleRecurse(w, req) 267 | } 268 | 269 | // handleRecurse is used to handle recursive DNS queries 270 | func (d *dnsServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) { 271 | q := req.Question[0] 272 | network := "udp" 273 | defer func(s time.Time) { 274 | log.Debugf("[agent] request served from client, "+ 275 | "question: %s, network: %s, latency: %s, client: %s, client_network: %s", 276 | q.String(), network, time.Since(s).String(), resp.RemoteAddr().String(), resp.RemoteAddr().Network()) 277 | }(time.Now()) 278 | 279 | // Switch to TCP if the client is 280 | if _, ok := resp.RemoteAddr().(*net.TCPAddr); ok { 281 | network = "tcp" 282 | } 283 | if d.recurseEnable { 284 | // Recursively resolve 285 | c := &dns.Client{Net: network, Timeout: d.recursorTimeout} 286 | var r *dns.Msg 287 | var rtt time.Duration 288 | var err error 289 | for _, recursor := range d.recursors { 290 | r, rtt, err = c.Exchange(req, recursor) 291 | // Check if the response is valid and has the desired Response code 292 | if r != nil && (r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError) { 293 | log.Warnf("[agent] recurse failed for question, question: %s, rtt: %s, recursor: %s, rcode: %s", 294 | q.String(), rtt, recursor, dns.RcodeToString[r.Rcode]) 295 | // If we still have recursors to forward the query to, 296 | // we move forward onto the next one else the loop ends 297 | continue 298 | } else if err == nil || (r != nil && r.Truncated) { 299 | // Forward the response 300 | log.Debugf("[agent] recurse succeeded for question, question: %s, rtt: %s, recursor: %s", 301 | q.String(), rtt, recursor) 302 | if err := resp.WriteMsg(r); err != nil { 303 | log.Warnf("failed to respond, error: %v", err) 304 | } 305 | return 306 | } 307 | log.Errorf("[agent] recurse failed, error: %v", err) 308 | } 309 | 310 | // If all resolvers fail, return a SERVFAIL message 311 | log.Errorf( 312 | "[agent] all resolvers failed for question from client, question: %s, client: %s, client_network: %s", 313 | q.String(), resp.RemoteAddr().String(), resp.RemoteAddr().Network()) 314 | } 315 | d.sendDnsCode(resp, req, dns.RcodeServerFailure) 316 | } 317 | 318 | // Size returns if buffer size *advertised* in the requests OPT record. 319 | // Or when the request was over TCP, we return the maximum allowed size of 64K. 320 | func size(proto string, r *dns.Msg) int { 321 | size := uint16(0) 322 | if o := r.IsEdns0(); o != nil { 323 | size = o.UDPSize() 324 | } 325 | 326 | // normalize size 327 | size = ednsSize(proto, size) 328 | return int(size) 329 | } 330 | 331 | // ednsSize returns a normalized size based on proto. 332 | func ednsSize(proto string, size uint16) uint16 { 333 | if proto == "tcp" { 334 | return dns.MaxMsgSize 335 | } 336 | if size < dns.MinMsgSize { 337 | return dns.MinMsgSize 338 | } 339 | return size 340 | } 341 | 342 | func ednsSubnetForRequest(req *dns.Msg) *dns.EDNS0_SUBNET { 343 | // IsEdns0 returns the EDNS RR if present or nil otherwise 344 | edns := req.IsEdns0() 345 | 346 | if edns == nil { 347 | return nil 348 | } 349 | 350 | for _, o := range edns.Option { 351 | if subnet, ok := o.(*dns.EDNS0_SUBNET); ok { 352 | return subnet 353 | } 354 | } 355 | 356 | return nil 357 | } 358 | 359 | // setEDNS is used to set the responses EDNS size headers and 360 | // possibly the ECS headers as well if they were present in the 361 | // original request 362 | func setEDNS(request *dns.Msg, response *dns.Msg, ecsGlobal bool) { 363 | edns := request.IsEdns0() 364 | if edns == nil { 365 | return 366 | } 367 | 368 | // cannot just use the SetEdns0 function as we need to embed 369 | // the ECS option as well 370 | ednsResp := new(dns.OPT) 371 | ednsResp.Hdr.Name = "." 372 | ednsResp.Hdr.Rrtype = dns.TypeOPT 373 | ednsResp.SetUDPSize(edns.UDPSize()) 374 | 375 | // Setup the ECS option if present 376 | if subnet := ednsSubnetForRequest(request); subnet != nil { 377 | subOp := new(dns.EDNS0_SUBNET) 378 | subOp.Code = dns.EDNS0SUBNET 379 | subOp.Family = subnet.Family 380 | subOp.Address = subnet.Address 381 | subOp.SourceNetmask = subnet.SourceNetmask 382 | if c := response.Rcode; ecsGlobal || c == dns.RcodeNameError || c == dns.RcodeServerFailure || 383 | c == dns.RcodeRefused || c == dns.RcodeNotImplemented { 384 | // reply is globally valid and should be cached accordingly 385 | subOp.SourceScope = 0 386 | } else { 387 | // reply is only valid for the subnet it was queried with 388 | subOp.SourceScope = subnet.SourceNetmask 389 | } 390 | ednsResp.Option = append(ednsResp.Option, subOp) 391 | } 392 | 393 | response.Extra = append(response.Extra, ednsResp) 394 | } 395 | -------------------------------------------------------------------------------- /resolver/util.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package resolver 19 | 20 | import ( 21 | "strings" 22 | 23 | "github.com/polarismesh/polaris-go/pkg/config" 24 | "github.com/polarismesh/polaris-go/pkg/model" 25 | ) 26 | 27 | const ( 28 | Quota = "." 29 | sysNamespace = "polaris" 30 | ) 31 | 32 | var ( 33 | ContextProtocol = struct{}{} 34 | ) 35 | 36 | // ParseQname parse the qname into service and suffix 37 | // qname format: .. 38 | func ParseQname(qname string, suffix string, currentNs string) *model.ServiceKey { 39 | var matched bool 40 | qname, matched = MatchSuffix(qname, suffix) 41 | if !matched { 42 | return nil 43 | } 44 | if strings.HasSuffix(qname, Quota) { 45 | qname = qname[0 : len(qname)-1] 46 | } 47 | var namespace string 48 | var serviceName string 49 | sepIndex := strings.LastIndex(qname, Quota) 50 | if sepIndex < 0 { 51 | namespace = currentNs 52 | serviceName = qname 53 | } else { 54 | namespace = qname[sepIndex+1:] 55 | if strings.ToLower(namespace) == sysNamespace { 56 | namespace = config.ServerNamespace 57 | } 58 | serviceName = qname[:sepIndex] 59 | } 60 | return &model.ServiceKey{Namespace: namespace, Service: serviceName} 61 | } 62 | 63 | // MatchSuffix match the suffix and return the split qname 64 | func MatchSuffix(qname string, suffix string) (string, bool) { 65 | if len(suffix) > 0 && !strings.HasSuffix(qname, suffix) { 66 | return qname, false 67 | } 68 | if len(suffix) > 0 { 69 | qname = qname[:len(qname)-len(suffix)] 70 | return qname, true 71 | } 72 | return qname, true 73 | } 74 | -------------------------------------------------------------------------------- /security/mtls/agent/agent.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "os" 7 | "path/filepath" 8 | 9 | "google.golang.org/grpc" 10 | 11 | "github.com/polarismesh/polaris-sidecar/pkg/log" 12 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate/caclient" 13 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate/manager" 14 | "github.com/polarismesh/polaris-sidecar/security/mtls/rotator" 15 | "github.com/polarismesh/polaris-sidecar/security/mtls/sds" 16 | ) 17 | 18 | type Agent struct { 19 | network string 20 | addr string 21 | sds *sds.Server 22 | client manager.CSRClient 23 | certManager manager.Manager 24 | rotator *rotator.Rotator 25 | } 26 | 27 | const defaultCAPath = "/etc/polaris-sidecar/certs/rootca.pem" 28 | 29 | func New(opt Option) (*Agent, error) { 30 | err := opt.init() 31 | if err != nil { 32 | return nil, err 33 | } 34 | a := &Agent{} 35 | a.network = opt.Network 36 | a.addr = opt.Address 37 | a.rotator = rotator.New(opt.RotatePeriod, opt.FailedRetryDelay) 38 | a.sds = sds.New(opt.CryptombPollDelay) 39 | 40 | if opt.Network == "unix" { 41 | if err := os.MkdirAll(filepath.Dir(opt.Address), os.ModePerm); err != nil { 42 | return nil, err 43 | } 44 | } 45 | 46 | cli, err := caclient.NewWithRootCA(opt.CAServer, caclient.ServiceAccountToken(), defaultCAPath) 47 | if err != nil { 48 | return nil, err 49 | } 50 | a.client = cli 51 | 52 | a.certManager = manager.NewManager(opt.Namespace, opt.ServiceAccount, opt.RSAKeyBits, opt.TTL, a.client) 53 | return a, nil 54 | } 55 | 56 | func (a *Agent) Run(ctx context.Context) error { 57 | // start sds grpc service 58 | srv := grpc.NewServer() 59 | a.sds.Serve(srv) 60 | l, err := net.Listen(a.network, a.addr) 61 | if err != nil { 62 | return err 63 | } 64 | go srv.Serve(l) 65 | defer srv.GracefulStop() 66 | log.Info("start rotator") 67 | // start certificate generation rotator 68 | return a.rotator.Run(ctx, func(ctx context.Context) error { 69 | bundle, err := a.certManager.GetBundle(ctx) 70 | if err != nil { 71 | return err 72 | } 73 | a.sds.UpdateSecrets(ctx, *bundle) 74 | return nil 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /security/mtls/agent/option.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type Option struct { 13 | // Network and Address are used to created a listener which sds will serve on. 14 | // See also: https://pkg.go.dev/net#Listen 15 | // Default Network: "unix" 16 | Network string 17 | 18 | // Default Address: "/var/run/polaris/mtls/sds.sock" 19 | Address string 20 | 21 | // CryptombPollDelay is a parameter of cryptomb 22 | // See also: https://www.envoyproxy.io/docs/envoy/v1.22.2/api-v3/extensions/private_key_providers/cryptomb/v3alpha/cryptomb.proto.html 23 | // Default is 0.2 ms. 24 | CryptombPollDelay time.Duration 25 | 26 | // RotatePeriod is the period of the rotation. 27 | // Default is 30 minute. 28 | RotatePeriod time.Duration 29 | 30 | // FailedRetryDelay It defines the retry interval when the rotation action failed. 31 | // Default is 1 second. 32 | FailedRetryDelay time.Duration 33 | 34 | // Namespace is the current namespace. 35 | Namespace string 36 | 37 | // ServiceAccount is the current ServiceAccount name. 38 | // See also: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ 39 | ServiceAccount string 40 | 41 | // CAServer is the address of the CA server. 42 | CAServer string 43 | 44 | // RSAKeyBits is the bit size of the RSA key. 45 | RSAKeyBits int 46 | 47 | // TTL of the certificate 48 | // Default is 1 hour. 49 | TTL time.Duration 50 | } 51 | 52 | func EnvDefaultDuration(name string, val time.Duration, def time.Duration) time.Duration { 53 | if val != 0 { 54 | return val 55 | } 56 | if d, err := time.ParseDuration(os.Getenv(name)); err == nil { 57 | return d 58 | } 59 | return def 60 | } 61 | 62 | func EnvDefaultInt(name string, val int, def int) int { 63 | if val != 0 { 64 | return val 65 | } 66 | if d, err := strconv.Atoi(os.Getenv(name)); err == nil { 67 | return d 68 | } 69 | return def 70 | } 71 | 72 | const DefaultSDSAddress = "/tmp/polaris-sidecar/mtls/sds.sock" 73 | 74 | // init options with enviroment variables 75 | func (opt *Option) init() error { 76 | if opt.Network == "" { 77 | opt.Network = "unix" 78 | } 79 | if opt.Address == "" { 80 | opt.Address = DefaultSDSAddress 81 | } 82 | 83 | opt.CryptombPollDelay = EnvDefaultDuration("POLARIS_SIDECAR_MTLS_CRYPTO_MB_POLL_DELAY", 84 | opt.CryptombPollDelay, 85 | time.Millisecond) 86 | 87 | opt.RotatePeriod = EnvDefaultDuration("POLARIS_SIDECAR_MTLS_ROTATE_PERIOD", 88 | opt.RotatePeriod, 89 | time.Hour) 90 | 91 | opt.FailedRetryDelay = EnvDefaultDuration("POLARIS_SIDECAR_MTLS_ROTATE_FAILED_RETRY_DELAY", 92 | opt.FailedRetryDelay, 93 | time.Second) 94 | 95 | if opt.Namespace == "" || opt.ServiceAccount == "" { 96 | if envNS := os.Getenv("KUBERNETES_NAMESPACE"); envNS != "" { 97 | opt.Namespace = envNS 98 | } 99 | if envSA := os.Getenv("KUBERNETES_SERVICE_ACCOUNT"); envSA != "" { 100 | opt.ServiceAccount = envSA 101 | } 102 | if opt.Namespace == "" || opt.ServiceAccount == "" { 103 | sa, err := loadServiceAccount() 104 | if err != nil { 105 | return fmt.Errorf("cannot detect which namespace and serviceaccount being used :%w", err) 106 | } 107 | opt.Namespace = sa.Namespace 108 | opt.ServiceAccount = sa.AccountName 109 | } 110 | } 111 | if opt.CAServer == "" { 112 | ca := os.Getenv("POLARIS_SIDECAR_MTLS_CA_SERVER") 113 | if ca == "" { 114 | return errors.New("no ca server endpoint provided") 115 | } 116 | opt.CAServer = ca 117 | } 118 | 119 | lca := strings.ToLower(opt.CAServer) 120 | 121 | if !strings.HasPrefix(lca, "http://") && !strings.HasPrefix(lca, "https://") { 122 | // add scheme to endpoint 123 | opt.CAServer = "http://" + opt.CAServer 124 | } 125 | 126 | opt.RSAKeyBits = EnvDefaultInt("POLARIS_SIDECAR_MTLS_KEY_BITS", 127 | opt.RSAKeyBits, 2048) 128 | 129 | opt.TTL = EnvDefaultDuration("POLARIS_SIDECAR_MTLS_CERT_TTL", 130 | opt.TTL, time.Hour) 131 | 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /security/mtls/agent/serviceaccount.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type ServiceAccount struct { 13 | Namespace string `json:"kubernetes.io/serviceaccount/namespace"` 14 | AccountName string `json:"kubernetes.io/serviceaccount/service-account.name"` 15 | } 16 | 17 | const DefaultJWTPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" 18 | 19 | // loadServiceAccount load service account information from token 20 | func loadServiceAccount() (*ServiceAccount, error) { 21 | tokenFile := DefaultJWTPath 22 | if _, err := os.Stat(tokenFile); err != nil { 23 | return nil, errors.New("no service account token file") 24 | } 25 | data, err := os.ReadFile(tokenFile) 26 | if err != nil { 27 | return nil, fmt.Errorf("cannot read service account token: %w", err) 28 | } 29 | parts := strings.Split(string(data), ".") 30 | if len(parts) != 3 { 31 | return nil, fmt.Errorf("unknown token format") 32 | } 33 | jsonStr, err := base64.RawURLEncoding.DecodeString(parts[1]) 34 | if err != nil { 35 | return nil, fmt.Errorf("unknown token base64 format") 36 | } 37 | sa := &ServiceAccount{} 38 | err = json.Unmarshal(jsonStr, sa) 39 | if err != nil { 40 | return nil, fmt.Errorf("unknown token payload json format") 41 | } 42 | return sa, nil 43 | } 44 | -------------------------------------------------------------------------------- /security/mtls/certificate/bundle.go: -------------------------------------------------------------------------------- 1 | package certificate 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/x509" 6 | "crypto/x509/pkix" 7 | "encoding/pem" 8 | "fmt" 9 | "net/url" 10 | ) 11 | 12 | type Bundle struct { 13 | ROOTCA []byte 14 | CertChain []byte 15 | PrivKey []byte 16 | } 17 | 18 | const ( 19 | SchemeSPIFFE = "spiffe" 20 | TrustDomain = "cluster.local" 21 | ) 22 | 23 | func GenerateCSR(ns string, sa string, priv interface{}) (csr []byte, err error) { 24 | // certificate must meet the SPIFFE document: https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md 25 | tpl := &x509.CertificateRequest{} 26 | tpl.Subject = pkix.Name{ 27 | Organization: []string{TrustDomain}, 28 | } 29 | tpl.URIs = append(tpl.URIs, &url.URL{ 30 | Scheme: SchemeSPIFFE, 31 | Host: TrustDomain, 32 | Path: fmt.Sprintf("/ns/%s/sa/%s", ns, sa), 33 | }) 34 | 35 | csr, err = x509.CreateCertificateRequest(rand.Reader, tpl, priv) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | csr = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr}) 41 | return csr, nil 42 | } 43 | -------------------------------------------------------------------------------- /security/mtls/certificate/caclient/client.go: -------------------------------------------------------------------------------- 1 | package caclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/json" 9 | "errors" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type Client struct { 18 | client *http.Client 19 | token string 20 | endpoint string 21 | } 22 | 23 | func NewCAClient(endpoint string) (*Client, error) { 24 | return New(endpoint, "", nil) 25 | } 26 | 27 | func NewWithRootCA(endpoint string, token string, rootcaFile string) (*Client, error) { 28 | certPEMBlock, err := os.ReadFile(rootcaFile) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | pool, err := x509.SystemCertPool() 34 | if err != nil { 35 | return nil, err 36 | } 37 | pool.AppendCertsFromPEM(certPEMBlock) 38 | cli := &http.Client{Transport: &http.Transport{ 39 | TLSClientConfig: &tls.Config{ 40 | RootCAs: pool, 41 | }, 42 | }} 43 | 44 | return New(endpoint, token, cli) 45 | } 46 | 47 | func New(endpoint string, token string, client *http.Client) (*Client, error) { 48 | u, err := url.Parse(endpoint) // must be a valid endpoint 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | if sc := strings.ToUpper(u.Scheme); sc != "HTTP" && sc != "HTTPS" { 54 | return nil, errors.New("unsupported endpoint scheme") 55 | } 56 | 57 | if token == "" { 58 | sat := ServiceAccountToken() 59 | if sat != "" { 60 | token = sat 61 | } 62 | } 63 | if client == nil { 64 | client = http.DefaultClient 65 | } 66 | return &Client{ 67 | token: token, 68 | endpoint: endpoint, 69 | client: client, 70 | }, nil 71 | } 72 | 73 | type CreateCertificateRequest struct { 74 | CSR string `json:"csr"` 75 | TTL int64 `json:"ttl"` 76 | } 77 | 78 | func (c *CreateCertificateRequest) Payload() *bytes.Buffer { 79 | buf := bytes.NewBuffer(nil) 80 | enc := json.NewEncoder(buf) 81 | enc.Encode(c) 82 | return buf 83 | } 84 | 85 | func (c *Client) CreateCertificate(ctx context.Context, csr []byte, ttl time.Duration) (certChanPem string, rootca string, err error) { 86 | req, err := http.NewRequest("POST", 87 | c.endpoint+"/security/v1/sign_certificate", 88 | (&CreateCertificateRequest{ 89 | CSR: string(csr), 90 | TTL: ttl.Milliseconds() / 1000, 91 | }).Payload()) 92 | if err != nil { 93 | return "", "", err 94 | } 95 | // oauth2 token style 96 | req.Header.Set("Authorization", "Bearer "+c.token) 97 | resp, err := c.client.Do(req) 98 | if err != nil { 99 | return "", "", err 100 | } 101 | dec := json.NewDecoder(resp.Body) 102 | ccr := &CreateCertificateResponse{} 103 | 104 | err = dec.Decode(ccr) 105 | if err != nil { 106 | return "", "", err 107 | } 108 | if resp.StatusCode != http.StatusOK { 109 | return "", "", errors.New(ccr.Message) 110 | } 111 | return ccr.CertChain, ccr.RootCert, nil 112 | } 113 | 114 | type CreateCertificateResponse struct { 115 | CertChain string `json:"cert_chain"` 116 | RootCert string `json:"root_cert"` 117 | // if signing failed , error message will be wrapped in `Message` 118 | Message string `json:"msg"` 119 | } 120 | -------------------------------------------------------------------------------- /security/mtls/certificate/caclient/client_test.go: -------------------------------------------------------------------------------- 1 | package caclient 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 12 | ) 13 | 14 | func TestCreateCertificate(t *testing.T) { 15 | rootca := os.Getenv("TEST_CREATE_CERT_ROOTCA") 16 | sat := os.Getenv("TEST_CREATE_CERT_SAT") 17 | certEndpoint := os.Getenv("TEST_CREATE_CERT_ENDPOINT") 18 | 19 | if rootca == "" || sat == "" || certEndpoint == "" { 20 | t.Skip() 21 | return 22 | } 23 | cli, err := NewWithRootCA(certEndpoint, sat, rootca) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | priv, _ := rsa.GenerateKey(rand.Reader, 2048) 28 | 29 | csr, _ := certificate.GenerateCSR("default", "default", priv) 30 | chain, root, err := cli.CreateCertificate(context.TODO(), csr, time.Second) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | t.Log("cert chain:", chain) 35 | t.Log("root ca", root) 36 | } 37 | -------------------------------------------------------------------------------- /security/mtls/certificate/caclient/sat.go: -------------------------------------------------------------------------------- 1 | package caclient 2 | 3 | import "io/ioutil" 4 | 5 | const SATLocation = "/var/run/secrets/kubernetes.io/serviceaccount/token" 6 | 7 | func ServiceAccountToken() string { 8 | token, _ := ioutil.ReadFile(SATLocation) 9 | return string(token) 10 | } 11 | -------------------------------------------------------------------------------- /security/mtls/certificate/manager/interface.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 8 | ) 9 | 10 | type Manager interface { 11 | GetBundle(ctx context.Context) (bundle *certificate.Bundle, err error) 12 | } 13 | 14 | type CSRClient interface { 15 | CreateCertificate(ctx context.Context, csr []byte, ttl time.Duration) (certChanPem string, rootca string, err error) 16 | } 17 | -------------------------------------------------------------------------------- /security/mtls/certificate/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "time" 10 | 11 | "github.com/polarismesh/polaris-sidecar/pkg/log" 12 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 13 | ) 14 | 15 | type manager struct { 16 | namespace string 17 | serviceAccount string 18 | bits int 19 | ttl time.Duration 20 | cli CSRClient 21 | } 22 | 23 | func NewManager(namespace string, 24 | serviceAccount string, 25 | bits int, 26 | ttl time.Duration, 27 | cli CSRClient, 28 | ) Manager { 29 | return &manager{ 30 | namespace: namespace, 31 | serviceAccount: serviceAccount, 32 | bits: bits, 33 | ttl: ttl, 34 | cli: cli, 35 | } 36 | } 37 | 38 | const ( 39 | PemCertificateType = "CERTIFICATE" 40 | PemPrivateKeyType = "PRIVATE KEY" 41 | PemRSAPrivateKeyType = "RSA PRIVATE KEY" 42 | ) 43 | 44 | const ( 45 | SchemeSPIFFE = "spiffe" 46 | TrustDomain = "cluster.local" 47 | ) 48 | 49 | func (m *manager) GetBundle(ctx context.Context) (*certificate.Bundle, error) { 50 | priv, err := rsa.GenerateKey(rand.Reader, m.bits) 51 | if err != nil { 52 | log.Errorf("rsa generate key failed: %s", err) 53 | return nil, err 54 | } 55 | // generate CSR using spiffe style 56 | csr, err := certificate.GenerateCSR(m.namespace, m.serviceAccount, priv) 57 | if err != nil { 58 | return nil, err 59 | } 60 | log.Infof("generate CSR for %s/%s", m.namespace, m.serviceAccount) 61 | 62 | chain, rootCA, err := m.cli.CreateCertificate(ctx, csr, m.ttl) 63 | if err != nil { 64 | log.Errorf("signed certificate failed: %s", err) 65 | return nil, err 66 | } 67 | log.Infof("signed certificate for %s/%s", m.namespace, m.serviceAccount) 68 | 69 | // the last cert in the chain must be the rootCA 70 | 71 | // rsa key should always got a success result 72 | PKCS8Priv, _ := x509.MarshalPKCS8PrivateKey(priv) 73 | 74 | return &certificate.Bundle{ 75 | ROOTCA: []byte(rootCA), 76 | CertChain: []byte(chain), 77 | PrivKey: pem.EncodeToMemory(&pem.Block{Type: PemPrivateKeyType, Bytes: PKCS8Priv}), 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /security/mtls/rotator/rotate.go: -------------------------------------------------------------------------------- 1 | package rotator 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/polarismesh/polaris-sidecar/pkg/log" 9 | ) 10 | 11 | type Rotator struct { 12 | once sync.Once 13 | period time.Duration 14 | retryDelay time.Duration 15 | } 16 | 17 | func New(period time.Duration, retryDelay time.Duration) *Rotator { 18 | return &Rotator{ 19 | period: period, 20 | retryDelay: retryDelay, 21 | } 22 | } 23 | 24 | func (r *Rotator) init() { 25 | if r.period == 0 { 26 | r.period = time.Minute * 30 27 | } 28 | 29 | if r.retryDelay == 0 { 30 | r.retryDelay = time.Second 31 | } 32 | } 33 | 34 | func (r *Rotator) execute(ctx context.Context, f func(ctx context.Context) error) { 35 | for { 36 | log.Infof("will execute by rotator") 37 | err := f(ctx) 38 | if err == nil { 39 | break 40 | } 41 | if ctx.Err() != nil { 42 | return 43 | } 44 | log.Errorf("action executed failed: %s", err.Error()) 45 | time.Sleep(r.retryDelay) 46 | } 47 | } 48 | 49 | func (r *Rotator) Run(ctx context.Context, f func(ctx context.Context) error) error { 50 | r.once.Do(r.init) 51 | r.execute(ctx, f) 52 | for { 53 | select { 54 | case <-ctx.Done(): 55 | return ctx.Err() 56 | case <-time.After(r.period): 57 | } 58 | 59 | r.execute(ctx, f) 60 | 61 | if ctx.Err() != nil { 62 | return ctx.Err() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /security/mtls/sds/secret_amd64.go: -------------------------------------------------------------------------------- 1 | //go:build amd64 2 | 3 | package sds 4 | 5 | import ( 6 | "time" 7 | 8 | cryptomb "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha" 9 | core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 10 | envoytls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 11 | "github.com/envoyproxy/go-control-plane/pkg/cache/types" 12 | "github.com/intel-go/cpuid" 13 | "google.golang.org/protobuf/types/known/anypb" 14 | "google.golang.org/protobuf/types/known/durationpb" 15 | 16 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 17 | ) 18 | 19 | // cryptombSupported indicate that whether the cpu can use crypto_mb library. 20 | // The crypto_mb library can accelerate tls in envoy by using AVX512 instructions. 21 | // So we should check the CPUID here. 22 | // See references: 23 | // 1. https://github.com/intel/ipp-crypto/blob/46944bd18e6dbad491ef9b9a3404303ef7680c09/sources/ippcp/crypto_mb/src/common/cpu_features.c#L227 24 | // 2. https://github.com/intel-go/cpuid/ 25 | var cryptombSupported = cpuid.EnabledAVX512 && 26 | cpuid.HasExtendedFeature(cpuid.BMI2) && 27 | cpuid.HasExtendedFeature(cpuid.AVX512F) && 28 | cpuid.HasExtendedFeature(cpuid.AVX512DQ) && 29 | cpuid.HasExtendedFeature(cpuid.AVX512BW) && 30 | cpuid.HasExtendedFeature(cpuid.AVX512IFMA) && 31 | cpuid.HasExtendedFeature(cpuid.AVX512VBMI2) 32 | 33 | // makeSecrets make all secrets which should be pushed to envoy. 34 | // For now, just ROOTCA & default. 35 | func (s *Server) makeSecrets(bundle certificate.Bundle) []types.Resource { 36 | results := []types.Resource{} 37 | 38 | rootCA := s.makeCASecret("ROOTCA", bundle.ROOTCA) 39 | def := s.makeSecret("default", bundle.PrivKey, bundle.CertChain, s.cryptombPollDelay) 40 | results = append(results, rootCA, def) 41 | return results 42 | } 43 | 44 | // makeSecret make secret object with the specified name. 45 | // key and cryptombPollDelay are optional parameters. 46 | // If key and cryptombPollDelay are provided, and the `cryptombSupported` is true, 47 | // secret will use the cryptomb PrivateKeyProvider. 48 | // See also https://www.envoyproxy.io/docs/envoy/v1.22.2/api-v3/extensions/private_key_providers/cryptomb/v3alpha/cryptomb.proto.html 49 | func (s *Server) makeSecret(name string, key, cert []byte, cryptombPollDelay time.Duration) *envoytls.Secret { 50 | tlsCert := &envoytls.Secret_TlsCertificate{ 51 | TlsCertificate: &envoytls.TlsCertificate{ 52 | CertificateChain: &core.DataSource{ 53 | Specifier: &core.DataSource_InlineBytes{ 54 | InlineBytes: cert, 55 | }, 56 | }, 57 | }, 58 | } 59 | 60 | if key != nil { 61 | if cryptombSupported && cryptombPollDelay != 0 { 62 | cpc := &cryptomb.CryptoMbPrivateKeyMethodConfig{ 63 | PollDelay: durationpb.New(cryptombPollDelay), 64 | PrivateKey: &core.DataSource{ 65 | Specifier: &core.DataSource_InlineBytes{ 66 | InlineBytes: key, 67 | }, 68 | }, 69 | } 70 | msg, _ := anypb.New(cpc) 71 | tlsCert.TlsCertificate.PrivateKeyProvider = &envoytls.PrivateKeyProvider{ 72 | ProviderName: "cryptomb", 73 | ConfigType: &envoytls.PrivateKeyProvider_TypedConfig{ 74 | TypedConfig: msg, 75 | }, 76 | } 77 | } else { 78 | tlsCert.TlsCertificate.PrivateKey = &core.DataSource{ 79 | Specifier: &core.DataSource_InlineBytes{ 80 | InlineBytes: key, 81 | }, 82 | } 83 | } 84 | } 85 | return &envoytls.Secret{ 86 | Name: name, 87 | Type: tlsCert, 88 | } 89 | } 90 | 91 | func (s *Server) makeCASecret(name string, ca []byte) *envoytls.Secret { 92 | return &envoytls.Secret{ 93 | Name: name, 94 | Type: &envoytls.Secret_ValidationContext{ 95 | ValidationContext: &envoytls.CertificateValidationContext{ 96 | TrustedCa: &core.DataSource{ 97 | Specifier: &core.DataSource_InlineBytes{ 98 | InlineBytes: ca, 99 | }, 100 | }, 101 | }, 102 | }, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /security/mtls/sds/secret_arm64.go: -------------------------------------------------------------------------------- 1 | //go:build arm64 2 | 3 | package sds 4 | 5 | import ( 6 | "time" 7 | 8 | cryptomb "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha" 9 | core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 10 | envoytls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 11 | "github.com/envoyproxy/go-control-plane/pkg/cache/types" 12 | "google.golang.org/protobuf/types/known/anypb" 13 | "google.golang.org/protobuf/types/known/durationpb" 14 | 15 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 16 | ) 17 | 18 | // cryptombSupported indicate that whether the cpu can use crypto_mb library. 19 | // The crypto_mb library can accelerate tls in envoy by using AVX512 instructions. 20 | // So we should check the CPUID here. 21 | // See references: 22 | // 1. https://github.com/intel/ipp-crypto/blob/46944bd18e6dbad491ef9b9a3404303ef7680c09/sources/ippcp/crypto_mb/src/common/cpu_features.c#L227 23 | // 2. https://github.com/intel-go/cpuid/ 24 | var cryptombSupported = false 25 | 26 | // makeSecrets make all secrets which should be pushed to envoy. 27 | // For now, just ROOTCA & default. 28 | func (s *Server) makeSecrets(bundle certificate.Bundle) []types.Resource { 29 | results := []types.Resource{} 30 | 31 | rootCA := s.makeCASecret("ROOTCA", bundle.ROOTCA) 32 | def := s.makeSecret("default", bundle.PrivKey, bundle.CertChain, s.cryptombPollDelay) 33 | results = append(results, rootCA, def) 34 | return results 35 | } 36 | 37 | // makeSecret make secret object with the specified name. 38 | // key and cryptombPollDelay are optional parameters. 39 | // If key and cryptombPollDelay are provided, and the `cryptombSupported` is true, 40 | // secret will use the cryptomb PrivateKeyProvider. 41 | // See also https://www.envoyproxy.io/docs/envoy/v1.22.2/api-v3/extensions/private_key_providers/cryptomb/v3alpha/cryptomb.proto.html 42 | func (s *Server) makeSecret(name string, key, cert []byte, cryptombPollDelay time.Duration) *envoytls.Secret { 43 | tlsCert := &envoytls.Secret_TlsCertificate{ 44 | TlsCertificate: &envoytls.TlsCertificate{ 45 | CertificateChain: &core.DataSource{ 46 | Specifier: &core.DataSource_InlineBytes{ 47 | InlineBytes: cert, 48 | }, 49 | }, 50 | }, 51 | } 52 | 53 | if key != nil { 54 | if cryptombSupported && cryptombPollDelay != 0 { 55 | cpc := &cryptomb.CryptoMbPrivateKeyMethodConfig{ 56 | PollDelay: durationpb.New(cryptombPollDelay), 57 | PrivateKey: &core.DataSource{ 58 | Specifier: &core.DataSource_InlineBytes{ 59 | InlineBytes: key, 60 | }, 61 | }, 62 | } 63 | msg, _ := anypb.New(cpc) 64 | tlsCert.TlsCertificate.PrivateKeyProvider = &envoytls.PrivateKeyProvider{ 65 | ProviderName: "cryptomb", 66 | ConfigType: &envoytls.PrivateKeyProvider_TypedConfig{ 67 | TypedConfig: msg, 68 | }, 69 | } 70 | } else { 71 | tlsCert.TlsCertificate.PrivateKey = &core.DataSource{ 72 | Specifier: &core.DataSource_InlineBytes{ 73 | InlineBytes: key, 74 | }, 75 | } 76 | } 77 | } 78 | return &envoytls.Secret{ 79 | Name: name, 80 | Type: tlsCert, 81 | } 82 | } 83 | 84 | func (s *Server) makeCASecret(name string, ca []byte) *envoytls.Secret { 85 | return &envoytls.Secret{ 86 | Name: name, 87 | Type: &envoytls.Secret_ValidationContext{ 88 | ValidationContext: &envoytls.CertificateValidationContext{ 89 | TrustedCa: &core.DataSource{ 90 | Specifier: &core.DataSource_InlineBytes{ 91 | InlineBytes: ca, 92 | }, 93 | }, 94 | }, 95 | }, 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /security/mtls/sds/server.go: -------------------------------------------------------------------------------- 1 | package sds 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 9 | secretv3 "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" 10 | "github.com/envoyproxy/go-control-plane/pkg/cache/types" 11 | cache "github.com/envoyproxy/go-control-plane/pkg/cache/v3" 12 | "github.com/envoyproxy/go-control-plane/pkg/resource/v3" 13 | serverv3 "github.com/envoyproxy/go-control-plane/pkg/server/v3" 14 | "google.golang.org/grpc" 15 | 16 | "github.com/polarismesh/polaris-sidecar/pkg/log" 17 | "github.com/polarismesh/polaris-sidecar/security/mtls/certificate" 18 | ) 19 | 20 | type Server struct { 21 | srv serverv3.Server 22 | snap cache.SnapshotCache 23 | cryptombPollDelay time.Duration 24 | } 25 | 26 | func New(cryptombPollDelay time.Duration) (s *Server) { 27 | s = &Server{} 28 | s.cryptombPollDelay = cryptombPollDelay 29 | 30 | snap := cache.NewSnapshotCache(false, defaultHash, nil) 31 | srv := serverv3.NewServer(context.TODO(), snap, nil) 32 | s.srv = srv 33 | s.snap = snap 34 | 35 | return s 36 | } 37 | 38 | func (s *Server) UpdateSecrets(ctx context.Context, bundle certificate.Bundle) { 39 | version := strconv.Itoa(int(time.Now().UnixNano())) 40 | 41 | log.Infof("update secrets: %s", version) 42 | 43 | resources := make(map[resource.Type][]types.Resource) 44 | resources[resource.SecretType] = s.makeSecrets(bundle) 45 | snapshot, _ := cache.NewSnapshot(version, resources) 46 | s.snap.SetSnapshot(ctx, string(defaultHash), snapshot) 47 | } 48 | 49 | func (s *Server) Serve(srv *grpc.Server) { 50 | secretv3.RegisterSecretDiscoveryServiceServer(srv, s.srv) 51 | } 52 | 53 | var defaultHash = ConstHash("default") 54 | 55 | // ConstHash uses a const string as the node hash. 56 | type ConstHash string 57 | 58 | // ID uses the string 59 | func (h ConstHash) ID(node *core.Node) string { 60 | return string(h) 61 | } 62 | 63 | var _ cache.NodeHash = ConstHash("") 64 | -------------------------------------------------------------------------------- /tool/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curpath=$(pwd) 4 | 5 | if [ "${0:0:1}" == "/" ]; then 6 | dir=$(dirname "$0") 7 | else 8 | dir=$(pwd)/$(dirname "$0") 9 | fi 10 | 11 | cd $dir/.. 12 | workdir=$(pwd) 13 | 14 | #------------------------------------------------------ 15 | source tool/include 16 | 17 | pids=$(ps -ef | grep -w "$cmdline" | grep -v "grep" | awk '{print $2}') 18 | array=($pids) 19 | if [ "${#array[@]}" == "0" ]; then 20 | start 21 | fi 22 | 23 | #------------------------------------------------------ 24 | 25 | cd $curpath 26 | -------------------------------------------------------------------------------- /tool/include: -------------------------------------------------------------------------------- 1 | server_name="polaris-sidecar" 2 | cmdline="./polaris-sidecar start" 3 | 4 | function log_date() { 5 | echo $(date "+%Y-%m-%dT%H:%M:%S") 6 | } 7 | 8 | function log_error() { 9 | echo -e "\033[31m\033[01m$(log_date)\terror\t$1 \033[0m" 10 | } 11 | 12 | function log_info() { 13 | echo -e "\033[32m\033[01m$(log_date)\tinfo\t$1 \033[0m" 14 | } 15 | 16 | function del_file() { 17 | log_info "del file for $server_name" 18 | 19 | rm -rf ./log 20 | } 21 | 22 | function start() { 23 | log_info "start $server_name" 24 | 25 | if [ ! -d "./log" ]; then 26 | mkdir ./log 27 | fi 28 | 29 | set +e 30 | ulimit -n 409600 31 | set -e 32 | nohup $cmdline >>./log/stdout 2>&1 & 33 | } 34 | 35 | function stop() { 36 | pids=$(ps -ef | grep -w "$cmdline" | grep -v "grep" | awk '{print $2}') 37 | array=($pids) 38 | for pid in ${array[@]}; do 39 | log_info "stop $server_name: pid=$pid" 40 | 41 | kill -15 $pid 42 | done 43 | } 44 | 45 | function reload() { 46 | pids=$(ps -ef | grep -w "$cmdline" | grep -v "grep" | awk '{print $2}') 47 | array=($pids) 48 | for pid in ${array[@]}; do 49 | log_info "reload $server_name: pid=$pid" 50 | 51 | kill -10 $pid 52 | done 53 | } 54 | 55 | function add_cron() { 56 | set +e 57 | item="$workdir/tool/check.sh >>$workdir/log/check.log 2>&1" 58 | exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) 59 | if [ "$exist" == "0" ]; then 60 | log_info "add cron for $server_name" 61 | 62 | cron=$(mktemp) 63 | crontab -l > $cron 64 | echo "*/1 * * * * $item" >> $cron 65 | crontab $cron 66 | rm -f $cron 67 | fi 68 | set -e 69 | } 70 | 71 | function del_cron() { 72 | set +e 73 | item="$workdir/tool/check.sh >>$workdir/log/check.log 2>&1" 74 | exist=$(crontab -l | grep "$item" | grep -v "#" | wc -l) 75 | if [ "$exist" != "0" ]; then 76 | log_info "del cron for $server_name" 77 | 78 | cron=$(mktemp) 79 | crontab -l | grep -v "$item" > $cron 80 | crontab $cron 81 | rm -f $cron 82 | fi 83 | set -e 84 | } 85 | -------------------------------------------------------------------------------- /tool/p.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Tencent is pleased to support the open source community by making Polaris available. 4 | rem Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | rem Licensed under the BSD 3-Clause License (the "License"); 6 | rem you may not use this file except in compliance with the License. 7 | rem You may obtain a copy of the License at 8 | rem 9 | rem https://opensource.org/licenses/BSD-3-Clause 10 | rem 11 | rem Unless required by applicable law or agreed to in writing, software distributed 12 | rem under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | rem CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | rem specific language governing permissions and limitations under the License. 15 | 16 | set "SERVER_NAME=polaris-sidecar.exe" 17 | tasklist | findstr "%SERVER_NAME%" -------------------------------------------------------------------------------- /tool/p.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curpath=$(pwd) 4 | 5 | if [ "${0:0:1}" == "/" ]; then 6 | dir=$(dirname "$0") 7 | else 8 | dir=$(pwd)/$(dirname "$0") 9 | fi 10 | 11 | cd $dir/.. 12 | workdir=$(pwd) 13 | 14 | #------------------------------------------------------ 15 | source tool/include 16 | 17 | ps -ef | grep -w "$cmdline" | grep -v "grep" 18 | 19 | #------------------------------------------------------ 20 | 21 | cd $curpath 22 | -------------------------------------------------------------------------------- /tool/reload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curpath=$(pwd) 4 | 5 | if [ "${0:0:1}" == "/" ]; then 6 | dir=$(dirname "$0") 7 | else 8 | dir=$(pwd)/$(dirname "$0") 9 | fi 10 | 11 | cd $dir/.. 12 | workdir=$(pwd) 13 | 14 | #------------------------------------------------------ 15 | source tool/include 16 | 17 | reload 18 | 19 | #------------------------------------------------------ 20 | 21 | cd $curpath 22 | 23 | -------------------------------------------------------------------------------- /tool/start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Tencent is pleased to support the open source community by making Polaris available. 4 | rem Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | rem Licensed under the BSD 3-Clause License (the "License"); 6 | rem you may not use this file except in compliance with the License. 7 | rem You may obtain a copy of the License at 8 | rem 9 | rem https://opensource.org/licenses/BSD-3-Clause 10 | rem 11 | rem Unless required by applicable law or agreed to in writing, software distributed 12 | rem under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | rem CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | rem specific language governing permissions and limitations under the License. 15 | 16 | setlocal 17 | 18 | rem Guess POLARIS_HOME if not defined 19 | set "SERVER_NAME=polaris-sidecar.exe" 20 | set "CURRENT_DIR=%cd%" 21 | if not "%POLARIS_HOME%" == "" goto gotHome 22 | set "POLARIS_HOME=%CURRENT_DIR%" 23 | if exist "%POLARIS_HOME%\%SERVER_NAME%" goto okHome 24 | cd .. 25 | set "POLARIS_HOME=%cd%" 26 | cd "%CURRENT_DIR%" 27 | :gotHome 28 | if exist "%POLARIS_HOME%\%SERVER_NAME%" goto okHome 29 | echo The POLARIS_HOME environment variable is not defined correctly 30 | echo This environment variable is needed to run this program 31 | goto end 32 | :okHome 33 | 34 | set "EXECUTABLE=%POLARIS_HOME%\%SERVER_NAME%" 35 | 36 | rem Check that target executable exists 37 | if exist "%EXECUTABLE%" goto okExec 38 | echo Cannot find "%EXECUTABLE%" 39 | echo This file is needed to run this program 40 | goto end 41 | :okExec 42 | 43 | echo "start %EXECUTABLE%" 44 | 45 | set/p SERVER_PROCESS=tasklist | findstr "%SERVER_NAME%" 46 | if not "%SERVER_PROCESS%" equ "" ( 47 | echo "%SERVER_PROCESS% started" 48 | goto end 49 | ) 50 | 51 | pushd %POLARIS_HOME% 52 | start "" "%EXECUTABLE%" start 53 | popd 54 | goto end 55 | 56 | :end -------------------------------------------------------------------------------- /tool/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curpath=$(pwd) 4 | 5 | if [ "${0:0:1}" == "/" ]; then 6 | dir=$(dirname "$0") 7 | else 8 | dir=$(pwd)/$(dirname "$0") 9 | fi 10 | 11 | cd $dir/.. 12 | workdir=$(pwd) 13 | 14 | #------------------------------------------------------ 15 | source tool/include 16 | 17 | pids=$(ps -ef | grep -w "$cmdline" | grep -v "grep" | awk '{print $2}') 18 | array=($pids) 19 | if [ "${#array[@]}" == "0" ]; then 20 | start 21 | fi 22 | add_cron 23 | 24 | #------------------------------------------------------ 25 | 26 | cd $curpath 27 | -------------------------------------------------------------------------------- /tool/stop.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem Tencent is pleased to support the open source community by making Polaris available. 4 | rem Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | rem Licensed under the BSD 3-Clause License (the "License"); 6 | rem you may not use this file except in compliance with the License. 7 | rem You may obtain a copy of the License at 8 | rem 9 | rem https://opensource.org/licenses/BSD-3-Clause 10 | rem 11 | rem Unless required by applicable law or agreed to in writing, software distributed 12 | rem under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 13 | rem CONDITIONS OF ANY KIND, either express or implied. See the License for the 14 | rem specific language governing permissions and limitations under the License. 15 | 16 | setlocal 17 | 18 | set "SERVER_NAME=polaris-sidecar.exe" 19 | 20 | for /F "TOKENS=1,2,*" %%a in ('tasklist ^| findstr "%SERVER_NAME%"') do ( 21 | echo "process is %%b" 22 | if not "%%b" equ "" ( 23 | echo "start to kill %%b" 24 | taskkill /PID %%b /T /F 25 | ) 26 | ) 27 | 28 | endlocal 29 | 30 | -------------------------------------------------------------------------------- /tool/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curpath=$(pwd) 4 | 5 | if [ "${0:0:1}" == "/" ]; then 6 | dir=$(dirname "$0") 7 | else 8 | dir=$(pwd)/$(dirname "$0") 9 | fi 10 | 11 | cd $dir/.. 12 | workdir=$(pwd) 13 | 14 | #------------------------------------------------------ 15 | source tool/include 16 | 17 | del_cron 18 | stop 19 | 20 | #------------------------------------------------------ 21 | 22 | cd $curpath 23 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making Polaris available. 3 | * 4 | * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * 6 | * Licensed under the BSD 3-Clause License (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://opensource.org/licenses/BSD-3-Clause 11 | * 12 | * Unless required by applicable law or agreed to in writing, software distributed 13 | * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 14 | * CONDITIONS OF ANY KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations under the License. 16 | */ 17 | 18 | package version 19 | 20 | var ( 21 | // Version version 22 | Version string 23 | // BuildDate build date 24 | BuildDate string 25 | ) 26 | 27 | /** 28 | * Get 获取版本号 29 | */ 30 | func Get() string { 31 | if Version == "" { 32 | return "v0.1.0" 33 | } 34 | 35 | return Version 36 | } 37 | 38 | // GetRevision 获取完整版本号信息,包括时间戳的 39 | func GetRevision() string { 40 | if Version == "" || BuildDate == "" { 41 | return "v0.1.0" 42 | } 43 | 44 | return Version + "." + BuildDate 45 | } 46 | --------------------------------------------------------------------------------