diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000000..0dd044fe4b5 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,82 @@ +# ===================================================================== +# Envoy specific Bazel build/test options. +# ===================================================================== + +# Keep envoy.bazelrc up-to-date by run: +# curl -sSL https://raw.githubusercontent.com/envoyproxy/envoy/master/.bazelrc > envoy.bazelrc +import %workspace%/envoy.bazelrc + +# Istio override: increase JVM heap to 4GB for ARM64 builds (envoy.bazelrc sets 3GB) +# The 3GB limit causes memory pressure during large downloads which breaks gRPC remote cache connections +# Also increase direct buffer memory for large file transfers +# See: https://github.com/istio/proxy/pull/6726 +startup --host_jvm_args=-Xmx4g +startup --host_jvm_args=-XX:MaxDirectMemorySize=4g + +# Overrides workspace_status_command +build --workspace_status_command=bazel/bazel_get_workspace_status +build:remote --remote_timeout=7200 +# ======================================== +# Istio specific Bazel build/test options. +# ======================================== + +# Enable libc++ and C++20 by default. +build:linux --config=clang + +# put /usr/local/bin before /usr/bin to avoid picking up wrong python3.6 when building envoy.tls.key_providers.cryptomb +build:linux --action_env=PATH=/usr/local/bin:/bin:/usr/bin + +# Need for CI image to pickup docker-credential-gcloud, PATH is fixed in rbe-toolchain-* configs. +build:remote-ci --action_env=PATH=/usr/local/google-cloud-sdk/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/llvm/bin +# These flags reduce bazel memory consumption at the cost of slower incremental builds +# CI builds are not incremental, since we build with a clean state, so the performance impact is not +# relevant for CI +build:remote-ci --discard_analysis_cache --nokeep_state_after_build --notrack_incremental_state + +# Apply memory optimization flags to all remote cache builds +build:remote --discard_analysis_cache --nokeep_state_after_build --notrack_incremental_state + +# Enable path normalization by default. +# See: https://github.com/envoyproxy/envoy/pull/6519 +build --define path_normalization_by_default=true + +build:macos --define tcmalloc=disabled + +# Build with embedded V8-based WebAssembly runtime. +build --define wasm=v8 + +# Build Proxy-WASM plugins as native extensions. +build --copt -DNULL_PLUGIN + +# Release builds without debug symbols. +build:release -c opt +build:release --strip=always + +# Release builds with debug symbols +build:release-symbol -c opt + +# Debug builds +build:debug -c dbg + +# Add compile option for all C++ files +# TODO(kuat) re-enable after fixing upstream dd/tracing-lib. +# build --cxxopt -Wnon-virtual-dtor +build --cxxopt -Wformat +build --cxxopt -Wformat-security + +# CI sanitizer configuration +# +build:clang-asan-ci --config=asan +build:clang-asan-ci --linkopt='-L/usr/lib/llvm/lib/x86_64-unknown-linux-gnu' +build:clang-asan-ci --linkopt='-Wl,-rpath,/usr/lib/llvm/lib/x86_64-unknown-linux-gnu' +build:clang-asan-ci --linkopt='-L/usr/lib/llvm/lib/clang/14.0.0/lib/x86_64-unknown-linux-gnu' + +build:clang-tsan-ci --config=tsan +build:clang-tsan-ci --linkopt=-L/opt/libcxx_tsan/lib +build:clang-tsan-ci --linkopt=-Wl,-rpath,/opt/libcxx_tsan/lib + +# get from https://github.com/Homebrew/homebrew-core/blob/master/Formula/e/envoy.rb +build:macos --cxxopt=-Wno-range-loop-analysis +build:macos --host_cxxopt=-Wno-range-loop-analysis +build:macos --cxxopt=-Wno-deprecated-declarations +build:macos --host_cxxopt=-Wno-deprecated-declarations diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000000..5942a0d3a0e --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +7.7.1 diff --git a/.circleci/Dockerfile b/.circleci/Dockerfile deleted file mode 100644 index 5e2c409f79c..00000000000 --- a/.circleci/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Use the JDK image to avoid installing it again. -FROM circleci/openjdk:latest - -# this will install the latest version of bazel - unfortunately it won't -# work, since they break backward compat on every single release. -# Proxy is currently requiring 0.11. -#RUN \ -# sudo sh -c 'echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" > /etc/apt/sources.list.d/bazel.list ' && \ -# curl https://storage.googleapis.com/bazel-apt/doc/apt-key.pub.gpg | sudo apt-key add - - -# clang is used for TSAN and ASAN tests -RUN sudo sh -c 'curl http://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -' -RUN sudo sh -c 'echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-5.0 main" > /etc/apt/sources.list.d/llvm.list' - -RUN sudo apt-get update && \ - sudo apt-get -y install \ - wget software-properties-common make cmake python python-pip \ - zlib1g-dev bash-completion bc libtool automake zip time g++-6 gcc-6 \ - clang-5.0 rsync - -# ~100M, depends on g++, zlib1g-dev, bash-completions -RUN curl -Lo /tmp/bazel.deb https://github.com/bazelbuild/bazel/releases/download/0.11.0/bazel_0.11.0-linux-x86_64.deb && \ - sudo dpkg -i /tmp/bazel.deb && rm /tmp/bazel.deb - - -# Instead of "apt-get -y install golang" -RUN cd /tmp && \ - wget https://redirector.gvt1.com/edgedl/go/go1.9.2.linux-amd64.tar.gz && \ - sudo rm -rf /usr/local/go && \ - sudo tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz && \ - sudo chown -R circleci /usr/local/go && \ - sudo ln -s /usr/local/go/bin/go /usr/local/bin - -RUN bazel version - -# For circleci unit test integration, "go test -v 2>&1 | go-junit-report > report.xml" -RUN go get -u github.com/jstemmer/go-junit-report diff --git a/.circleci/Makefile b/.circleci/Makefile deleted file mode 100644 index fcbb7ef827e..00000000000 --- a/.circleci/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -HUB ?= -PROJECT ?= istio - -# Using same naming convention as istio/istio -VERSION ?= go1.9-bazel0.11 -IMG ?= ci - -# Build a local image, can be used for testing with circleci command line. -image: - docker build -t ${HUB}$(PROJECT)/${IMG}:$(VERSION) -f Dockerfile . - -# Push the image to docker -push: - docker push "${HUB}$(PROJECT)/${IMG}:$(VERSION)" - -# Run the image locally, as current user, for debug. -run: - cd $TOP && docker run -it --rm -u $(id -u) -it \ - -v $PWD:$TOP -w $TOP -e USER=$USER ${HUB}$(PROJECT)/${IMG}:$(VERSION) /bin/bash - -.PHONY: image push latest diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 1ebf91bec2c..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,63 +0,0 @@ -version: 2 - -jobs: - build: - docker: - - image: istio/ci:go1.9-bazel0.11 - environment: - - BAZEL_TEST_ARGS: "--test_env=ENVOY_IP_TEST_VERSIONS=v4only --test_output=all" - resource_class: xlarge - steps: - - checkout - - restore_cache: - keys: - - bazel-cache-{{ checksum "WORKSPACE" }} - - restore_cache: - keys: - - repo-cache-{{ checksum "WORKSPACE" }} - # To build docker containers or run tests in a docker - - setup_remote_docker - - run: rm ~/.gitconfig - - run: make deb BAZEL_BUILD_ARGS="-j 4" - - run: make test - - run: make test_asan - - run: make test_tsan - - save_cache: - key: repo-cache-{{ checksum "WORKSPACE" }} - paths: - - /home/circleci/.repo - - save_cache: - key: bazel-cache-{{ checksum "WORKSPACE" }} - paths: - - /home/circleci/.cache/bazel - - store_artifacts: - path: /home/circleci/project/bazel-bin/tools/deb/istio-proxy.deb - destination: /proxy/deb - - store_artifacts: - path: /home/circleci/project/bazel-bin/src/envoy/mixer/envoy - destination: /proxy/bin - macos: - macos: - xcode: "9.3.0" - environment: - - BAZEL_TEST_ARGS: "--test_env=ENVOY_IP_TEST_VERSIONS=v4only --test_output=all" - steps: - - run: sudo ntpdate -vu time.apple.com - - run: brew install automake bazel cmake coreutils go libtool wget - - checkout - - restore_cache: - keys: - - bazel-cache-{{ checksum "WORKSPACE" }}-macos - - run: rm ~/.gitconfig - - run: make test - - save_cache: - key: bazel-cache-{{ checksum "WORKSPACE" }}-macos - paths: - - /home/circleci/.cache/bazel - -workflows: - version: 2 - all: - jobs: - - build - - macos diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..5283618f4fe --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +ColumnLimit: 100 +DerivePointerAlignment: false +PointerAlignment: Left +SortIncludes: false +TypenameMacros: ['STACK_OF'] +... + +--- +Language: Proto +ColumnLimit: 100 +SpacesInContainerLiterals: false +AllowShortFunctionsOnASingleLine: false +ReflowComments: false +... diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..695bed92a17 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +{ + "name": "istio build-tools", + "image": "registry.istio.io/testing/build-tools-proxy:master-63fd6eec6f3df5a3ed4e190e60153ef3425f66b4", + "privileged": true, + "remoteEnv": { + "USE_GKE_GCLOUD_AUTH_PLUGIN": "True", + "BUILD_WITH_CONTAINER": "0", + "CARGO_HOME": "/home/.cargo", + "RUSTUP_HOME": "/home/.rustup" + }, + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/mpriscella/features/kind:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "golang.go", + "rust-lang.rust-analyzer", + "eamodio.gitlens", + "zxh404.vscode-proto3", + "ms-azuretools.vscode-docker", + "redhat.vscode-yaml", + "IBM.output-colorizer" + ], + "settings": { + "files.eol": "\n", + "go.useLanguageServer": true, + "go.lintTool": "golangci-lint" + } + } + } +} diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 452e9932513..00000000000 --- a/.drone.yml +++ /dev/null @@ -1,20 +0,0 @@ -clone: - git: - image: plugins/git -pipeline: - build: - image: costinm/proxy-builder:0.4.4 - commands: - - id - - env - - sudo chown -R circleci /drone/src - - HOME=/drone/src make build_envoy - test: - image: costinm/proxy-builder:0.4.4 - commands: - - id - - env - - sudo chown -R circleci /drone/src - - HOME=/drone/src make test_envoy - - diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..f050bb655cd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +*.descriptor linguist-generated=true +*.descriptor -diff -merge +*.descriptor_set linguist-generated=true +*.descriptor_set -diff -merge +*.pb.html linguist-generated=true +*.pb.go linguist-generated=true +*.gen.go linguist-generated=true +*.gen.yaml linguist-generated=true +*.gen.json linguist-generated=true +*_pb2.py linguist-generated=true +manifests/charts/**/profile*.yaml linguist-generated=true +go.sum merge=union +vendor/** linguist-vendored +common/** linguist-vendored +archive/** linquist-vendored +**/vmlinux.h linquist-vendored diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c473fb7b002..3f54ab37c54 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,3 @@ **Which issue this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close that issue when PR gets merged)*: fixes # **Special notes for your reviewer**: - -**Release note**: - -```release-note -``` diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000000..9b64336ea3e --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1 @@ +Refer to [Istio Security Overview](https://github.com/istio/istio/blob/master/.github/SECURITY.md) for more details. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..8141eac6e51 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# Configures Depdendabot to PR go security updates only + +version: 2 +updates: + # Go configuration for master branch + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + # Limit number of open PRs to 0 so that we only get security updates + # See https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates + open-pull-requests-limit: 0 + labels: + - "release-notes-none" diff --git a/.gitignore b/.gitignore index 3ad5ae57ae3..cef2b0f6191 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,15 @@ .idea/* docker/envoy *.iml +*.swp +*.test /artifacts +clang.bazelrc +user.bazelrc +compile_commands.json +test/envoye2e/tcp_metadata_exchange/testoutput +test/envoye2e/http_metadata_exchange/testoutput +*.wasm +.vscode +out/ +.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bf693c4a668..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -sudo: required - -dist: trusty - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - gcc-4.9 - - g++-4.9 - - realpath - - wget - -branches: - except: - - stable - -language: cpp - -jdk: - - oraclejdk8 - -env: - - BAZEL_VERSION=0.4.5 - -cache: - directories: - - $HOME/bazel/install - - $HOME/bazel/outbase - - $HOME/clang - -before_install: - - mkdir -p ${HOME}/bazel/install - - cd ${HOME}/bazel/install - - wget --no-clobber "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel_${BAZEL_VERSION}-linux-x86_64.deb" - - chmod +x bazel_${BAZEL_VERSION}-linux-x86_64.deb - - sudo dpkg -i bazel_${BAZEL_VERSION}-linux-x86_64.deb - - sudo apt-get -f install -qqy uuid-dev - - cd ${TRAVIS_BUILD_DIR} - - mv tools/bazel.rc tools/bazel.rc.orig - - cat tools/bazel.rc.travis tools/bazel.rc.orig > tools/bazel.rc - -script: - - script/check-style - - CC=/usr/bin/gcc-4.9 CXX=/usr/bin/g++-4.9 bazel --output_base=${HOME}/bazel/outbase test //... - -notifications: - slack: istio-dev:wEEEbaabdP5ieCgDOFetA9nX diff --git a/BUGS-AND-FEATURE-REQUESTS.md b/BUGS-AND-FEATURE-REQUESTS.md new file mode 100644 index 00000000000..7b97d0e4d79 --- /dev/null +++ b/BUGS-AND-FEATURE-REQUESTS.md @@ -0,0 +1,10 @@ +# Bugs and Feature Requests + +You can report bugs and feature requests to the Istio team in one of three places: + +- [Product Bugs and Feature Requests](https://github.com/istio/istio/issues) +- [Documentation Bugs and Feature Requests](https://github.com/istio/istio.io/issues) +- [Community and Governance Issues](https://github.com/istio/community/issues) + +For security vulnerabilities, please don't report a bug (which is public) and instead follow +[these procedures](https://istio.io/about/security-vulnerabilities/). diff --git a/BUILD b/BUILD index 37252ae00af..7c129d70b9e 100644 --- a/BUILD +++ b/BUILD @@ -1,3 +1,8 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_binary", +) + # Copyright 2016 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +19,7 @@ # ################################################################################ # +load("@rules_pkg//:pkg.bzl", "pkg_tar") exports_files(["LICENSE"]) @@ -22,17 +28,34 @@ config_setting( values = { "cpu": "darwin", }, - visibility = ["//visibility:public"], ) -genrule( - name = "deb_version", - srcs = [], - outs = ["deb_version.txt"], - cmd = "echo $${ISTIO_VERSION:-\"0.3.0-dev\"} > \"$@\"", - visibility = ["//visibility:public"], -) +ISTIO_EXTENSIONS = [ + # TODO(krinkinmu): drop this extension once upstream Envoy hashable string + # is available in all Istio releases we support, so that we can migrate to + # envoy hashable string instead of using custom Envoy implementation. + "//source/extensions/common/hashable_string", + "//source/extensions/common/workload_discovery:api_lib", + "//source/extensions/filters/http/alpn:config_lib", + "//source/extensions/filters/http/istio_stats", + "//source/extensions/filters/http/peer_metadata:filter_lib", + "//source/extensions/filters/network/metadata_exchange:config_lib", + "//source/extensions/filters/network/peer_metadata", +] -load("@io_bazel_rules_go//go:def.bzl", "go_prefix") +envoy_cc_binary( + name = "envoy", + repository = "@envoy", + deps = ISTIO_EXTENSIONS + [ + "@envoy//source/exe:envoy_main_entry_lib", + ], +) -go_prefix("istio.io/proxy") +pkg_tar( + name = "envoy_tar", + srcs = [":envoy"], + extension = "tar.gz", + mode = "0755", + package_dir = "/usr/local/bin/", + tags = ["manual"], +) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..37f2015ed7b --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @istio/wg-policies-and-telemetry-maintainers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c38680b0241..2a94c3bf933 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,22 @@ # Contribution guidelines -So, you want to hack on the Istio proxy? Yay! Please refer to Istio's overall -[contribution guidelines](https://github.com/istio/istio/blob/master/DEV-GUIDE.md) +So you want to hack on Istio? Yay! Please refer to Istio's overall +[contribution guidelines](https://github.com/istio/community/blob/master/CONTRIBUTING.md) to find out how you can help. + +## Prerequisites + +To make sure you're ready to build Envoy, clone and follow the upstream Envoy [build instructions](https://github.com/envoyproxy/envoy/blob/main/bazel/README.md). Be sure to copy clang.bazelrc into this directory after running the setup_clang script. Confirm that your environment is ready to go by running `bazel build --config=clang --define=wasm=disabled :envoy` (in this directory). + +## How to use a devcontainer + +1. Change the image in .devcontainer.json to `build-tools-proxy` instead of `build-tools` +2. Open the directory in a container with the Remote - Containers extension +3. Install the following extensions: + - [clangd](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) + - [bazel-stack-vscode-cc](https://marketplace.visualstudio.com/items?itemName=StackBuild.bazel-stack-vscode-cc) +4. Update clangd and reload the container +5. Edit the new `bsv.cc.compdb.targets` workspace setting and set it to `//:envoy_tar` +6. Execute `Bazel/C++: Generate Compilation Database` within vscode + +Note: if you have a remote bazel cache or something mounted in your build container for your normal proxy builds, you'll need to configure that in the devcontainer with runArgs. diff --git a/LICENSE b/LICENSE index 2c45691e883..75bfd113b3c 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016 Istio Authors + Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index 0c0216b171e..b0e2b4524c5 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,69 @@ -## Copyright 2017 Istio Authors -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. - -TOP := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) - -SHELL := /bin/bash -LOCAL_ARTIFACTS_DIR ?= $(abspath artifacts) -ARTIFACTS_DIR ?= $(LOCAL_ARTIFACTS_DIR) -BAZEL_STARTUP_ARGS ?= --batch -BAZEL_BUILD_ARGS ?= -BAZEL_TEST_ARGS ?= -HUB ?= -TAG ?= - -build: - @bazel $(BAZEL_STARTUP_ARGS) build $(BAZEL_BUILD_ARGS) //... - -# Build only envoy - fast -build_envoy: - bazel $(BAZEL_STARTUP_ARGS) build $(BAZEL_BUILD_ARGS) //src/envoy/mixer:envoy - -clean: - @bazel clean - -test: - bazel $(BAZEL_STARTUP_ARGS) test $(BAZEL_TEST_ARGS) //... - -test_asan: - CC=clang-5.0 CXX=clang++-5.0 bazel $(BAZEL_STARTUP_ARGS) test $(BAZEL_TEST_ARGS) --config=clang-asan //... - -test_tsan: - CC=clang-5.0 CXX=clang++-5.0 bazel $(BAZEL_STARTUP_ARGS) test $(BAZEL_TEST_ARGS) --config=clang-tsan //... - -check: - @script/check-license-headers - @script/check-style - -artifacts: build - @script/push-debian.sh -c opt -p $(ARTIFACTS_DIR) - -deb: - @bazel build tools/deb:istio-proxy ${BAZEL_BUILD_ARGS} - - -.PHONY: build clean test check artifacts +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SHELL := /usr/bin/env bash + +# allow optional per-repo overrides +-include Makefile.overrides.mk + +# Set the environment variable BUILD_WITH_CONTAINER to use a container +# to build the repo. The only dependencies in this mode are to have make and +# docker. If you'd rather build with a local tool chain instead, you'll need to +# figure out all the tools you need in your environment to make that work. +export BUILD_WITH_CONTAINER ?= 0 + +ifeq ($(BUILD_WITH_CONTAINER),1) + +# An export free of arguments in a Makefile places all variables in the Makefile into the +# environment. This is needed to allow overrides from Makefile.overrides.mk. +export + +RUN = ./common/scripts/run.sh + +MAKE_DOCKER = $(RUN) make --no-print-directory -e -f Makefile.core.mk + +%: + @$(MAKE_DOCKER) $@ + +default: + @$(MAKE_DOCKER) + +shell: + @$(RUN) /bin/bash + +.PHONY: default shell + +else + +# If we are not in build container, we need a workaround to get environment properly set +# Write to file, then include +$(shell mkdir -p out) +$(shell $(shell pwd)/common/scripts/setup_env.sh envfile > out/.env) +include out/.env +# An export free of arguments in a Makefile places all variables in the Makefile into the +# environment. This behavior may be surprising to many that use shell often, which simply +# displays the existing environment +export + +export GOBIN ?= $(GOPATH)/bin +include Makefile.core.mk + +endif diff --git a/Makefile.core.mk b/Makefile.core.mk new file mode 100644 index 00000000000..a8d71344ac5 --- /dev/null +++ b/Makefile.core.mk @@ -0,0 +1,138 @@ +## Copyright 2017 Istio Authors +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +TOP := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +SHELL := /bin/bash +BAZEL_STARTUP_ARGS ?= +BAZEL_BUILD_ARGS ?= +BAZEL_TARGETS ?= //... +# Don't build Debian packages and Docker images in tests. +BAZEL_TEST_TARGETS ?= ${BAZEL_TARGETS} +E2E_TEST_TARGETS ?= $$(go list ./...) +E2E_TEST_FLAGS := -p=1 -parallel=1 +HUB ?= +TAG ?= +repo_dir := . + +VERBOSE ?= +ifeq "$(VERBOSE)" "1" +BAZEL_STARTUP_ARGS := --client_debug $(BAZEL_STARTUP_ARGS) +BAZEL_BUILD_ARGS := -s --sandbox_debug --verbose_failures $(BAZEL_BUILD_ARGS) +endif + +BAZEL_CONFIG = + +UNAME := $(shell uname) +ifeq ($(UNAME),Linux) +BAZEL_CONFIG_DEV = $(BAZEL_CONFIG) +BAZEL_CONFIG_REL = $(BAZEL_CONFIG) --config=release +BAZEL_CONFIG_ASAN = $(BAZEL_CONFIG) --config=clang-asan-ci +BAZEL_CONFIG_TSAN = $(BAZEL_CONFIG) --config=clang-tsan-ci +endif +ifeq ($(UNAME),Darwin) +BAZEL_CONFIG_DEV = # macOS always links against libc++ +BAZEL_CONFIG_REL = --config=release +BAZEL_CONFIG_ASAN = --config=macos-asan +BAZEL_CONFIG_TSAN = # no working config +endif +BAZEL_CONFIG_CURRENT ?= $(BAZEL_CONFIG_DEV) + +TEST_ENVOY_TARGET ?= //:envoy +TEST_ENVOY_DEBUG ?= trace + +build: + bazel $(BAZEL_STARTUP_ARGS) build $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_CURRENT) -- $(BAZEL_TARGETS) + +build_envoy: BAZEL_CONFIG_CURRENT = $(BAZEL_CONFIG_REL) +build_envoy: BAZEL_TARGETS = //:envoy +build_envoy: build + +build_envoy_tsan: BAZEL_CONFIG_CURRENT = $(BAZEL_CONFIG_TSAN) +build_envoy_tsan: BAZEL_TARGETS = //:envoy +build_envoy_tsan: build + +build_envoy_asan: BAZEL_CONFIG_CURRENT = $(BAZEL_CONFIG_ASAN) +build_envoy_asan: BAZEL_TARGETS = //:envoy +build_envoy_asan: build + +check_wasm: + @true + +clean: + @bazel clean + +.PHONY: gen-extensions-doc +gen-extensions-doc: + buf generate --path source/extensions/filters + +gen: + @scripts/gen-testdata.sh + +gen-check: + @scripts/gen-testdata.sh -c + +test: + bazel $(BAZEL_STARTUP_ARGS) build $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_CURRENT) -- $(TEST_ENVOY_TARGET) $(BAZEL_TEST_TARGETS) + if [ -n "$(BAZEL_TEST_TARGETS)" ]; then \ + bazel $(BAZEL_STARTUP_ARGS) test $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_CURRENT) -- $(BAZEL_TEST_TARGETS); \ + fi + if [ -n "$(E2E_TEST_TARGETS)" ]; then \ + env ENVOY_DEBUG=$(TEST_ENVOY_DEBUG) ENVOY_PATH=$(shell bazel $(BAZEL_STARTUP_ARGS) info $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_CURRENT) bazel-bin)/envoy $(E2E_TEST_ENVS) GO111MODULE=on go test -timeout 30m $(E2E_TEST_FLAGS) $(E2E_TEST_TARGETS); \ + fi + +test_asan: BAZEL_CONFIG_CURRENT = $(BAZEL_CONFIG_ASAN) +test_asan: E2E_TEST_ENVS = ASAN=true +test_asan: test + +test_tsan: BAZEL_CONFIG_CURRENT = $(BAZEL_CONFIG_TSAN) +test_tsan: E2E_TEST_ENVS = TSAN=true +test_tsan: TEST_ENVOY_DEBUG = debug # tsan is too slow for trace +test_tsan: test + +check: + @echo >&2 "Please use \"make lint\" instead." + @false + +lint: lint-copyright-banner format-go lint-go tidy-go lint-scripts gen-extensions-doc + @scripts/check-repository.sh + @scripts/check-style.sh + +test_release: +ifeq "$(shell uname -m)" "x86_64" + export BAZEL_BUILD_ARGS="$(BAZEL_BUILD_ARGS)" && ./scripts/release-binary.sh +else + # Only x86 has support for legacy GLIBC, otherwise pass -i to skip the check + export BAZEL_BUILD_ARGS="$(BAZEL_BUILD_ARGS)" && ./scripts/release-binary.sh -i +endif + +push_release: +ifeq "$(shell uname -m)" "x86_64" + export BAZEL_BUILD_ARGS="$(BAZEL_BUILD_ARGS)" && ./scripts/release-binary.sh -d "$(RELEASE_GCS_PATH)" ${PUSH_RELEASE_FLAGS} +else + # Only x86 has support for legacy GLIBC, otherwise pass -i to skip the check + export BAZEL_BUILD_ARGS="$(BAZEL_BUILD_ARGS)" && ./scripts/release-binary.sh -i -d "$(RELEASE_GCS_PATH)" ${PUSH_RELEASE_FLAGS} +endif + +# Used by build container to export the build output from the docker volume cache +exportcache: BAZEL_BIN_PATH ?= $(shell bazel info $(BAZEL_BUILD_ARGS) $(BAZEL_CONFIG_CURRENT) bazel-bin) +exportcache: + @mkdir -p /work/out/$(TARGET_OS)_$(TARGET_ARCH) + @cp -a $(BAZEL_BIN_PATH)/envoy /work/out/$(TARGET_OS)_$(TARGET_ARCH) + @chmod +w /work/out/$(TARGET_OS)_$(TARGET_ARCH)/envoy + @cp -a $(BAZEL_BIN_PATH)/**/*wasm /work/out/$(TARGET_OS)_$(TARGET_ARCH) &> /dev/null || true + +.PHONY: build clean test check extensions-proto + +include common/Makefile.common.mk diff --git a/include/istio/prefetch/BUILD b/Makefile.overrides.mk similarity index 66% rename from include/istio/prefetch/BUILD rename to Makefile.overrides.mk index 93ab40bd531..4041a4bfa80 100644 --- a/include/istio/prefetch/BUILD +++ b/Makefile.overrides.mk @@ -1,23 +1,18 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. +# Copyright 2019 Istio Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 - +# http://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -licenses(["notice"]) - -cc_library( - name = "headers_lib", - hdrs = [ - "quota_prefetch.h", - ], - visibility = ["//visibility:public"], -) +# this repo is not on the container plan by default +BUILD_WITH_CONTAINER ?= 0 +IMAGE_NAME ?= build-tools-proxy +CGO_ENABLED = 0 diff --git a/OWNERS b/OWNERS deleted file mode 100644 index d5d8e94bb67..00000000000 --- a/OWNERS +++ /dev/null @@ -1,14 +0,0 @@ -reviewers: - - qiwzhang - - lizan - - sebastienvas - - rshriram - - linsun - - JimmyCYJ -approvers: - - qiwzhang - - lizan - - sebastienvas - - rshriram - - linsun - - JimmyCYJ diff --git a/README.md b/README.md index 9c478e2f3f0..3b2d4b1bc49 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,8 @@ # Istio Proxy -The Istio Proxy is a microservice proxy that can be used on the client and server side, and forms a microservice mesh. The Proxy supports a large number of features. +The Istio Proxy is a microservice proxy that can be used on the client and server side, and forms a microservice mesh. +It is based on [Envoy](http://envoyproxy.io) with the addition of several policy and telemetry extensions. -Client Side Features: +According to the [conclusion from Istio workgroup meeting on 4-17-2024](https://docs.google.com/document/d/1wsa06GGiq1LEGwhkiPP0FKIZJqdAiue-VeBonWAzAyk/edit#heading=h.ma5hboh81yw): -- *Discovery & Load Balancing*. The Proxy can use several standard service discovery and load balancing APIs to efficiently distribute traffic to services. - -- *Credential Injection*. The Proxy can inject client identity, either through connection tunneling or protocol-specific mechanisms such as JWT tokens for HTTP requests. - -- *Connection Management*. The Proxy manages connections to services, handling health checking, retry, failover, and flow control. - -- *Monitoring & Logging*. The Proxy can report client-side metrics and logs to the Mixer. - -Server Side Features: - -- *Rate Limiting & Flow Control*. The Proxy can prevent overload of backend systems and provide client-aware rate limiting. - -- *Protocol Translation*. The Proxy is a gRPC gateway, providing translation between JSON-REST and gRPC. - -- *Authentication & Authorization*. The Proxy supports multiple authentication mechanisms, and can use the client identities to perform authorization checks through the Mixer. - -- *Monitoring & Logging*. The Proxy can report server-side metrics and logs to the Mixer. - -Please see [istio.io](https://istio.io) -to learn about the overall Istio project and how to get in touch with us. To learn how you can -contribute to any of the Istio components, including the proxy, please -see the Istio [contribution guidelines](https://github.com/istio/istio/blob/master/CONTRIBUTING.md). +- New extensions are not added unless they are part of core APIs \ No newline at end of file diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 00000000000..6e80792bfc1 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,7 @@ +# Support + +Here are some resources to help you understand and use Istio: + +- For in-depth information about how to use Istio, visit [istio.io](https://istio.io) +- To ask questions and get assistance from our community, visit [GitHub Discussions](https://github.com/istio/istio/discussions) +- To learn how to participate in our overall community, visit [our community page](https://istio.io/latest/get-involved/) diff --git a/WORKSPACE b/WORKSPACE index b864fa6513a..60aa60607b0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -14,56 +14,81 @@ # ################################################################################ # +workspace(name = "io_istio_proxy") -load( - "//:repositories.bzl", - "googletest_repositories", - "mixerapi_dependencies", -) +# http_archive is not a native function since bazel 0.19 +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -googletest_repositories() -mixerapi_dependencies() +# 1. Determine SHA256 `wget https://github.com/envoyproxy/envoy/archive/$COMMIT.tar.gz && sha256sum $COMMIT.tar.gz` +# 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. +# +# Commit date: 2026-05-14 +ENVOY_SHA = "4f19551ad2417d4e8cf77d64ce25c3ba5a95ca78" -bind( - name = "boringssl_crypto", - actual = "//external:ssl", -) +ENVOY_SHA256 = "8c43deda3dbab9996c13cf54272ddf725ee6de4de6164027a7d25216f776322a" -# We need newer gRPC than Envoy has for ALTS, this could be removed once Envoy picks -# newer gRPC with ALTS support. (likely v1.11). -git_repository( - name = "com_github_grpc_grpc", - remote = "https://github.com/grpc/grpc.git", - commit = "c50405364a194a0e4931153cbe329662d90530bc", # Mar 28, 2018 -) +ENVOY_ORG = "envoyproxy" -# When updating envoy sha manually please update the sha in istio.deps file also -ENVOY_SHA = "2b2c299144600fb9e525d21aabf39bf48e64fb1f" +ENVOY_REPO = "envoy" +# To override with local envoy, just pass `--override_repository=envoy=/PATH/TO/ENVOY` to Bazel or +# persist the option in `user.bazelrc`. http_archive( name = "envoy", - strip_prefix = "envoy-" + ENVOY_SHA, - url = "https://github.com/envoyproxy/envoy/archive/" + ENVOY_SHA + ".zip", + sha256 = ENVOY_SHA256, + strip_prefix = ENVOY_REPO + "-" + ENVOY_SHA, + url = "https://github.com/" + ENVOY_ORG + "/" + ENVOY_REPO + "/archive/" + ENVOY_SHA + ".tar.gz", +) + +load("@envoy//bazel:api_binding.bzl", "envoy_api_binding") + +local_repository( + name = "envoy_build_config", + # Relative paths are also supported. + path = "bazel/extension_config", ) +envoy_api_binding() + +load("@envoy//bazel:api_repositories.bzl", "envoy_api_dependencies") + +envoy_api_dependencies() + load("@envoy//bazel:repositories.bzl", "envoy_dependencies") + envoy_dependencies() -load("@envoy//bazel:cc_configure.bzl", "cc_configure") -cc_configure() +load("@envoy//bazel:bazel_deps.bzl", "envoy_bazel_dependencies") -load("@envoy_api//bazel:repositories.bzl", "api_dependencies") -api_dependencies() +envoy_bazel_dependencies() -load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") -load("@com_lyft_protoc_gen_validate//bazel:go_proto_library.bzl", "go_proto_repositories") -go_proto_repositories(shared=0) -go_rules_dependencies() -go_register_toolchains() +load("@envoy//bazel:repositories_extra.bzl", "envoy_dependencies_extra") -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") -git_repository( - name = "org_pubref_rules_protobuf", - commit = "563b674a2ce6650d459732932ea2bc98c9c9a9bf", # Nov 28, 2017 (bazel 0.8.0 support) - remote = "https://github.com/pubref/rules_protobuf", +envoy_dependencies_extra( + glibc_version = "2.28", + ignore_root_user_error = True, ) + +load("@envoy//bazel:python_dependencies.bzl", "envoy_python_dependencies") + +envoy_python_dependencies() + +load("@base_pip3//:requirements.bzl", "install_deps") + +install_deps() + +load("@envoy//bazel:dependency_imports.bzl", "envoy_dependency_imports") + +envoy_dependency_imports() + +load("@envoy//bazel:repo.bzl", "envoy_repo") + +envoy_repo() + +load("@envoy//bazel:toolchains.bzl", "envoy_toolchains") + +envoy_toolchains() + +load("@llvm_toolchain//:toolchains.bzl", "llvm_register_toolchains") + +llvm_register_toolchains() diff --git a/include/istio/api_spec/BUILD b/bazel/BUILD similarity index 65% rename from include/istio/api_spec/BUILD rename to bazel/BUILD index 4526fe2ce68..05bc5ad3ca3 100644 --- a/include/istio/api_spec/BUILD +++ b/bazel/BUILD @@ -1,23 +1,16 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. +# Copyright 2020 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 - +# http://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "headers_lib", - hdrs = [ - "http_api_spec_parser.h", - ], - visibility = ["//visibility:public"], -) +# +################################################################################ +# diff --git a/src/envoy/tcp/mixer/BUILD b/bazel/bazel_get_workspace_status old mode 100644 new mode 100755 similarity index 52% rename from src/envoy/tcp/mixer/BUILD rename to bazel/bazel_get_workspace_status index f9cba6d787e..ba02051ebef --- a/src/envoy/tcp/mixer/BUILD +++ b/bazel/bazel_get_workspace_status @@ -1,4 +1,6 @@ -# Copyright 2016 Istio Authors. All Rights Reserved. +#!/bin/bash +# +# Copyright 2020 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,29 +15,23 @@ # limitations under the License. # ################################################################################ -# -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", -) +if [ -z "${BUILD_SCM_REVISION}" ]; then + if git rev-parse --verify --quiet HEAD >/dev/null; then + BUILD_SCM_REVISION="$(git rev-parse --verify HEAD)" + else + exit 1 + fi +fi + +if [ -z "${BUILD_SCM_STATUS}" ]; then + if git diff-index --quiet HEAD; then + BUILD_SCM_STATUS="Clean" + else + BUILD_SCM_STATUS="Modified" + fi +fi -envoy_cc_library( - name = "filter_lib", - srcs = [ - "config.h", - "control.cc", - "control.h", - "control_factory.h", - "filter.cc", - "filter.h", - "filter_factory.cc", - ], - repository = "@envoy", - visibility = ["//visibility:public"], - deps = [ - "//src/envoy/utils:utils_lib", - "//src/istio/control/tcp:control_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) +echo "BUILD_SCM_REVISION ${BUILD_SCM_REVISION}" +echo "BUILD_SCM_STATUS ${BUILD_SCM_STATUS}" +echo "BUILD_CONFIG ${BUILD_CONFIG:-default}" diff --git a/bazel/extension_config/BUILD b/bazel/extension_config/BUILD new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bazel/extension_config/WORKSPACE b/bazel/extension_config/WORKSPACE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bazel/extension_config/extensions_build_config.bzl b/bazel/extension_config/extensions_build_config.bzl new file mode 100644 index 00000000000..b3c1a3f955c --- /dev/null +++ b/bazel/extension_config/extensions_build_config.bzl @@ -0,0 +1,559 @@ +load("@bazel_skylib//lib:dicts.bzl", "dicts") + +ENVOY_EXTENSIONS = { + # + # Access loggers + # + + "envoy.access_loggers.file": "//source/extensions/access_loggers/file:config", + "envoy.access_loggers.extension_filters.cel": "//source/extensions/access_loggers/filters/cel:config", + "envoy.access_loggers.fluentd" : "//source/extensions/access_loggers/fluentd:config", + "envoy.access_loggers.http_grpc": "//source/extensions/access_loggers/grpc:http_config", + "envoy.access_loggers.tcp_grpc": "//source/extensions/access_loggers/grpc:tcp_config", + "envoy.access_loggers.open_telemetry": "//source/extensions/access_loggers/open_telemetry:config", + "envoy.access_loggers.stats": "//source/extensions/access_loggers/stats:config", + "envoy.access_loggers.stdout": "//source/extensions/access_loggers/stream:config", + "envoy.access_loggers.stderr": "//source/extensions/access_loggers/stream:config", + "envoy.access_loggers.wasm": "//source/extensions/access_loggers/wasm:config", + "envoy.access_loggers.dynamic_modules": "//source/extensions/access_loggers/dynamic_modules:config", + + # + # Clusters + # + + "envoy.clusters.aggregate": "//source/extensions/clusters/aggregate:cluster", + "envoy.clusters.dns": "//source/extensions/clusters/dns:dns_cluster_lib", + "envoy.clusters.dynamic_forward_proxy": "//source/extensions/clusters/dynamic_forward_proxy:cluster", + "envoy.clusters.eds": "//source/extensions/clusters/eds:eds_lib", + "envoy.clusters.redis": "//source/extensions/clusters/redis:redis_cluster", + "envoy.clusters.static": "//source/extensions/clusters/static:static_cluster_lib", + "envoy.clusters.strict_dns": "//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", + "envoy.clusters.original_dst": "//source/extensions/clusters/original_dst:original_dst_cluster_lib", + "envoy.clusters.logical_dns": "//source/extensions/clusters/logical_dns:logical_dns_cluster_lib", + "envoy.clusters.dynamic_modules": "//source/extensions/clusters/dynamic_modules:cluster", + + # + # Compression + # + + "envoy.compression.gzip.compressor": "//source/extensions/compression/gzip/compressor:config", + "envoy.compression.gzip.decompressor": "//source/extensions/compression/gzip/decompressor:config", + "envoy.compression.brotli.compressor": "//source/extensions/compression/brotli/compressor:config", + "envoy.compression.brotli.decompressor": "//source/extensions/compression/brotli/decompressor:config", + "envoy.compression.zstd.compressor": "//source/extensions/compression/zstd/compressor:config", + "envoy.compression.zstd.decompressor": "//source/extensions/compression/zstd/decompressor:config", + + # + # gRPC Credentials Plugins + # + + "envoy.grpc_credentials.file_based_metadata": "//source/extensions/grpc_credentials/file_based_metadata:config", + + # + # WASM + # + + "envoy.bootstrap.wasm": "//source/extensions/bootstrap/wasm:config", + "envoy.bootstrap.dynamic_modules": "//source/extensions/bootstrap/dynamic_modules:config", + + # + # Health checkers + # + + "envoy.health_checkers.redis": "//source/extensions/health_checkers/redis:config", + "envoy.health_checkers.tcp": "//source/extensions/health_checkers/tcp:health_checker_lib", + "envoy.health_checkers.http": "//source/extensions/health_checkers/http:health_checker_lib", + "envoy.health_checkers.grpc": "//source/extensions/health_checkers/grpc:health_checker_lib", + + # + # Input Matchers + # + + "envoy.matching.matchers.consistent_hashing": "//source/extensions/matching/input_matchers/consistent_hashing:config", + "envoy.matching.matchers.ip": "//source/extensions/matching/input_matchers/ip:config", + "envoy.matching.matchers.dynamic_modules": "//source/extensions/matching/input_matchers/dynamic_modules:config", + + # + # Network Matchers + # + + "envoy.matching.inputs.application_protocol": "//source/extensions/matching/network/application_protocol:config", + # Ideally these would be split up. We'll do so if anyone cares. + "envoy.matching.inputs.destination_ip": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.destination_port": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.source_ip": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.source_port": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.direct_source_ip": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.source_type": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.server_name": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.transport_protocol": "//source/extensions/matching/network/common:inputs_lib", + "envoy.matching.inputs.dynamic_module_data_input": "//source/extensions/matching/http/dynamic_modules:data_input_lib", + + # + # Generic Inputs + # + + "envoy.matching.common_inputs.environment_variable": "//source/extensions/matching/common_inputs/environment_variable:config", + + # + # Matching actions + # + + "envoy.matching.actions.format_string": "//source/extensions/matching/actions/format_string:config", + + # + # StringMatchers + # + "envoy.string_matcher.lua": "//source/extensions/string_matcher/lua:config", + + # + # HTTP filters + # + + "envoy.filters.http.adaptive_concurrency": "//source/extensions/filters/http/adaptive_concurrency:config", + "envoy.filters.http.admission_control": "//source/extensions/filters/http/admission_control:config", + "envoy.filters.http.alternate_protocols_cache": "//source/extensions/filters/http/alternate_protocols_cache:config", + "envoy.filters.http.aws_lambda": "//source/extensions/filters/http/aws_lambda:config", + "envoy.filters.http.aws_request_signing": "//source/extensions/filters/http/aws_request_signing:config", + "envoy.filters.http.bandwidth_limit": "//source/extensions/filters/http/bandwidth_limit:config", + "envoy.filters.http.basic_auth": "//source/extensions/filters/http/basic_auth:config", + "envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", + "envoy.filters.http.cache": "//source/extensions/filters/http/cache:config", + "envoy.filters.http.cdn_loop": "//source/extensions/filters/http/cdn_loop:config", + "envoy.filters.http.compressor": "//source/extensions/filters/http/compressor:config", + "envoy.filters.http.cors": "//source/extensions/filters/http/cors:config", + "envoy.filters.http.composite": "//source/extensions/filters/http/composite:config", + "envoy.filters.http.connect_grpc_bridge": "//source/extensions/filters/http/connect_grpc_bridge:config", + "envoy.filters.http.credential_injector": "//source/extensions/filters/http/credential_injector:config", + "envoy.filters.http.csrf": "//source/extensions/filters/http/csrf:config", + "envoy.filters.http.decompressor": "//source/extensions/filters/http/decompressor:config", + "envoy.filters.http.dynamic_forward_proxy": "//source/extensions/filters/http/dynamic_forward_proxy:config", + "envoy.filters.http.ext_authz": "//source/extensions/filters/http/ext_authz:config", + "envoy.filters.http.ext_proc": "//source/extensions/filters/http/ext_proc:config", + "envoy.filters.http.fault": "//source/extensions/filters/http/fault:config", + "envoy.filters.http.gcp_authn": "//source/extensions/filters/http/gcp_authn:config", + "envoy.filters.http.grpc_field_extraction": "//source/extensions/filters/http/grpc_field_extraction:config", + "envoy.filters.http.grpc_http1_bridge": "//source/extensions/filters/http/grpc_http1_bridge:config", + "envoy.filters.http.grpc_http1_reverse_bridge": "//source/extensions/filters/http/grpc_http1_reverse_bridge:config", + "envoy.filters.http.grpc_json_transcoder": "//source/extensions/filters/http/grpc_json_transcoder:config", + "envoy.filters.http.grpc_stats": "//source/extensions/filters/http/grpc_stats:config", + "envoy.filters.http.grpc_web": "//source/extensions/filters/http/grpc_web:config", + "envoy.filters.http.header_to_metadata": "//source/extensions/filters/http/header_to_metadata:config", + "envoy.filters.http.header_mutation": "//source/extensions/filters/http/header_mutation:config", + "envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config", + "envoy.filters.http.ip_tagging": "//source/extensions/filters/http/ip_tagging:config", + "envoy.filters.http.json_to_metadata": "//source/extensions/filters/http/json_to_metadata:config", + "envoy.filters.http.jwt_authn": "//source/extensions/filters/http/jwt_authn:config", + # Disabled by default + "envoy.filters.http.kill_request": "//source/extensions/filters/http/kill_request:kill_request_config", + "envoy.filters.http.local_ratelimit": "//source/extensions/filters/http/local_ratelimit:config", + "envoy.filters.http.lua": "//source/extensions/filters/http/lua:config", + "envoy.filters.http.oauth2": "//source/extensions/filters/http/oauth2:config", + "envoy.filters.http.on_demand": "//source/extensions/filters/http/on_demand:config", + "envoy.filters.http.original_src": "//source/extensions/filters/http/original_src:config", + "envoy.filters.http.ratelimit": "//source/extensions/filters/http/ratelimit:config", + "envoy.filters.http.rate_limit_quota": "//source/extensions/filters/http/rate_limit_quota:config", + "envoy.filters.http.rbac": "//source/extensions/filters/http/rbac:config", + "envoy.filters.http.router": "//source/extensions/filters/http/router:config", + "envoy.filters.http.set_filter_state": "//source/extensions/filters/http/set_filter_state:config", + "envoy.filters.http.set_metadata": "//source/extensions/filters/http/set_metadata:config", + "envoy.filters.http.tap": "//source/extensions/filters/http/tap:config", + "envoy.filters.http.wasm": "//source/extensions/filters/http/wasm:config", + "envoy.filters.http.stateful_session": "//source/extensions/filters/http/stateful_session:config", + "envoy.filters.http.dynamic_modules": "//source/extensions/filters/http/dynamic_modules:factory_registration", + + # + # Listener filters + # + + "envoy.filters.listener.http_inspector": "//source/extensions/filters/listener/http_inspector:config", + # NOTE: The original_dst filter is implicitly loaded if original_dst functionality is + # configured on the listener. Do not remove it in that case or configs will fail to load. + "envoy.filters.listener.original_dst": "//source/extensions/filters/listener/original_dst:config", + "envoy.filters.listener.original_src": "//source/extensions/filters/listener/original_src:config", + # NOTE: The proxy_protocol filter is implicitly loaded if proxy_protocol functionality is + # configured on the listener. Do not remove it in that case or configs will fail to load. + "envoy.filters.listener.proxy_protocol": "//source/extensions/filters/listener/proxy_protocol:config", + "envoy.filters.listener.tls_inspector": "//source/extensions/filters/listener/tls_inspector:config", + "envoy.filters.listener.dynamic_modules": "//source/extensions/filters/listener/dynamic_modules:config", + + # + # Network filters + # + + "envoy.filters.network.connection_limit": "//source/extensions/filters/network/connection_limit:config", + "envoy.filters.network.direct_response": "//source/extensions/filters/network/direct_response:config", + "envoy.filters.network.dubbo_proxy": "//source/extensions/filters/network/dubbo_proxy:config", + "envoy.filters.network.echo": "//source/extensions/filters/network/echo:config", + "envoy.filters.network.ext_authz": "//source/extensions/filters/network/ext_authz:config", + "envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", + "envoy.filters.network.local_ratelimit": "//source/extensions/filters/network/local_ratelimit:config", + "envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config", + "envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config", + "envoy.filters.network.rbac": "//source/extensions/filters/network/rbac:config", + "envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config", + "envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config", + "envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config", + "envoy.filters.network.set_filter_state": "//source/extensions/filters/network/set_filter_state:config", + "envoy.filters.network.sni_cluster": "//source/extensions/filters/network/sni_cluster:config", + "envoy.filters.network.sni_dynamic_forward_proxy": "//source/extensions/filters/network/sni_dynamic_forward_proxy:config", + "envoy.filters.network.wasm": "//source/extensions/filters/network/wasm:config", + "envoy.filters.network.zookeeper_proxy": "//source/extensions/filters/network/zookeeper_proxy:config", + "envoy.filters.network.dynamic_modules": "//source/extensions/filters/network/dynamic_modules:config", + + # + # UDP filters + # + + "envoy.filters.udp.dns_filter": "//source/extensions/filters/udp/dns_filter:config", + "envoy.filters.udp_listener.udp_proxy": "//source/extensions/filters/udp/udp_proxy:config", + "envoy.filters.udp_listener.dynamic_modules": "//source/extensions/filters/udp/dynamic_modules:config", + + # + # Resource monitors + # + + "envoy.resource_monitors.cpu_utilization": "//source/extensions/resource_monitors/cpu_utilization:config", + "envoy.resource_monitors.fixed_heap": "//source/extensions/resource_monitors/fixed_heap:config", + "envoy.resource_monitors.cgroup_memory": "//source/extensions/resource_monitors/cgroup_memory:config", + "envoy.resource_monitors.injected_resource": "//source/extensions/resource_monitors/injected_resource:config", + "envoy.resource_monitors.downstream_connections": "//source/extensions/resource_monitors/downstream_connections:config", + + # + # Stat sinks + # + + "envoy.stat_sinks.dog_statsd": "//source/extensions/stat_sinks/dog_statsd:config", + "envoy.stat_sinks.graphite_statsd": "//source/extensions/stat_sinks/graphite_statsd:config", + "envoy.stat_sinks.hystrix": "//source/extensions/stat_sinks/hystrix:config", + "envoy.stat_sinks.metrics_service": "//source/extensions/stat_sinks/metrics_service:config", + "envoy.stat_sinks.open_telemetry": "//source/extensions/stat_sinks/open_telemetry:config", + "envoy.stat_sinks.statsd": "//source/extensions/stat_sinks/statsd:config", + "envoy.stat_sinks.wasm": "//source/extensions/stat_sinks/wasm:config", + + # + # Thrift filters + # + + "envoy.filters.thrift.router": "//source/extensions/filters/network/thrift_proxy/router:config", + "envoy.filters.thrift.header_to_metadata": "//source/extensions/filters/network/thrift_proxy/filters/header_to_metadata:config", + "envoy.filters.thrift.rate_limit": "//source/extensions/filters/network/thrift_proxy/filters/ratelimit:config", + + # + # Tracers + # + + "envoy.tracers.datadog": "//source/extensions/tracers/datadog:config", + "envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", + "envoy.tracers.xray": "//source/extensions/tracers/xray:config", + "envoy.tracers.skywalking": "//source/extensions/tracers/skywalking:config", + "envoy.tracers.opentelemetry": "//source/extensions/tracers/opentelemetry:config", + "envoy.tracers.dynamic_modules": "//source/extensions/tracers/dynamic_modules:config", + + # + # OpenTelemetry Resource Detectors + # + + "envoy.tracers.opentelemetry.resource_detectors.environment": "//source/extensions/tracers/opentelemetry/resource_detectors/environment:config", + "envoy.tracers.opentelemetry.resource_detectors.dynatrace": "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:config", + + # + # OpenTelemetry tracer samplers + # + + "envoy.tracers.opentelemetry.samplers.always_on": "//source/extensions/tracers/opentelemetry/samplers/always_on:config", + "envoy.tracers.opentelemetry.samplers.dynatrace": "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config", + + # + # Transport sockets + # + + "envoy.transport_sockets.alts": "//source/extensions/transport_sockets/alts:config", + "envoy.transport_sockets.upstream_proxy_protocol": "//source/extensions/transport_sockets/proxy_protocol:upstream_config", + "envoy.transport_sockets.raw_buffer": "//source/extensions/transport_sockets/raw_buffer:config", + "envoy.transport_sockets.tap": "//source/extensions/transport_sockets/tap:config", + "envoy.transport_sockets.starttls": "//source/extensions/transport_sockets/starttls:config", + "envoy.transport_sockets.tcp_stats": "//source/extensions/transport_sockets/tcp_stats:config", + "envoy.transport_sockets.tls": "//source/extensions/transport_sockets/tls:config", + "envoy.transport_sockets.internal_upstream": "//source/extensions/transport_sockets/internal_upstream:config", + + # + # Retry host predicates + # + + "envoy.retry_host_predicates.previous_hosts": "//source/extensions/retry/host/previous_hosts:config", + "envoy.retry_host_predicates.omit_canary_hosts": "//source/extensions/retry/host/omit_canary_hosts:config", + "envoy.retry_host_predicates.omit_host_metadata": "//source/extensions/retry/host/omit_host_metadata:config", + + # + # Retry priorities + # + + "envoy.retry_priorities.previous_priorities": "//source/extensions/retry/priority/previous_priorities:config", + + # + # CacheFilter plugins + # + "envoy.extensions.http.cache.file_system_http_cache": "//source/extensions/http/cache/file_system_http_cache:config", + "envoy.extensions.http.cache.simple": "//source/extensions/http/cache/simple_http_cache:config", + + # + # Internal redirect predicates + # + + "envoy.internal_redirect_predicates.allow_listed_routes": "//source/extensions/internal_redirect/allow_listed_routes:config", + "envoy.internal_redirect_predicates.previous_routes": "//source/extensions/internal_redirect/previous_routes:config", + "envoy.internal_redirect_predicates.safe_cross_scheme": "//source/extensions/internal_redirect/safe_cross_scheme:config", + + # + # Http Upstreams (excepting envoy.upstreams.http.generic which is hard-coded into the build so not registered here) + # + + "envoy.upstreams.http.http": "//source/extensions/upstreams/http/http:config", + "envoy.upstreams.http.tcp": "//source/extensions/upstreams/http/tcp:config", + "envoy.upstreams.http.dynamic_modules": "//source/extensions/upstreams/http/dynamic_modules:config", + + # + # Watchdog actions + # + + "envoy.watchdog.profile_action": "//source/extensions/watchdog/profile_action:config", + + # + # WebAssembly runtimes + # + + "envoy.wasm.runtime.null": "//source/extensions/wasm_runtime/null:config", + "envoy.wasm.runtime.v8": "//source/extensions/wasm_runtime/v8:config", + "envoy.wasm.runtime.wamr": "//source/extensions/wasm_runtime/wamr:config", + "envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config", + + # + # Rate limit descriptors + # + + "envoy.rate_limit_descriptors.expr": "//source/extensions/rate_limit_descriptors/expr:config", + + # + # IO socket + # + + "envoy.io_socket.user_space": "//source/extensions/io_socket/user_space:config", + "envoy.bootstrap.internal_listener": "//source/extensions/bootstrap/internal_listener:config", + + # + # TLS peer certification validators + # + + "envoy.tls.cert_validator.spiffe": "//source/extensions/transport_sockets/tls/cert_validator/spiffe:config", + "envoy.tls.cert_validator.dynamic_modules": "//source/extensions/transport_sockets/tls/cert_validator/dynamic_modules:config", + + # + # HTTP header formatters + # + + "envoy.http.stateful_header_formatters.preserve_case": "//source/extensions/http/header_formatters/preserve_case:config", + + # + # Original IP detection + # + + "envoy.http.original_ip_detection.custom_header": "//source/extensions/http/original_ip_detection/custom_header:config", + "envoy.http.original_ip_detection.xff": "//source/extensions/http/original_ip_detection/xff:config", + + # + # Stateful session + # + + "envoy.http.stateful_session.cookie": "//source/extensions/http/stateful_session/cookie:config", + "envoy.http.stateful_session.header": "//source/extensions/http/stateful_session/header:config", + + # + # QUIC extensions + # + + "envoy.quic.deterministic_connection_id_generator": "//source/extensions/quic/connection_id_generator/deterministic:envoy_deterministic_connection_id_generator_config", + "envoy.quic.crypto_stream.server.quiche": "//source/extensions/quic/crypto_stream:envoy_quic_default_crypto_server_stream", + "envoy.quic.proof_source.filter_chain": "//source/extensions/quic/proof_source:envoy_quic_default_proof_source", + + # + # UDP packet writers + # + "envoy.udp_packet_writer.default": "//source/extensions/udp_packet_writer/default:config", + "envoy.udp_packet_writer.gso": "//source/extensions/udp_packet_writer/gso:config", + + # + # Formatter + # + + "envoy.formatter.metadata": "//source/extensions/formatter/metadata:config", + "envoy.formatter.req_without_query": "//source/extensions/formatter/req_without_query:config", + "envoy.formatter.cel": "//source/extensions/formatter/cel:config", + + # + # Key value store + # + + "envoy.key_value.file_based": "//source/extensions/key_value/file_based:config_lib", + + # + # RBAC matchers + # + + "envoy.rbac.matchers.upstream_ip_port": "//source/extensions/filters/common/rbac/matchers:upstream_ip_port_lib", + + # + # DNS Resolver + # + + # c-ares DNS resolver extension is recommended to be enabled to maintain the legacy DNS resolving behavior. + "envoy.network.dns_resolver.cares": "//source/extensions/network/dns_resolver/cares:config", + # apple DNS resolver extension is only needed in MacOS build plus one want to use apple library for DNS resolving. + "envoy.network.dns_resolver.apple": "//source/extensions/network/dns_resolver/apple:config", + + # + # Custom matchers + # + + "envoy.matching.custom_matchers.ip_range_matcher": "//source/extensions/common/matcher:ip_range_matcher_lib", + + # + # Header Validators + # + + "envoy.http.header_validators.envoy_default": "//source/extensions/http/header_validators/envoy_default:config", + + # + # Load balancing policies for upstream + # + "envoy.load_balancing_policies.least_request": "//source/extensions/load_balancing_policies/least_request:config", + "envoy.load_balancing_policies.random": "//source/extensions/load_balancing_policies/random:config", + "envoy.load_balancing_policies.round_robin": "//source/extensions/load_balancing_policies/round_robin:config", + "envoy.load_balancing_policies.maglev": "//source/extensions/load_balancing_policies/maglev:config", + "envoy.load_balancing_policies.ring_hash": "//source/extensions/load_balancing_policies/ring_hash:config", + "envoy.load_balancing_policies.subset": "//source/extensions/load_balancing_policies/subset:config", + "envoy.load_balancing_policies.cluster_provided": "//source/extensions/load_balancing_policies/cluster_provided:config", + "envoy.load_balancing_policies.override_host": "//source/extensions/load_balancing_policies/override_host:config", + "envoy.load_balancing_policies.client_side_weighted_round_robin": "//source/extensions/load_balancing_policies/client_side_weighted_round_robin:config", + "envoy.load_balancing_policies.dynamic_modules": "//source/extensions/load_balancing_policies/dynamic_modules:config", + + # + # HTTP Early Header Mutation + # + "envoy.http.early_header_mutation.header_mutation": "//source/extensions/http/early_header_mutation/header_mutation:config", + + # + # Config Subscription + # + "envoy.config_subscription.rest": "//source/extensions/config_subscription/rest:http_subscription_lib", + "envoy.config_subscription.filesystem": "//source/extensions/config_subscription/filesystem:filesystem_subscription_lib", + "envoy.config_subscription.filesystem_collection": "//source/extensions/config_subscription/filesystem:filesystem_subscription_lib", + "envoy.config_subscription.grpc": "//source/extensions/config_subscription/grpc:grpc_subscription_lib", + "envoy.config_subscription.delta_grpc": "//source/extensions/config_subscription/grpc:grpc_subscription_lib", + "envoy.config_subscription.ads": "//source/extensions/config_subscription/grpc:grpc_subscription_lib", + "envoy.config_subscription.aggregated_grpc_collection": "//source/extensions/config_subscription/grpc:grpc_collection_subscription_lib", + "envoy.config_subscription.aggregated_delta_grpc_collection": "//source/extensions/config_subscription/grpc:grpc_collection_subscription_lib", + "envoy.config_subscription.ads_collection": "//source/extensions/config_subscription/grpc:grpc_collection_subscription_lib", + + # + # cluster specifier plugin + # + + "envoy.router.cluster_specifier_plugin.lua": "//source/extensions/router/cluster_specifiers/lua:config", + "envoy.router.cluster_specifier_plugin.matcher": "//source/extensions/router/cluster_specifiers/matcher:config", + + # + # Injected credentials + # + + "envoy.http.injected_credentials.generic": "//source/extensions/http/injected_credentials/generic:config", + "envoy.http.injected_credentials.oauth2": "//source/extensions/http/injected_credentials/oauth2:config", +} + +ENVOY_CONTRIB_EXTENSIONS = { + # + # HTTP filters + # + + "envoy.filters.http.dynamo": "//contrib/dynamo/filters/http/dynamo:config", + "envoy.filters.http.golang": "//contrib/golang/filters/http/source:config", + "envoy.filters.http.squash": "//contrib/squash/filters/http/source:config", + "envoy.filters.http.sxg": "//contrib/sxg/filters/http/source:config", + + # + # Network filters + # + + "envoy.filters.network.client_ssl_auth": "//contrib/client_ssl_auth/filters/network/source:config", + "envoy.filters.network.golang": "//contrib/golang/filters/network/source:config", + "envoy.filters.network.kafka_broker": "//contrib/kafka/filters/network/source:kafka_broker_config_lib", + "envoy.filters.network.kafka_mesh": "//contrib/kafka/filters/network/source/mesh:config_lib", + "envoy.filters.network.mysql_proxy": "//contrib/mysql_proxy/filters/network/source:config", + "envoy.filters.network.postgres_proxy": "//contrib/postgres_proxy/filters/network/source:config", + "envoy.filters.network.rocketmq_proxy": "//contrib/rocketmq_proxy/filters/network/source:config", + + # + # Sip proxy + # + + "envoy.filters.network.sip_proxy": "//contrib/sip_proxy/filters/network/source:config", + "envoy.filters.sip.router": "//contrib/sip_proxy/filters/network/source/router:config", + + # + # Private key providers + # + + "envoy.tls.key_providers.cryptomb": "//contrib/cryptomb/private_key_providers/source:config", + "envoy.tls.key_providers.qat": "//contrib/qat/private_key_providers/source:config", + + # + # Socket interface extensions + # + + "envoy.bootstrap.vcl": "//contrib/vcl/source:config", + + # + # Connection Balance extensions + # + + "envoy.network.connection_balance.dlb": "//contrib/dlb/source:connection_balancer", + + # + # Peak EWMA Loadbalancer + # + "envoy.load_balancing_policies.peak_ewma": "//contrib/peak_ewma/load_balancing_policies/source:config", + "envoy.filters.http.peak_ewma": "//contrib/peak_ewma/filters/http/source:config", +} + + +ISTIO_DISABLED_EXTENSIONS = [ + # ISTIO disable tcp_stats by default because this plugin must be built and running on kernel >= 4.6 + "envoy.transport_sockets.tcp_stats", +] + +ISTIO_ENABLED_CONTRIB_EXTENSIONS = [ + "envoy.filters.http.golang", + "envoy.filters.network.golang", + "envoy.filters.network.mysql_proxy", + "envoy.filters.network.postgres_proxy", + "envoy.filters.network.sip_proxy", + "envoy.filters.sip.router", + "envoy.tls.key_providers.cryptomb", + # "envoy.tls.key_providers.qat", + "envoy.network.connection_balance.dlb", + "envoy.load_balancing_policies.peak_ewma", + "envoy.filters.http.peak_ewma", +] + +EXTENSIONS = dict([(k,v) for k,v in ENVOY_EXTENSIONS.items() if not k in ISTIO_DISABLED_EXTENSIONS] + + [(k,v) for k, v in ENVOY_CONTRIB_EXTENSIONS.items() if k in ISTIO_ENABLED_CONTRIB_EXTENSIONS]) + + +# These can be changed to ["//visibility:public"], for downstream builds which +# need to directly reference Envoy extensions. +EXTENSION_CONFIG_VISIBILITY = ["//visibility:public"] +EXTENSION_PACKAGE_VISIBILITY = ["//visibility:public"] +CONTRIB_EXTENSION_PACKAGE_VISIBILITY = ["//visibility:public"] +MOBILE_PACKAGE_VISIBILITY = ["//:mobile_library"] + +LEGACY_ALWAYSLINK = True diff --git a/bazel/platform_mappings b/bazel/platform_mappings new file mode 100644 index 00000000000..b3d228f9add --- /dev/null +++ b/bazel/platform_mappings @@ -0,0 +1,36 @@ +flags: + --cpu=arm64-v8a + --crosstool_top=//external:android/crosstool + @envoy//bazel:android_aarch64 + + --cpu=armeabi-v7a + --crosstool_top=//external:android/crosstool + @envoy//bazel:android_armeabi + + --cpu=x86 + --crosstool_top=//external:android/crosstool + @envoy//bazel:android_x86 + + --cpu=x86_64 + --crosstool_top=//external:android/crosstool + @envoy//bazel:android_x86_64 + + --cpu=darwin_x86_64 + --apple_platform_type=macos + @envoy//bazel:macos_x86_64 + + --cpu=darwin_arm64 + --apple_platform_type=macos + @envoy//bazel:macos_arm64 + + --cpu=ios_x86_64 + --apple_platform_type=ios + @envoy//bazel:ios_x86_64_platform + + --cpu=ios_sim_arm64 + --apple_platform_type=ios + @envoy//bazel:ios_sim_arm64 + + --cpu=ios_arm64 + --apple_platform_type=ios + @envoy//bazel:ios_arm64_platform \ No newline at end of file diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 00000000000..ee3af816197 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,8 @@ +# buf.gen.yaml sets up the generation configuration for all of our plugins. +# Note: buf does not allow multi roots that are within each other; as a result, the common-protos folders are +# symlinked into the top level directory. +version: v1 +plugins: +- name: docs + out: . + opt: camel_case_fields=false,warnings=false,per_file=true,mode=html_fragment_with_front_matter diff --git a/cc_gogo_protobuf.bzl b/cc_gogo_protobuf.bzl deleted file mode 100644 index 4f549fd2e29..00000000000 --- a/cc_gogo_protobuf.bzl +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") - -def cc_gogoproto_repositories(bind=True): - BUILD = """ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -licenses(["notice"]) - -load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library") - -exports_files(glob(["google/**"])) - -cc_proto_library( - name = "cc_gogoproto", - srcs = [ - "gogoproto/gogo.proto", - ], - include = ".", - default_runtime = "//external:protobuf", - protoc = "//external:protoc", - visibility = ["//visibility:public"], - deps = [ - "//external:cc_wkt_protos", - ], -) -""" - new_git_repository( - name = "gogoproto_git", - commit = "100ba4e885062801d56799d78530b73b178a78f3", - remote = "https://github.com/gogo/protobuf", - build_file_content = BUILD, - ) - - if bind: - native.bind( - name = "cc_gogoproto", - actual = "@gogoproto_git//:cc_gogoproto", - ) - - native.bind( - name = "cc_gogoproto_genproto", - actual = "@gogoproto_git//:cc_gogoproto_genproto", - ) diff --git a/common-protos/google/protobuf/duration.proto b/common-protos/google/protobuf/duration.proto new file mode 100644 index 00000000000..99cb102c353 --- /dev/null +++ b/common-protos/google/protobuf/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/duration"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/common-protos/google/protobuf/wrappers.proto b/common-protos/google/protobuf/wrappers.proto new file mode 100644 index 00000000000..9ee41e384ac --- /dev/null +++ b/common-protos/google/protobuf/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/wrappers"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/common/.commonfiles.sha b/common/.commonfiles.sha new file mode 100644 index 00000000000..d73250cf227 --- /dev/null +++ b/common/.commonfiles.sha @@ -0,0 +1 @@ +0b9117247a6b1a9b5478c016a28d78125c1d6058 diff --git a/common/Makefile.common.mk b/common/Makefile.common.mk new file mode 100644 index 00000000000..6e9b85bb0f8 --- /dev/null +++ b/common/Makefile.common.mk @@ -0,0 +1,126 @@ +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FINDFILES=find . \( -path ./common-protos -o -path ./.git -o -path ./out -o -path ./.github -o -path ./licenses -o -path ./vendor $(if $(strip ${FINDFILES_IGNORE}), -o ${FINDFILES_IGNORE}) \) -prune -o -type f + +XARGS = xargs -0 -r + +lint-dockerfiles: + @${FINDFILES} -name 'Dockerfile*' -print0 | ${XARGS} hadolint -c ./common/config/.hadolint.yml + +lint-scripts: + @${FINDFILES} -name '*.sh' -print0 | ${XARGS} -n 256 shellcheck + +lint-yaml: + @${FINDFILES} \( -name '*.yml' -o -name '*.yaml' \) -not -exec grep -q -e "{{" {} \; -print0 | ${XARGS} yamllint -c ./common/config/.yamllint.yml + +lint-helm: + @${FINDFILES} -name 'Chart.yaml' -print0 | ${XARGS} -L 1 dirname | xargs -r helm lint --strict + +lint-copyright-banner: + @${FINDFILES} \( -name '*.go' -o -name '*.cc' -o -name '*.h' -o -name '*.proto' -o -name '*.py' -o -name '*.sh' -o -name '*.rs' \) \( ! \( -name '*.gen.go' -o -name '*.pb.go' -o -name '*_pb2.py' \) \) -print0 |\ + ${XARGS} common/scripts/lint_copyright_banner.sh + +fix-copyright-banner: + @${FINDFILES} \( -name '*.go' -o -name '*.cc' -o -name '*.h' -o -name '*.proto' -o -name '*.py' -o -name '*.sh' -o -name '*.rs' \) \( ! \( -name '*.gen.go' -o -name '*.pb.go' -o -name '*_pb2.py' \) \) -print0 |\ + ${XARGS} common/scripts/fix_copyright_banner.sh + +lint-go: + @${FINDFILES} -name '*.go' \( ! \( -name '*.gen.go' -o -name '*.pb.go' \) \) -print0 | ${XARGS} common/scripts/lint_go.sh + +lint-python: + @${FINDFILES} -name '*.py' \( ! \( -name '*_pb2.py' \) \) -print0 | ${XARGS} autopep8 --max-line-length 160 --exit-code -d + +lint-markdown: + @${FINDFILES} -name '*.md' -not -path './manifests/addons/dashboards/*' -print0 | ${XARGS} mdl --ignore-front-matter --style common/config/mdl.rb + +lint-links: + @${FINDFILES} -name '*.md' -print0 | ${XARGS} awesome_bot --skip-save-results --allow_ssl --allow-timeout --allow-dupe --allow-redirect --white-list ${MARKDOWN_LINT_ALLOWLIST} + +lint-sass: + @${FINDFILES} -name '*.scss' -print0 | ${XARGS} sass-lint -c common/config/sass-lint.yml --verbose + +lint-typescript: + @${FINDFILES} -name '*.ts' -print0 | ${XARGS} tslint -c common/config/tslint.json + +lint-licenses: + @if test -d licenses; then license-lint --config common/config/license-lint.yml; fi + +lint-all: lint-dockerfiles lint-scripts lint-yaml lint-helm lint-copyright-banner lint-go lint-python lint-markdown lint-sass lint-typescript lint-licenses + +tidy-go: + @find -name go.mod -execdir go mod tidy \; + +mod-download-go: + @-GOFLAGS="-mod=readonly" find -name go.mod -execdir go mod download \; +# go mod tidy is needed with Golang 1.16+ as go mod download affects go.sum +# https://github.com/golang/go/issues/43994 + @find -name go.mod -execdir go mod tidy \; + +format-go: tidy-go + @${FINDFILES} -name '*.go' \( ! \( -name '*.gen.go' -o -name '*.pb.go' \) \) -print0 | ${XARGS} common/scripts/format_go.sh + +format-python: + @${FINDFILES} -name '*.py' -print0 | ${XARGS} autopep8 --max-line-length 160 --aggressive --aggressive -i + +dump-licenses: mod-download-go + @license-lint --config common/config/license-lint.yml --report + +dump-licenses-csv: mod-download-go + @license-lint --config common/config/license-lint.yml --csv + +mirror-licenses: mod-download-go + @rm -fr licenses + @license-lint --mirror + +TMP := $(shell mktemp -d -u) +UPDATE_BRANCH ?= "master" + +BUILD_TOOLS_ORG ?= "istio" + +update-common: + @mkdir -p $(TMP) + @git clone -q --depth 1 --single-branch --branch $(UPDATE_BRANCH) https://github.com/$(BUILD_TOOLS_ORG)/common-files $(TMP)/common-files + @cd $(TMP)/common-files ; git rev-parse HEAD >files/common/.commonfiles.sha + @rm -fr common +# istio/community has its own CONTRIBUTING.md file. + @CONTRIB_OVERRIDE=$(shell grep -l "istio/community/blob/master/CONTRIBUTING.md" CONTRIBUTING.md) + @if [ "$(CONTRIB_OVERRIDE)" != "CONTRIBUTING.md" ]; then\ + rm $(TMP)/common-files/files/CONTRIBUTING.md;\ + fi + @cp -a $(TMP)/common-files/files/* $(TMP)/common-files/files/.devcontainer $(TMP)/common-files/files/.gitattributes $(shell pwd) + @rm -fr $(TMP)/common-files + @if [ "$(AUTOMATOR_REPO)" == "proxy" ]; then\ + sed -i -e 's/build-tools:/build-tools-proxy:/g' .devcontainer/devcontainer.json;\ + fi + @$(or $(COMMONFILES_POSTPROCESS), true) + +check-clean-repo: + @common/scripts/check_clean_repo.sh + +tidy-docker: + @docker image prune --all --force --filter="label=io.istio.repo=https://github.com/istio/tools" --filter="label!=io.istio.version=$(IMAGE_VERSION)" + +# help works by looking over all Makefile includes matching `target: ## comment` regex and outputting them +help: ## Show this help + @egrep -h '^[a-zA-Z_\.-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: lint-dockerfiles lint-scripts lint-yaml lint-copyright-banner lint-go lint-python lint-helm lint-markdown lint-sass lint-typescript lint-all format-go format-python update-common lint-licenses dump-licenses dump-licenses-csv check-clean-repo tidy-docker help tidy-go mod-download-go diff --git a/common/config/.golangci.yml b/common/config/.golangci.yml new file mode 100644 index 00000000000..b9b6f68bf85 --- /dev/null +++ b/common/config/.golangci.yml @@ -0,0 +1,228 @@ +version: "2" +run: + build-tags: + - integ + - integfuzz +linters: + default: none + enable: + - copyloopvar + - depguard + - errcheck + - gocritic + - gosec + - govet + - ineffassign + - lll + - misspell + - revive + - staticcheck + - unconvert + - unparam + - unused + settings: + depguard: + rules: + DenyGogoProtobuf: + files: + - $all + deny: + - pkg: github.com/gogo/protobuf + desc: gogo/protobuf is deprecated, use golang/protobuf + errcheck: + check-type-assertions: false + check-blank: false + gocritic: + disable-all: true + enabled-checks: + - appendCombine + - argOrder + - assignOp + - badCond + - boolExprSimplify + - builtinShadow + - captLocal + - caseOrder + - codegenComment + - commentedOutCode + - commentedOutImport + - defaultCaseOrder + - deprecatedComment + - docStub + - dupArg + - dupBranchBody + - dupCase + - dupSubExpr + - elseif + - emptyFallthrough + - equalFold + - flagDeref + - flagName + - hexLiteral + - indexAlloc + - initClause + - methodExprCall + - nilValReturn + - octalLiteral + - offBy1 + - rangeExprCopy + - regexpMust + - sloppyLen + - stringXbytes + - switchTrue + - typeAssertChain + - typeSwitchVar + - typeUnparen + - underef + - unlambda + - unnecessaryBlock + - unslice + - valSwap + - weakCond + gosec: + includes: + - G401 + - G402 + - G404 + govet: + disable: + - shadow + lll: + line-length: 160 + tab-width: 1 + misspell: + locale: US + ignore-rules: + - cancelled + revive: + confidence: 0 + severity: warning + rules: + - name: blank-imports + - name: context-keys-type + - name: time-naming + - name: var-declaration + - name: unexported-return + - name: errorf + - name: context-as-argument + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: increment-decrement + - name: var-naming + - name: package-comments + - name: range + - name: receiver-naming + - name: indent-error-flow + - name: superfluous-else + - name: modifies-parameter + - name: unreachable-code + - name: struct-tag + - name: constant-logical-expr + - name: bool-literal-in-expr + - name: redefines-builtin-id + - name: imports-blocklist + - name: range-val-in-closure + - name: range-val-address + - name: waitgroup-by-value + - name: atomic + - name: call-to-gc + - name: duplicated-imports + - name: string-of-int + - name: defer + arguments: + - - call-chain + - name: unconditional-recursion + - name: identical-branches + unparam: + check-exported: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - errcheck + - maligned + path: _test\.go$|tests/|samples/ + - path: _test\.go$ + text: 'dot-imports: should not use dot imports' + - linters: + - staticcheck + text: 'SA1019: package github.com/golang/protobuf/jsonpb' + - linters: + - staticcheck + text: 'SA1019: "github.com/golang/protobuf/jsonpb"' + - linters: + - staticcheck + text: 'SA1019: grpc.Dial is deprecated: use NewClient instead' + - linters: + - staticcheck + text: 'SA1019: grpc.DialContext is deprecated: use NewClient instead' + - linters: + - staticcheck + text: 'SA1019: grpc.WithBlock is deprecated' + - linters: + - staticcheck + text: 'SA1019: grpc.FailOnNonTempDialError' + - linters: + - staticcheck + text: 'SA1019: grpc.WithReturnConnectionError' + - path: (.+)\.go$ + text: composite literal uses unkeyed fields + # TODO: remove following rule in the future + - linters: + - staticcheck + text: 'QF' + - linters: + - staticcheck + text: 'ST1005' + - linters: + - staticcheck + text: 'S1007' + # TODO: remove once we have updated package names + - linters: + - revive + text: "var-naming: avoid meaningless package names" + - linters: + - revive + text: "var-naming: avoid package names that conflict with Go standard library package names" + paths: + - .*\.pb\.go + - .*\.gen\.go + - genfiles$ + - vendor$ + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gci + - gofumpt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(istio.io/) + goimports: + local-prefixes: + - istio.io/ + exclusions: + generated: lax + paths: + - .*\.pb\.go + - .*\.gen\.go + - genfiles$ + - vendor$ + - third_party$ + - builtin$ + - examples$ diff --git a/common/config/.hadolint.yml b/common/config/.hadolint.yml new file mode 100644 index 00000000000..f900b9a18d9 --- /dev/null +++ b/common/config/.hadolint.yml @@ -0,0 +1,18 @@ +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +ignored: + - DL3008 + - DL3059 + +trustedRegistries: + - gcr.io + - docker.io + - quay.io + - registry.istio.io + - "*.pkg.dev" + - "cgr.dev" diff --git a/common/config/.yamllint.yml b/common/config/.yamllint.yml new file mode 100644 index 00000000000..c2f21b58189 --- /dev/null +++ b/common/config/.yamllint.yml @@ -0,0 +1,29 @@ +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +rules: + braces: disable + brackets: disable + colons: enable + commas: disable + comments: disable + comments-indentation: disable + document-end: disable + document-start: disable + empty-lines: disable + empty-values: disable + hyphens: enable + indentation: disable + key-duplicates: enable + key-ordering: disable + line-length: disable + new-line-at-end-of-file: disable + new-lines: enable + octal-values: disable + quoted-strings: disable + trailing-spaces: disable + truthy: disable diff --git a/common/config/license-lint.yml b/common/config/license-lint.yml new file mode 100644 index 00000000000..882533c1dd3 --- /dev/null +++ b/common/config/license-lint.yml @@ -0,0 +1,157 @@ +unrestricted_licenses: + - Apache-2.0 + - CC-BY-3.0 + - ISC + - AFL-2.1 + - AFL-3.0 + - Artistic-1.0 + - Artistic-2.0 + - Apache-1.1 + - BSD-1-Clause + - BSD-2-Clause + - BSD-3-Clause + - 0BSD + - FTL + - LPL-1.02 + - MS-PL + - MIT + - NCSA + - OpenSSL + - PHP-3.0 + - TCP-wrappers + - W3C + - Xnet + - Zlib + +reciprocal_licenses: + - CC0-1.0 + - APSL-2.0 + - CDDL-1.0 + - CDDL-1.1 + - CPL-1.0 + - EPL-1.0 + - IPL-1.0 + - MPL-1.0 + - MPL-1.1 + - MPL-2.0 + - MPL-2.0-no-copyleft-exception + - Ruby + +restricted_licenses: + - GPL-1.0-only + - GPL-1.0-or-later + - GPL-2.0-only + - GPL-2.0-or-later + - GPL-3.0-only + - GPL-3.0-or-later + - LGPL-2.0-only + - LGPL-2.0-or-later + - LGPL-2.1-only + - LGPL-2.1-or-later + - LGPL-3.0-only + - LGPL-3.0-or-later + - NPL-1.0 + - NPL-1.1 + - OSL-1.0 + - OSL-1.1 + - OSL-2.0 + - OSL-2.1 + - OSL-3.0 + - QPL-1.0 + - Sleepycat + +allowlisted_modules: +# MIT: https://github.com/ghodss/yaml/blob/master/LICENSE +- github.com/ghodss/yaml + +# BSD: https://github.com/gogo/protobuf/blob/master/LICENSE +- github.com/gogo/protobuf + +# BSD: https://github.com/magiconair/properties/blob/master/LICENSE.md +- github.com/magiconair/properties + +# Apache 2.0 +- github.com/spf13/cobra +- github.com/spf13/afero + +# Public domain: https://github.com/xi2/xz/blob/master/LICENSE +- github.com/xi2/xz + +# Helm is Apache 2.0: https://github.com/helm/helm/blob/master/LICENSE +# However, it has a bunch of LICENSE test files that our linter fails to understand +- helm.sh/helm/v3 +- helm.sh/helm/v4 + +# https://github.com/pelletier/go-toml/blob/master/LICENSE +# Uses MIT for everything, except a few files copied from +# google's civil library that uses Apache 2 +- github.com/pelletier/go-toml + +# https://github.com/xeipuuv/gojsonpointer/blob/master/LICENSE-APACHE-2.0.txt +- github.com/xeipuuv/gojsonpointer +# https://github.com/xeipuuv/gojsonreference/blob/master/LICENSE-APACHE-2.0.txt +- github.com/xeipuuv/gojsonreference +# Apache 2.0: https://github.com/xeipuuv/gojsonschema/blob/master/LICENSE-APACHE-2.0.txt +- github.com/xeipuuv/gojsonschema + +# Apache 2.0 (but missing appendix): https://github.com/garyburd/redigo/blob/master/LICENSE +- github.com/garyburd/redigo +- github.com/gomodule/redigo + +# Apache 2.0 +# github.com/ghodss/yaml: MIT / BSD-3 +# github.com/gogo/protobuf: BSD-3 +# github.com/jmespath/go-jmespath: Apache 2.0 +# sigs.k8s.io/yaml: MIT / BSD-3 +- github.com/tektoncd/pipeline + +# MIT: https://github.com/kubernetes-sigs/yaml/blob/master/LICENSE +- sigs.k8s.io/yaml + +# https://github.com/go-errors/errors/blob/master/LICENSE.MIT +- github.com/go-errors/errors + +# runc is Apache 2.0: https://github.com/opencontainers/runc/blob/master/LICENSE +# but it contains BSD dep which our linter fails to understand: https://github.com/opencontainers/runc/blob/v0.1.1/Godeps/_workspace/src/github.com/golang/protobuf/LICENSE +- github.com/opencontainers/runc + +# MIT: https://github.com/felixge/fgprof/blob/master/LICENSE.txt +- github.com/felixge/fgprof + +# Apache 2.0 +- github.com/google/pprof + +# MIT: https://github.com/invopop/yaml/blob/v0.1.0/LICENSE +- github.com/invopop/yaml + +# Simplified BSD (BSD-2-Clause): https://github.com/russross/blackfriday/blob/master/LICENSE.txt +- github.com/russross/blackfriday +- github.com/russross/blackfriday/v2 + +# W3C Test Suite License, W3C 3-clause BSD License +# gonum uses this for its some of its test files +# gonum.org/v1/gonum/graph/formats/rdf/testdata/LICENSE.md +- gonum.org/v1/gonum + +# BSD 3-clause: https://github.com/go-inf/inf/blob/v0.9.1/LICENSE +- gopkg.in/inf.v0 + +# BSD 3-clause: https://github.com/go-git/gcfg/blob/main/LICENSE +- github.com/go-git/gcfg + +# Apache 2.0 +- github.com/aws/smithy-go + +# Simplified BSD License: https://github.com/gomarkdown/markdown/blob/master/LICENSE.txt +- github.com/gomarkdown/markdown + +# MPL-2.0 +# https://github.com/cyphar/filepath-securejoin/blob/main/LICENSE.MPL-2.0 +# BSD +# https://github.com/cyphar/filepath-securejoin/blob/main/LICENSE.BSD +- github.com/cyphar/filepath-securejoin + +# The code is MIT: https://github.com/quic-go/quic-go/blob/master/LICENSE +# However, the logo is not https://github.com/quic-go/quic-go/blob/master/assets/LICENSE.md. +# However, we do not consume the logo. +- github.com/quic-go/quic-go \ No newline at end of file diff --git a/common/config/mdl.rb b/common/config/mdl.rb new file mode 100644 index 00000000000..8764f94d726 --- /dev/null +++ b/common/config/mdl.rb @@ -0,0 +1,12 @@ +all +rule 'MD002', :level => 1 +rule 'MD007', :indent => 4 +rule 'MD013', :line_length => 160, :code_blocks => false, :tables => false +rule 'MD026', :punctuation => ".,;:!" +exclude_rule 'MD013' +exclude_rule 'MD014' +exclude_rule 'MD030' +exclude_rule 'MD032' +exclude_rule 'MD033' +exclude_rule 'MD041' +exclude_rule 'MD046' diff --git a/common/config/sass-lint.yml b/common/config/sass-lint.yml new file mode 100644 index 00000000000..da43ee79c46 --- /dev/null +++ b/common/config/sass-lint.yml @@ -0,0 +1,98 @@ +######################### +## Config for sass-lint +######################### +# Linter Options +options: + # Don't merge default rules + merge-default-rules: false + # Raise an error if more than 50 warnings are generated + max-warnings: 500 +# Rule Configuration +rules: + attribute-quotes: + - 2 + - + include: false + bem-depth: 2 + border-zero: 2 + brace-style: 2 + class-name-format: 2 + clean-import-paths: 2 + declarations-before-nesting: 2 + empty-args: 2 + empty-line-between-blocks: 2 + extends-before-declarations: 2 + extends-before-mixins: 2 + final-newline: 2 + force-attribute-nesting: 0 + force-element-nesting: 0 + force-pseudo-nesting: 0 + function-name-format: 2 + hex-length: 0 + hex-notation: 2 + id-name-format: 2 + indentation: + - 2 + - + size: 4 + leading-zero: + - 2 + - + include: false + max-file-line-count: 0 + max-file-length: 0 + mixins-before-declarations: 2 + no-attribute-selectors: 0 + no-color-hex: 0 + no-color-keywords: 0 + no-color-literals: 0 + no-combinators: 0 + no-css-comments: 2 + no-debug: 2 + no-disallowed-properties: 2 + no-duplicate-properties: 2 + no-empty-rulesets: 2 + no-extends: 2 + no-ids: 0 + no-invalid-hex: 2 + no-important: 0 + no-mergeable-selectors: 2 + no-misspelled-properties: 2 + no-qualifying-elements: 0 + no-trailing-whitespace: 2 + no-trailing-zero: 2 + no-transition-all: 0 + no-url-domains: 2 + no-url-protocols: 2 + no-warn: 2 + one-declaration-per-line: 2 + placeholder-in-extend: 2 + placeholder-name-format: 2 + property-sort-order: 0 + property-units: 2 + pseudo-element: 2 + quotes: + - 2 + - + style: double + shorthand-values: 2 + single-line-per-selector: 0 + space-after-bang: 2 + space-after-colon: 2 + space-after-comma: 2 + space-around-operator: 2 + space-before-bang: 2 + space-before-brace: 2 + space-before-colon: 2 + space-between-parens: 2 + trailing-semicolon: 2 + url-quotes: 2 + variable-for-property: + - 0 + - + properties: + - color + - background-color + - fill + variable-name-format: 0 + zero-unit: 2 diff --git a/common/config/tslint.json b/common/config/tslint.json new file mode 100644 index 00000000000..6db4bb19d5f --- /dev/null +++ b/common/config/tslint.json @@ -0,0 +1,25 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "rules": { + "max-line-length": { + "options": [160] + }, + "arrow-parens": false, + "new-parens": true, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": true, + "no-console": { + "severity": "warning", + "options": ["debug", "info", "log", "time", "timeEnd", "trace"] + }, + "no-shadowed-variable": false, + "eofline": false + }, + "jsRules": {}, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/common/scripts/check_clean_repo.sh b/common/scripts/check_clean_repo.sh new file mode 100755 index 00000000000..ff40000244c --- /dev/null +++ b/common/scripts/check_clean_repo.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Copyright Istio Authors + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +function write_patch_file() { + if [ -z "${ARTIFACTS}" ]; then + return 0 + fi + + PATCH_NAME="check-clean-repo-diff.patch" + PATCH_OUT="${ARTIFACTS}/${PATCH_NAME}" + git diff > "${PATCH_OUT}" + + [ -n "${JOB_NAME}" ] && [ -n "${BUILD_ID}" ] + # shellcheck disable=SC2319 + IN_PROW="$?" + + # Don't persist large diffs (30M+) on CI + LARGE_FILE="$(find "${ARTIFACTS}" -name "${PATCH_NAME}" -type 'f' -size +30M)" + if [ "${IN_PROW}" -eq 0 ] && [ -n "${LARGE_FILE}" ]; then + rm "${PATCH_OUT}" + echo "WARNING: patch file was too large to persist ($(du -h "${PATCH_OUT}"))" + return 0 + fi + outName="artifacts/${PATCH_OUT#"${ARTIFACTS}"/}" + patchFile="${PROW_ARTIFACTS_BASE:-https://gcsweb.istio.io/gcs/istio-prow}/pr-logs/pull/${REPO_OWNER}_${REPO_NAME}/${PULL_NUMBER}/${JOB_NAME}/${BUILD_ID}/${outName}" + echo "You can also try applying the patch file from the build artifacts: + +git apply <(curl -sL \"${patchFile}\") +" +} + +if [[ -n $(git status --porcelain) ]]; then + git status + git diff + echo "ERROR: Some files need to be updated, please run 'make gen' and include any changed files in your PR" + write_patch_file + exit 1 +fi diff --git a/common/scripts/copyright-banner-go.txt b/common/scripts/copyright-banner-go.txt new file mode 100644 index 00000000000..ffac692d1bc --- /dev/null +++ b/common/scripts/copyright-banner-go.txt @@ -0,0 +1,14 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + diff --git a/common/scripts/fix_copyright_banner.sh b/common/scripts/fix_copyright_banner.sh new file mode 100755 index 00000000000..92d64e75899 --- /dev/null +++ b/common/scripts/fix_copyright_banner.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +WD=$(dirname "$0") +WD=$(cd "$WD"; pwd) + +for fn in "$@"; do + if ! grep -L -q -e "Apache License, Version 2" -e "Copyright" "${fn}"; then + if [[ "${fn}" == *.go || "${fn}" == *.rs ]]; then + newfile=$(cat "${WD}/copyright-banner-go.txt" "${fn}") + echo "${newfile}" > "${fn}" + echo "Fixing license: ${fn}" + else + echo "Cannot fix license: ${fn}. Unknown file type" + fi + fi +done diff --git a/common/scripts/format_go.sh b/common/scripts/format_go.sh new file mode 100755 index 00000000000..cc7cd958295 --- /dev/null +++ b/common/scripts/format_go.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +golangci-lint run --fix -c ./common/config/.golangci.yml diff --git a/common/scripts/gobuild.sh b/common/scripts/gobuild.sh new file mode 100755 index 00000000000..25f2c8120d0 --- /dev/null +++ b/common/scripts/gobuild.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script builds and version stamps the output + +VERBOSE=${VERBOSE:-"0"} +V="" +if [[ "${VERBOSE}" == "1" ]];then + V="-x" + set -x +fi + +SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +OUT=${1:?"output path"} +shift + +set -e + +export BUILD_GOOS=${GOOS:-linux} +export BUILD_GOARCH=${GOARCH:-amd64} +GOBINARY=${GOBINARY:-go} +GOPKG="$GOPATH/pkg" +BUILDINFO=${BUILDINFO:-""} +STATIC=${STATIC:-1} +LDFLAGS=${LDFLAGS:--extldflags -static} +GOBUILDFLAGS=${GOBUILDFLAGS:-""} +# Split GOBUILDFLAGS by spaces into an array called GOBUILDFLAGS_ARRAY. +IFS=' ' read -r -a GOBUILDFLAGS_ARRAY <<< "$GOBUILDFLAGS" + +GCFLAGS=${GCFLAGS:-} +export CGO_ENABLED=${CGO_ENABLED:-0} + +if [[ "${STATIC}" != "1" ]];then + LDFLAGS="" +fi + +# gather buildinfo if not already provided +# For a release build BUILDINFO should be produced +# at the beginning of the build and used throughout +if [[ -z ${BUILDINFO} ]];then + BUILDINFO=$(mktemp) + "${SCRIPTPATH}/report_build_info.sh" > "${BUILDINFO}" +fi + +# BUILD LD_EXTRAFLAGS +LD_EXTRAFLAGS="" + +while read -r line; do + LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X ${line}" +done < "${BUILDINFO}" + +OPTIMIZATION_FLAGS=(-trimpath) +if [ "${DEBUG}" == "1" ]; then + OPTIMIZATION_FLAGS=() +fi + +time GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} ${GOBINARY} build \ + ${V} "${GOBUILDFLAGS_ARRAY[@]}" ${GCFLAGS:+-gcflags "${GCFLAGS}"} \ + -o "${OUT}" \ + "${OPTIMIZATION_FLAGS[@]}" \ + -pkgdir="${GOPKG}/${BUILD_GOOS}_${BUILD_GOARCH}" \ + -ldflags "${LDFLAGS} ${LD_EXTRAFLAGS}" "${@}" diff --git a/common/scripts/kind_provisioner.sh b/common/scripts/kind_provisioner.sh new file mode 100644 index 00000000000..3a414d7d528 --- /dev/null +++ b/common/scripts/kind_provisioner.sh @@ -0,0 +1,489 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +set -x + +# The purpose of this file is to unify prow/lib.sh in both istio and istio.io +# repos to avoid code duplication. + +#################################################################### +################# COMMON SECTION ############################### +#################################################################### + +# DEFAULT_KIND_IMAGE is used to set the Kubernetes version for KinD unless overridden in params to setup_kind_cluster(s) +DEFAULT_KIND_IMAGE="registry.istio.io/testing/kind-node:v1.35.0" + +# the default kind cluster should be ipv4 if not otherwise specified +KIND_IP_FAMILY="${KIND_IP_FAMILY:-ipv4}" + +# COMMON_SCRIPTS contains the directory this file is in. +COMMON_SCRIPTS=$(dirname "${BASH_SOURCE:-$0}") + +function log() { + echo -e "$(date -u '+%Y-%m-%dT%H:%M:%S.%NZ')\t$*" +} + +function retry() { + local n=1 + local max=5 + local delay=5 + while true; do + "$@" && break + if [[ $n -lt $max ]]; then + ((n++)) + log "Command failed. Attempt $n/$max:" + sleep $delay; + else + log "The command has failed after $n attempts." >&2 + return 2 + fi + done +} + +# load_cluster_topology function reads cluster configuration topology file and +# sets up environment variables used by other functions. So this should be called +# before anything else. +# +# Note: Cluster configuration topology file specifies basic configuration of each +# KinD cluster like its name, pod and service subnets and network_id. If two cluster +# have the same network_id then they belong to the same network and their pods can +# talk to each other directly. +# +# [{ "cluster_name": "cluster1","pod_subnet": "10.10.0.0/16","svc_subnet": "10.255.10.0/24","network_id": "0" }, +# { "cluster_name": "cluster2","pod_subnet": "10.20.0.0/16","svc_subnet": "10.255.20.0/24","network_id": "0" }, +# { "cluster_name": "cluster3","pod_subnet": "10.30.0.0/16","svc_subnet": "10.255.30.0/24","network_id": "1" }] +function load_cluster_topology() { + CLUSTER_TOPOLOGY_CONFIG_FILE="${1}" + + if [[ ! -f "${CLUSTER_TOPOLOGY_CONFIG_FILE}" ]]; then + log 'cluster topology configuration file is not specified' + exit 1 + fi + + export CLUSTER_NAMES + export CLUSTER_POD_SUBNETS + export CLUSTER_SVC_SUBNETS + export CLUSTER_NETWORK_ID + + KUBE_CLUSTERS=$(jq '.[] | select(.kind == "Kubernetes" or .kind == null)' "${CLUSTER_TOPOLOGY_CONFIG_FILE}") + + while read -r value; do + CLUSTER_NAMES+=("$value") + done < <(echo "${KUBE_CLUSTERS}" | jq -r '.cluster_name // .clusterName') + + while read -r value; do + CLUSTER_POD_SUBNETS+=("$value") + done < <(echo "${KUBE_CLUSTERS}" | jq -r '.pod_subnet // .podSubnet') + + while read -r value; do + CLUSTER_SVC_SUBNETS+=("$value") + done < <(echo "${KUBE_CLUSTERS}" | jq -r '.svc_subnet // .svcSubnet') + + while read -r value; do + CLUSTER_NETWORK_ID+=("$value") + done < <(echo "${KUBE_CLUSTERS}" | jq -r '.network_id // .network') + + export NUM_CLUSTERS + NUM_CLUSTERS=$(echo "${KUBE_CLUSTERS}" | jq -s 'length') + + echo "${CLUSTER_NAMES[@]}" + echo "${CLUSTER_POD_SUBNETS[@]}" + echo "${CLUSTER_SVC_SUBNETS[@]}" + echo "${CLUSTER_NETWORK_ID[@]}" + echo "${NUM_CLUSTERS}" +} + +##################################################################### +################### SINGLE-CLUSTER SECTION ###################### +##################################################################### + +# cleanup_kind_cluster takes a single parameter NAME +# and deletes the KinD cluster with that name +function cleanup_kind_cluster() { + echo "Test exited with exit code $?." + NAME="${1}" + kind export logs --name "${NAME}" "${ARTIFACTS}/kind" -v9 || true + if [[ -z "${SKIP_CLEANUP:-}" ]]; then + echo "Cleaning up kind cluster" + kind delete cluster --name "${NAME}" -v9 || true + fi +} + +# check_default_cluster_yaml checks the presence of default cluster YAML +# It returns 1 if it is not present +function check_default_cluster_yaml() { + if [[ -z "${DEFAULT_CLUSTER_YAML:-}" ]]; then + echo 'DEFAULT_CLUSTER_YAML file must be specified. Exiting...' + return 1 + fi +} + +function setup_kind_cluster_retry() { + retry setup_kind_cluster "$@" +} + +# setup_kind_cluster creates new KinD cluster with given name, image and configuration +# 1. NAME: Name of the Kind cluster (optional) +# 2. IMAGE: Node image used by KinD (optional) +# 3. CONFIG: KinD cluster configuration YAML file. If not specified then DEFAULT_CLUSTER_YAML is used +# 4. NOMETALBINSTALL: Dont install metalb if set. +# This function returns 0 when everything goes well, or 1 otherwise +# If Kind cluster was already created then it would be cleaned up in case of errors +function setup_kind_cluster() { + local NAME="${1:-istio-testing}" + local IMAGE="${2:-"${DEFAULT_KIND_IMAGE}"}" + local CONFIG="${3:-}" + local NOMETALBINSTALL="${4:-}" + local CLEANUP="${5:-true}" + + check_default_cluster_yaml + + # Delete any previous KinD cluster + echo "Deleting previous KinD cluster with name=${NAME}" + if ! (kind delete cluster --name="${NAME}" -v9) > /dev/null; then + echo "No existing kind cluster with name ${NAME}. Continue..." + fi + + # explicitly disable shellcheck since we actually want $NAME to expand now + # shellcheck disable=SC2064 + if [[ "${CLEANUP}" == "true" ]]; then + trap "cleanup_kind_cluster ${NAME}" EXIT + fi + + # If config not explicitly set, then use defaults + if [[ -z "${CONFIG}" ]]; then + # Kubernetes 1.15+ + CONFIG=${DEFAULT_CLUSTER_YAML} + fi + + KIND_WAIT_FLAG="--wait=180s" + KIND_DISABLE_CNI="false" + if [[ -n "${KUBERNETES_CNI:-}" ]]; then + unset KIND_WAIT_FLAG + KIND_DISABLE_CNI="true" + fi + + # Create KinD cluster + if ! (yq eval "${CONFIG}" --expression ".networking.disableDefaultCNI = ${KIND_DISABLE_CNI}" \ + --expression ".networking.ipFamily = \"${KIND_IP_FAMILY}\"" | \ + kind create cluster --name="${NAME}" -v4 --retain --image "${IMAGE}" ${KIND_WAIT_FLAG:+"$KIND_WAIT_FLAG"} --config -); then + echo "Could not setup KinD environment. Something wrong with KinD setup. Exporting logs." + return 9 + # kubectl config set clusters.kind-istio-testing.server https://istio-testing-control-plane:6443 + fi + + if [[ -n "${DEVCONTAINER:-}" ]]; then + # identify our docker container id using proc and regex + containerid=$(grep 'resolv.conf' /proc/self/mountinfo | sed 's/.*\/docker\/containers\/\([0-9a-f]*\).*/\1/') + docker network connect kind "$containerid" + kind export kubeconfig --name="${NAME}" --internal + fi + + # Workaround kind issue causing taints to not be removed in 1.24 + kubectl taint nodes "${NAME}"-control-plane node-role.kubernetes.io/control-plane- 2>/dev/null || true + + # Determine what CNI to install + case "${KUBERNETES_CNI:-}" in + + "calico") + echo "Installing Calico CNI" + install_calico "" "$(dirname "$CONFIG")" + ;; + + "") + # perfectly fine, we accepted the default KinD CNI + ;; + + *) + # we don't know what to do but we've got no CNI, return non-zero + echo "${KUBERNETES_CNI} is not recognized. Supported options are \"calico\" or do not set the variable to use default." + return 1 + ;; + esac + + # If metrics server configuration directory is specified then deploy in + # the cluster just created + if [[ -n ${METRICS_SERVER_CONFIG_DIR:-} ]]; then + retry kubectl apply -f "${METRICS_SERVER_CONFIG_DIR}" + fi + + # Install Metallb if not set to install explicitly + if [[ -z "${NOMETALBINSTALL}" ]]; then + retry install_metallb "" + fi + + # IPv6 clusters need some CoreDNS changes in order to work in CI: + # Istio CI doesn't offer IPv6 connectivity, so CoreDNS should be configured + # to work in an offline environment: + # https://github.com/coredns/coredns/issues/2494#issuecomment-457215452 + # CoreDNS should handle those domains and answer with NXDOMAIN instead of SERVFAIL + # otherwise pods stops trying to resolve the domain. + if [ "${KIND_IP_FAMILY}" = "ipv6" ] || [ "${KIND_IP_FAMILY}" = "dual" ]; then + # Get the current config + original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns) + echo "Original CoreDNS config:" + echo "${original_coredns}" + # Patch it + fixed_coredns=$( + printf '%s' "${original_coredns}" | sed \ + -e 's/^.*kubernetes cluster\.local/& internal/' \ + -e '/^.*upstream$/d' \ + -e '/^.*fallthrough.*$/d' \ + -e '/^.*forward . \/etc\/resolv.conf$/d' \ + -e '/^.*loop$/d' \ + ) + echo "Patched CoreDNS config:" + echo "${fixed_coredns}" + printf '%s' "${fixed_coredns}" | kubectl apply -f - + fi +} + +############################################################################### +#################### MULTICLUSTER SECTION ############################### +############################################################################### + +# Cleans up the clusters created by setup_kind_clusters +# It expects CLUSTER_NAMES to be present which means that +# load_cluster_topology must be called before invoking it +function cleanup_kind_clusters() { + echo "Test exited with exit code $?." + for c in "${CLUSTER_NAMES[@]}"; do + cleanup_kind_cluster "${c}" + done +} + +# setup_kind_clusters sets up a given number of kind clusters with given topology +# as specified in cluster topology configuration file. +# 1. IMAGE = docker image used as node by KinD +# 2. KIND_IP_FAMILY = either ipv4 or ipv6 or dual +# +# NOTE: Please call load_cluster_topology before calling this method as it expects +# cluster topology information to be loaded in advance +function setup_kind_clusters() { + IMAGE="${1:-"${DEFAULT_KIND_IMAGE}"}" + KUBECONFIG_DIR="${ARTIFACTS:-$(mktemp -d)}/kubeconfig" + KIND_IP_FAMILY="${2:-ipv4}" + + check_default_cluster_yaml + + # Trap replaces any previous trap's, so we need to explicitly cleanup clusters here + trap cleanup_kind_clusters EXIT + + function deploy_kind() { + IDX="${1}" + CLUSTER_NAME="${CLUSTER_NAMES[$IDX]}" + CLUSTER_POD_SUBNET="${CLUSTER_POD_SUBNETS[$IDX]}" + CLUSTER_SVC_SUBNET="${CLUSTER_SVC_SUBNETS[$IDX]}" + CLUSTER_YAML="${ARTIFACTS}/config-${CLUSTER_NAME}.yaml" + if [ ! -f "${CLUSTER_YAML}" ]; then + cp "${DEFAULT_CLUSTER_YAML}" "${CLUSTER_YAML}" + cat <> "${CLUSTER_YAML}" +networking: + podSubnet: ${CLUSTER_POD_SUBNET} + serviceSubnet: ${CLUSTER_SVC_SUBNET} +EOF + fi + + CLUSTER_KUBECONFIG="${KUBECONFIG_DIR}/${CLUSTER_NAME}" + + # Create the clusters. + KUBECONFIG="${CLUSTER_KUBECONFIG}" setup_kind_cluster "${CLUSTER_NAME}" "${IMAGE}" "${CLUSTER_YAML}" "true" "false" + + # Kind currently supports getting a kubeconfig for internal or external usage. To simplify our tests, + # its much simpler if we have a single kubeconfig that can be used internally and externally. + # To do this, we can replace the server with the IP address of the docker container + # https://github.com/kubernetes-sigs/kind/issues/1558 tracks this upstream + CONTAINER_IP=$(docker inspect "${CLUSTER_NAME}-control-plane" --format "{{ .NetworkSettings.Networks.kind.IPAddress }}") + n=0 + until [ $n -ge 10 ]; do + n=$((n+1)) + kind get kubeconfig --name "${CLUSTER_NAME}" --internal | \ + sed "s/${CLUSTER_NAME}-control-plane/${CONTAINER_IP}/g" > "${CLUSTER_KUBECONFIG}" + [ -s "${CLUSTER_KUBECONFIG}" ] && break + sleep 3 + done + + # Enable core dumps + retry docker exec "${CLUSTER_NAME}"-control-plane bash -c "sysctl -w kernel.core_pattern=/var/lib/istio/data/core.proxy && ulimit -c unlimited" + } + + # Now deploy the specified number of KinD clusters and + # wait till they are provisioned successfully. + declare -a DEPLOY_KIND_JOBS + for i in "${!CLUSTER_NAMES[@]}"; do + deploy_kind "${i}" & DEPLOY_KIND_JOBS+=("${!}") + done + + for pid in "${DEPLOY_KIND_JOBS[@]}"; do + wait "${pid}" || exit 1 + done + + # Install MetalLB for LoadBalancer support. Must be done synchronously since METALLB_IPS is shared. + # and keep track of the list of Kubeconfig files that will be exported later + export KUBECONFIGS + for CLUSTER_NAME in "${CLUSTER_NAMES[@]}"; do + KUBECONFIG_FILE="${KUBECONFIG_DIR}/${CLUSTER_NAME}" + if [[ ${NUM_CLUSTERS} -gt 1 ]]; then + retry install_metallb "${KUBECONFIG_FILE}" + fi + KUBECONFIGS+=("${KUBECONFIG_FILE}") + done + + ITER_END=$((NUM_CLUSTERS-1)) + for i in $(seq 0 "$ITER_END"); do + for j in $(seq 0 "$ITER_END"); do + if [[ "${j}" -gt "${i}" ]]; then + NETWORK_ID_I="${CLUSTER_NETWORK_ID[i]}" + NETWORK_ID_J="${CLUSTER_NETWORK_ID[j]}" + if [[ "$NETWORK_ID_I" == "$NETWORK_ID_J" ]]; then + POD_TO_POD_AND_SERVICE_CONNECTIVITY=1 + else + POD_TO_POD_AND_SERVICE_CONNECTIVITY=0 + fi + connect_kind_clusters \ + "${CLUSTER_NAMES[i]}" "${KUBECONFIGS[i]}" \ + "${CLUSTER_NAMES[j]}" "${KUBECONFIGS[j]}" \ + "${POD_TO_POD_AND_SERVICE_CONNECTIVITY}" + fi + done + done +} + +function connect_kind_clusters() { + C1="${1}" + C1_KUBECONFIG="${2}" + C2="${3}" + C2_KUBECONFIG="${4}" + POD_TO_POD_AND_SERVICE_CONNECTIVITY="${5}" + + C1_NODE="${C1}-control-plane" + C2_NODE="${C2}-control-plane" + C1_DOCKER_IP=$(docker inspect -f "{{ .NetworkSettings.Networks.kind.IPAddress }}" "${C1_NODE}") + C2_DOCKER_IP=$(docker inspect -f "{{ .NetworkSettings.Networks.kind.IPAddress }}" "${C2_NODE}") + if [ "${POD_TO_POD_AND_SERVICE_CONNECTIVITY}" -eq 1 ]; then + # Set up routing rules for inter-cluster direct pod to pod & service communication + C1_POD_CIDR=$(KUBECONFIG="${C1_KUBECONFIG}" kubectl get node -ojsonpath='{.items[0].spec.podCIDR}') + C2_POD_CIDR=$(KUBECONFIG="${C2_KUBECONFIG}" kubectl get node -ojsonpath='{.items[0].spec.podCIDR}') + C1_SVC_CIDR=$(KUBECONFIG="${C1_KUBECONFIG}" kubectl cluster-info dump | sed -n 's/^.*--service-cluster-ip-range=\([^"]*\).*$/\1/p' | head -n 1) + C2_SVC_CIDR=$(KUBECONFIG="${C2_KUBECONFIG}" kubectl cluster-info dump | sed -n 's/^.*--service-cluster-ip-range=\([^"]*\).*$/\1/p' | head -n 1) + docker exec "${C1_NODE}" ip route add "${C2_POD_CIDR}" via "${C2_DOCKER_IP}" + docker exec "${C1_NODE}" ip route add "${C2_SVC_CIDR}" via "${C2_DOCKER_IP}" + docker exec "${C2_NODE}" ip route add "${C1_POD_CIDR}" via "${C1_DOCKER_IP}" + docker exec "${C2_NODE}" ip route add "${C1_SVC_CIDR}" via "${C1_DOCKER_IP}" + fi +} + +function install_calico { + local KUBECONFIG="${1}" + local CONFIG_DIR="${2}" + + echo "Setting up ambient cluster, Calico CNI will be used." + kubectl --kubeconfig="$KUBECONFIG" apply -f "${CONFIG_DIR}"/calico.yaml + + kubectl --kubeconfig="$KUBECONFIG" wait --for condition=ready -n kube-system pod -l k8s-app=calico-node --timeout 90s + kubectl --kubeconfig="$KUBECONFIG" wait --for condition=ready -n kube-system pod -l k8s-app=calico-kube-controllers --timeout 90s +} + +function install_metallb() { + KUBECONFIG="${1}" + kubectl --kubeconfig="$KUBECONFIG" apply -f "${COMMON_SCRIPTS}/metallb-native.yaml" + kubectl --kubeconfig="$KUBECONFIG" wait -n metallb-system pod --timeout=120s -l app=metallb --for=condition=Ready + + if [ -z "${METALLB_IPS4+x}" ]; then + # Take IPs from the end of the docker kind network subnet to use for MetalLB IPs + DOCKER_KIND_SUBNET="$(docker inspect kind | jq '.[0].IPAM.Config[0].Subnet' -r)" + METALLB_IPS4=() + while read -r ip; do + METALLB_IPS4+=("$ip") + done < <(cidr_to_ips "$DOCKER_KIND_SUBNET" | tail -n 100) + METALLB_IPS6=() + if [[ "$(docker inspect kind | jq '.[0].IPAM.Config | length' -r)" == 2 ]]; then + # Two configs? Must be dual stack. + DOCKER_KIND_SUBNET="$(docker inspect kind | jq '.[0].IPAM.Config[1].Subnet' -r)" + while read -r ip; do + METALLB_IPS6+=("$ip") + done < <(cidr_to_ips "$DOCKER_KIND_SUBNET" | tail -n 100) + fi + fi + + # Give this cluster of those IPs + RANGE="[" + for i in {0..19}; do + RANGE+="${METALLB_IPS4[1]}," + METALLB_IPS4=("${METALLB_IPS4[@]:1}") + if [[ "${#METALLB_IPS6[@]}" != 0 ]]; then + RANGE+="${METALLB_IPS6[1]}," + METALLB_IPS6=("${METALLB_IPS6[@]:1}") + fi + done + RANGE="${RANGE%?}]" + + echo ' +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: default-pool + namespace: metallb-system +spec: + addresses: '"$RANGE"' +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: default-l2 + namespace: metallb-system +spec: + ipAddressPools: + - default-pool +' | kubectl apply --kubeconfig="$KUBECONFIG" -f - + +} + +function cidr_to_ips() { + CIDR="$1" + # cidr_to_ips returns a list of single IPs from a CIDR. We skip 1000 (since they are likely to be allocated + # already to other services), then pick the next 100. + python3 - < 2000: + start, end = 1000, 2000 + +[print(str(ip) + "/" + str(ip.max_prefixlen)) for ip in islice(ip_network('$CIDR').hosts(), start, end)] +EOF +} + +function ips_to_cidrs() { + IP_RANGE_START="$1" + IP_RANGE_END="$2" + python3 - < /dev/null); then + if [[ -z "${IGNORE_DIRTY_TREE}" ]] && [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then + BUILD_GIT_REVISION=${BUILD_GIT_REVISION}"-dirty" + fi +else + BUILD_GIT_REVISION=unknown +fi + +# Check for local changes +tree_status="Clean" +if [[ -z "${IGNORE_DIRTY_TREE}" ]] && ! git diff-index --quiet HEAD --; then + tree_status="Modified" +fi + +GIT_DESCRIBE_TAG=$(git describe --tags --always) +HUB=${HUB:-"registry.istio.io/release"} + +# used by common/scripts/gobuild.sh +echo "istio.io/istio/pkg/version.buildVersion=${VERSION:-$BUILD_GIT_REVISION}" +echo "istio.io/istio/pkg/version.buildGitRevision=${BUILD_GIT_REVISION}" +echo "istio.io/istio/pkg/version.buildStatus=${tree_status}" +echo "istio.io/istio/pkg/version.buildTag=${GIT_DESCRIBE_TAG}" +echo "istio.io/istio/pkg/version.buildHub=${HUB}" +echo "istio.io/istio/pkg/version.buildOS=${BUILD_GOOS}" +echo "istio.io/istio/pkg/version.buildArch=${BUILD_GOARCH}" diff --git a/common/scripts/run.sh b/common/scripts/run.sh new file mode 100755 index 00000000000..caac60e860f --- /dev/null +++ b/common/scripts/run.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +WD=$(dirname "$0") +WD=$(cd "$WD"; pwd) + +export FOR_BUILD_CONTAINER=1 +# shellcheck disable=SC1090,SC1091 +source "${WD}/setup_env.sh" + + +MOUNT_SOURCE="${MOUNT_SOURCE:-${PWD}}" +MOUNT_DEST="${MOUNT_DEST:-/work}" + +read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}" + +[[ -t 0 ]] && DOCKER_RUN_OPTIONS+=("-it") +[[ ${UID} -ne 0 ]] && DOCKER_RUN_OPTIONS+=(-u "${UID}:${DOCKER_GID}") + +# $CONTAINER_OPTIONS becomes an empty arg when quoted, so SC2086 is disabled for the +# following command only +# shellcheck disable=SC2086 +"${CONTAINER_CLI}" run \ + --rm \ + "${DOCKER_RUN_OPTIONS[@]}" \ + --init \ + --sig-proxy=true \ + --cap-add=SYS_ADMIN \ + ${DOCKER_SOCKET_MOUNT:--v /var/run/docker.sock:/var/run/docker.sock} \ + -e DOCKER_HOST=${DOCKER_SOCKET_HOST:-unix:///var/run/docker.sock} \ + $CONTAINER_OPTIONS \ + --env-file <(env | grep -v ${ENV_BLOCKLIST}) \ + -e IN_BUILD_CONTAINER=1 \ + -e TZ="${TIMEZONE:-$TZ}" \ + --mount "type=bind,source=${MOUNT_SOURCE},destination=/work" \ + --mount "type=volume,source=go,destination=/go" \ + --mount "type=volume,source=gocache,destination=/gocache" \ + --mount "type=volume,source=cache,destination=/home/.cache" \ + --mount "type=volume,source=crates,destination=/home/.cargo/registry" \ + --mount "type=volume,source=git-crates,destination=/home/.cargo/git" \ + ${CONDITIONAL_HOST_MOUNTS} \ + -w "${MOUNT_DEST}" "${IMG}" "$@" diff --git a/common/scripts/setup_env.sh b/common/scripts/setup_env.sh new file mode 100755 index 00000000000..275dad0018a --- /dev/null +++ b/common/scripts/setup_env.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# https://stackoverflow.com/questions/59895/how-can-i-get-the-source-directory-of-a-bash-script-from-within-the-script-itsel +# Note: the normal way we use in other scripts in Istio do not work when `source`d, which is why we use this approach +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +REPO_ROOT="$(dirname "$(dirname "${SCRIPT_DIR}")")" + +LOCAL_ARCH=$(uname -m) + +# Pass environment set target architecture to build system +if [[ ${TARGET_ARCH} ]]; then + # Target explicitly set + : +elif [[ ${LOCAL_ARCH} == x86_64 ]]; then + TARGET_ARCH=amd64 +elif [[ ${LOCAL_ARCH} == armv8* ]]; then + TARGET_ARCH=arm64 +elif [[ ${LOCAL_ARCH} == arm64* ]]; then + TARGET_ARCH=arm64 +elif [[ ${LOCAL_ARCH} == aarch64* ]]; then + TARGET_ARCH=arm64 +elif [[ ${LOCAL_ARCH} == armv* ]]; then + TARGET_ARCH=arm +elif [[ ${LOCAL_ARCH} == s390x ]]; then + TARGET_ARCH=s390x +elif [[ ${LOCAL_ARCH} == ppc64le ]]; then + TARGET_ARCH=ppc64le +elif [[ ${LOCAL_ARCH} == riscv64 ]]; then + TARGET_ARCH=riscv64 +else + echo "This system's architecture, ${LOCAL_ARCH}, isn't supported" + exit 1 +fi + +LOCAL_OS=$(uname) + +# Pass environment set target operating-system to build system +if [[ ${TARGET_OS} ]]; then + # Target explicitly set + : +elif [[ $LOCAL_OS == Linux ]]; then + TARGET_OS=linux + readlink_flags="-f" +elif [[ $LOCAL_OS == Darwin ]]; then + TARGET_OS=darwin + readlink_flags="" +else + echo "This system's OS, $LOCAL_OS, isn't supported" + exit 1 +fi + +# Build image to use +TOOLS_REGISTRY_PROVIDER=${TOOLS_REGISTRY_PROVIDER:-registry.istio.io} +PROJECT_ID=${PROJECT_ID:-testing} +if [[ "${IMAGE_VERSION:-}" == "" ]]; then + IMAGE_VERSION=master-63fd6eec6f3df5a3ed4e190e60153ef3425f66b4 +fi +if [[ "${IMAGE_NAME:-}" == "" ]]; then + IMAGE_NAME=build-tools +fi + +DOCKER_GID="${DOCKER_GID:-$(grep '^docker:' /etc/group | cut -f3 -d:)}" + +TIMEZONE=$(readlink "$readlink_flags" /etc/localtime | sed -e 's/^.*zoneinfo\///') + +TARGET_OUT="${TARGET_OUT:-$(pwd)/out/${TARGET_OS}_${TARGET_ARCH}}" +TARGET_OUT_LINUX="${TARGET_OUT_LINUX:-$(pwd)/out/linux_${TARGET_ARCH}}" + +CONTAINER_TARGET_OUT="${CONTAINER_TARGET_OUT:-/work/out/${TARGET_OS}_${TARGET_ARCH}}" +CONTAINER_TARGET_OUT_LINUX="${CONTAINER_TARGET_OUT_LINUX:-/work/out/linux_${TARGET_ARCH}}" + +IMG="${IMG:-${TOOLS_REGISTRY_PROVIDER}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_VERSION}}" + +CONTAINER_CLI="${CONTAINER_CLI:-docker}" + +# Try to use the latest cached image we have. Use at your own risk, may have incompatibly-old versions +if [[ "${LATEST_CACHED_IMAGE:-}" != "" ]]; then + prefix="$(<<<"$IMAGE_VERSION" cut -d- -f1)" + query="${TOOLS_REGISTRY_PROVIDER}/${PROJECT_ID}/${IMAGE_NAME}:${prefix}-*" + latest="$("${CONTAINER_CLI}" images --filter=reference="${query}" --format "{{.CreatedAt|json}}~{{.Repository}}:{{.Tag}}~{{.CreatedSince}}" | sort -n -r | head -n1)" + IMG="$(<<<"$latest" cut -d~ -f2)" + if [[ "${IMG}" == "" ]]; then + echo "Attempted to use LATEST_CACHED_IMAGE, but found no images matching ${query}" >&2 + exit 1 + fi + echo "Using cached image $IMG, created $(<<<"$latest" cut -d~ -f3)" >&2 +fi + +ENV_BLOCKLIST="${ENV_BLOCKLIST:-^_\|^PATH=\|^GOPATH=\|^GOROOT=\|^SHELL=\|^EDITOR=\|^TMUX=\|^USER=\|^HOME=\|^PWD=\|^TERM=\|^RUBY_\|^GEM_\|^rvm_\|^SSH=\|^TMPDIR=\|^CC=\|^CXX=\|^MAKEFILE_LIST=}" + +# Remove functions from the list of exported variables, they mess up with the `env` command. +for f in $(declare -F -x | cut -d ' ' -f 3); +do + unset -f "${f}" +done + +# Set conditional host mounts +CONDITIONAL_HOST_MOUNTS="${CONDITIONAL_HOST_MOUNTS:-} " +container_kubeconfig='' + +# docker conditional host mount (needed for make docker push) +if [[ -d "${HOME}/.docker" ]]; then + CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${HOME}/.docker,destination=/config/.docker,readonly " +fi + +# gcloud conditional host mount (needed for docker push with the gcloud auth configure-docker) +if [[ -d "${HOME}/.config/gcloud" ]]; then + CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${HOME}/.config/gcloud,destination=/config/.config/gcloud,readonly " +fi + +# gitconfig conditional host mount (needed for git commands inside container) +if [[ -f "${HOME}/.gitconfig" ]]; then + CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${HOME}/.gitconfig,destination=/home/.gitconfig,readonly " +fi + +# .netrc conditional host mount (needed for git commands inside container) +if [[ -f "${HOME}/.netrc" ]]; then + CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${HOME}/.netrc,destination=/home/.netrc,readonly " +fi + +# echo ${CONDITIONAL_HOST_MOUNTS} + +# This function checks if the file exists. If it does, it creates a randomly named host location +# for the file, adds it to the host KUBECONFIG, and creates a mount for it. Note that we use a copy +# of the original file, so that the container can write to it. +add_KUBECONFIG_if_exists () { + if [[ -f "$1" ]]; then + local local_config + local_config="$(mktemp)" + cp "${1}" "${local_config}" + + kubeconfig_random="$(od -vAn -N4 -tx /dev/random | tr -d '[:space:]' | cut -c1-8)" + container_kubeconfig+="/config/${kubeconfig_random}:" + CONDITIONAL_HOST_MOUNTS+="--mount type=bind,source=${local_config},destination=/config/${kubeconfig_random} " + fi +} + +# This function is designed for maximum compatibility with various platforms. This runs on +# any Mac or Linux platform with bash 4.2+. Please take care not to modify this function +# without testing properly. +# +# This function will properly handle any type of path including those with spaces using the +# loading pattern specified by *kubectl config*. +# +# testcase: "a:b c:d" +# testcase: "a b:c d:e f" +# testcase: "a b:c:d e" +parse_KUBECONFIG () { +TMPDIR="" +if [[ "$1" =~ ([^:]*):(.*) ]]; then + while true; do + rematch=${BASH_REMATCH[1]} + add_KUBECONFIG_if_exists "$rematch" + remainder="${BASH_REMATCH[2]}" + if [[ ! "$remainder" =~ ([^:]*):(.*) ]]; then + if [[ -n "$remainder" ]]; then + add_KUBECONFIG_if_exists "$remainder" + break + fi + fi + done +else + add_KUBECONFIG_if_exists "$1" +fi +} + +KUBECONFIG=${KUBECONFIG:="$HOME/.kube/config"} +parse_KUBECONFIG "${KUBECONFIG}" +if [[ "${FOR_BUILD_CONTAINER:-0}" -eq "1" ]]; then + KUBECONFIG="${container_kubeconfig%?}" +fi + +# LOCAL_OUT should point to architecture where we are currently running versus the desired. +# This is used when we need to run a build artifact during tests or later as part of another +# target. +if [[ "${FOR_BUILD_CONTAINER:-0}" -eq "1" ]]; then + # Override variables with container specific + TARGET_OUT=${CONTAINER_TARGET_OUT} + TARGET_OUT_LINUX=${CONTAINER_TARGET_OUT_LINUX} + REPO_ROOT=/work + LOCAL_OUT="${TARGET_OUT_LINUX}" +else + LOCAL_OUT="${TARGET_OUT}" +fi + +go_os_arch=${LOCAL_OUT##*/} +# Golang OS/Arch format +LOCAL_GO_OS=${go_os_arch%_*} +LOCAL_GO_ARCH=${go_os_arch##*_} + +BUILD_WITH_CONTAINER=0 + +VARS=( + CONTAINER_TARGET_OUT + CONTAINER_TARGET_OUT_LINUX + TARGET_OUT + TARGET_OUT_LINUX + LOCAL_GO_OS + LOCAL_GO_ARCH + LOCAL_OUT + LOCAL_OS + TARGET_OS + LOCAL_ARCH + TARGET_ARCH + TIMEZONE + KUBECONFIG + CONDITIONAL_HOST_MOUNTS + ENV_BLOCKLIST + CONTAINER_CLI + DOCKER_GID + IMG + IMAGE_NAME + IMAGE_VERSION + REPO_ROOT + BUILD_WITH_CONTAINER +) + +# For non container build, we need to write env to file +if [[ "${1}" == "envfile" ]]; then + # ! does a variable-variable https://stackoverflow.com/a/10757531/374797 + for var in "${VARS[@]}"; do + echo "${var}"="${!var}" + done +else + for var in "${VARS[@]}"; do + # shellcheck disable=SC2163 + export "${var}" + done +fi diff --git a/common/scripts/tracing.sh b/common/scripts/tracing.sh new file mode 100755 index 00000000000..7ac41e52bd2 --- /dev/null +++ b/common/scripts/tracing.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY +# +# The original version of this file is located in the https://github.com/istio/common-files repo. +# If you're looking at this file in a different repo and want to make a change, please go to the +# common-files repo, make the change there and check it in. Then come back to this repo and run +# "make update-common". + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Usage: tracing::extract_prow_trace. +# If running in a prow job, this sets the parent trace to the same value Prow tracing will use, as defined in https://github.com/kubernetes/test-infra/issues/30010 +function tracing::extract_prow_trace() { + if [[ "${PROW_JOB_ID:-}" != "" ]]; then + local trace + trace="$(<<< "$PROW_JOB_ID" tr -d '\-')" + local span + span="${trace:0:16}" + export TRACEPARENT="01-${trace}-${span}-00" + fi +} + +function _genattrs() { + # No upstream standard, so copy from https://github.com/jenkinsci/opentelemetry-plugin/blob/master/docs/job-traces.md + if [[ -n "${PULL_NUMBER:=}" ]] + then + # Presubmit + url="https://prow.istio.io/view/gs/istio-prow/pr-logs/pull/${REPO_OWNER}_${REPO_NAME}/${PULL_NUMBER}/${JOB_NAME}/${BUILD_ID}," + else + # Postsubmit or periodic + url="https://prow.istio.io/view/gs/istio-prow/pr-logs/${JOB_NAME}/${BUILD_ID}," + fi + # Use printf instead of echo to avoid spaces between args + printf '%s' "ci.pipeline.id=${JOB_NAME},"\ + "ci.pipeline.type=${JOB_TYPE},"\ + "ci.pipeline.run.url=${url}"\ + "ci.pipeline.run.number=${BUILD_ID},"\ + "ci.pipeline.run.id=${PROW_JOB_ID},"\ + "ci.pipeline.run.repo=${REPO_OWNER:-unknown}/${REPO_NAME:-unknown},"\ + "ci.pipeline.run.base=${PULL_BASE_REF:-none},"\ + "ci.pipeline.run.pull_number=${PULL_NUMBER:-none},"\ + "ci.pipeline.run.pull_sha=${PULL_PULL_SHA:-${PULL_BASE_SHA:-none}}" +} + +# Usage: tracing::run [command ...] +function tracing::run() { + # If not running in a prow job or otel-cli is not available (e.g. build system without otel-cli) just run the command + if [ -z "${JOB_NAME:-}" ] || ! command -v otel-cli &> /dev/null + then + "${@:2}" + return "$?" + fi + + # Disable execution tracing to avoid noise + { [[ $- = *x* ]] && was_execution_trace=1 || was_execution_trace=0; } 2>/dev/null + { set +x; } 2>/dev/null + # Throughout, "local" usage is critical to avoid nested calls overwriting things + local start + start="$(date -u +%s.%N)" + # First, get a trace and span ID. We need to get one now so we can propagate it to the child + # Get trace ID from TRACEPARENT, if present + local tid + tid="$(<<<"${TRACEPARENT:-}" cut -d- -f2)" + tid="${tid:-"$(tr -dc 'a-f0-9' < /dev/urandom | head -c 32)"}" + # Always generate a new span ID + local sid + sid="$(tr -dc 'a-f0-9' < /dev/urandom | head -c 16)" + + # Execute the command they wanted with the propagation through TRACEPARENT + if [[ $was_execution_trace == 1 ]]; then + { set -x; } 2>/dev/null + fi + + TRACEPARENT="00-${tid}-${sid}-01" "${@:2}" + local result="$?" + { set +x; } 2>/dev/null + + local end + end="$(date -u +%s.%N)" + + # Now report this span. We override the IDs to the ones we set before. + otel-cli span \ + --service "${BASH_SOURCE[-1]}" \ + --name "$1" \ + --start "$start" \ + --end "$end" \ + --force-trace-id "$tid" \ + --force-span-id "$sid" \ + --attrs "$(_genattrs)" + if [[ $was_execution_trace == 1 ]]; then + { set -x; } 2>/dev/null + fi + return "$result" +} + +# Usage: tracing::decorate +# Automatically makes a function traced. +function tracing::decorate() { +eval "\ +function $1() { +_$(typeset -f "$1") +tracing::run '$1' _$1 +} +" +} diff --git a/envoy.bazelrc b/envoy.bazelrc new file mode 100644 index 00000000000..69fefca40a8 --- /dev/null +++ b/envoy.bazelrc @@ -0,0 +1,526 @@ +############################################################################# +# startup +############################################################################# + +# Bazel doesn't need more than 200MB of memory for local build based on memory profiling: +# https://docs.bazel.build/versions/master/skylark/performance.html#memory-profiling +# The default JVM max heapsize is 1/4 of physical memory up to 32GB which could be large +# enough to consume all memory constrained by cgroup in large host. +# Limiting JVM heapsize here to let it do GC more when approaching the limit to +# leave room for compiler/linker. +# The number 3G is chosen heuristically to both support large VM and small VM with RBE. +# Startup options cannot be selected via config. +# TODO: Adding just to test android +startup --host_jvm_args=-Xmx3g +startup --host_jvm_args="-DBAZEL_TRACK_SOURCE_DIRECTORIES=1" + + +############################################################################# +# global +############################################################################# + +common --noenable_bzlmod + +fetch --color=yes +run --color=yes + +build --color=yes +build --jobs=HOST_CPUS-1 +build --workspace_status_command="bash bazel/get_workspace_status" +build --incompatible_strict_action_env +build --java_runtime_version=remotejdk_11 +build --tool_java_runtime_version=remotejdk_11 +build --java_language_version=11 +build --tool_java_language_version=11 +# silence absl logspam. +build --copt=-DABSL_MIN_LOG_LEVEL=4 +# Global C++ standard and common warning suppressions +build --cxxopt=-std=c++20 --host_cxxopt=-std=c++20 +build --copt=-Wno-deprecated-declarations +build --define envoy_mobile_listener=enabled +build --experimental_repository_downloader_retries=2 +build --experimental_cc_static_library +build --enable_platform_specific_config +build --incompatible_merge_fixed_and_default_shell_env +# A workaround for slow ICU download. +build --http_timeout_scaling=6.0 + +# Allow stamped caches to bust when local filesystem changes. +# Requires setting `BAZEL_VOLATILE_DIRTY` in the env. +build --action_env=BAZEL_VOLATILE_DIRTY --host_action_env=BAZEL_VOLATILE_DIRTY + +build --test_summary=terse + +# TODO(keith): Remove once these 2 are the default +build --incompatible_config_setting_private_default_visibility +build --incompatible_enforce_config_setting_visibility + +test --test_verbose_timeout_warnings +test --experimental_ui_max_stdouterr_bytes=11712829 #default 1048576 + +# Allow tags to influence execution requirements +common --experimental_allow_tags_propagation + +# Python +common --@rules_python//python/config_settings:bootstrap_impl=script +build --incompatible_default_to_explicit_init_py + +# We already have absl in the build, define absl=1 to tell googletest to use absl for backtrace. +build --define absl=1 + +# Disable ICU linking for googleurl. +build --@googleurl//build_config:system_icu=0 + +# Test options +build --test_env=HEAPCHECK=normal --test_env=PPROF_PATH + +# Coverage options +coverage --config=coverage +coverage --build_tests_only + +# Specifies the rustfmt.toml for all rustfmt_test targets. +build --@rules_rust//rust/settings:rustfmt.toml=@envoy//:rustfmt.toml + + +############################################################################# +# os +############################################################################# + +build:linux --copt=-fdebug-types-section +# Enable position independent code (this is the default on macOS and Windows) +# (Workaround for https://github.com/bazelbuild/rules_foreign_cc/issues/421) +build:linux --copt=-fPIC +build:linux --cxxopt=-fsized-deallocation --host_cxxopt=-fsized-deallocation +build:linux --conlyopt=-fexceptions +build:linux --fission=dbg,opt +build:linux --features=per_object_debug_info + +# macOS +build:macos --action_env=PATH=/opt/homebrew/bin:/opt/local/bin:/usr/local/bin:/usr/bin:/bin +build:macos --host_action_env=PATH=/opt/homebrew/bin:/opt/local/bin:/usr/local/bin:/usr/bin:/bin +build:macos --define tcmalloc=disabled +build:macos --cxxopt=-Wno-nullability-completeness +build:macos --@toolchains_llvm//toolchain/config:compiler-rt=false +build:macos --@toolchains_llvm//toolchain/config:libunwind=false + + +############################################################################# +# compiler +############################################################################# + +# Common flags for Clang (shared between all clang variants) +common:clang-common --linkopt=-fuse-ld=lld +common:clang-common --@toolchains_llvm//toolchain/config:compiler-rt=false +common:clang-common --@toolchains_llvm//toolchain/config:libunwind=false + +# Clang with libc++ (default) +common:clang --config=clang-common +common:clang --config=libc++ +common:clang --host_platform=@clang_platform +common:clang --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 + +# Clang installed to non-standard location (ie not /opt/llvm/) +common:clang-local --config=clang-common +common:clang-local --config=libc++ + +# Use gold linker for gcc compiler. +build:gcc --config=libstdc++ +build:gcc --test_env=HEAPCHECK= +build:gcc --action_env=BAZEL_COMPILER=gcc +build:gcc --action_env=CC=gcc --action_env=CXX=g++ +# This is to work around a bug in GCC that makes debug-types-section +# option not play well with fission: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110885 +build:gcc --copt=-fno-debug-types-section +# These trigger errors in multiple places both in Envoy dependecies +# and in Envoy code itself when using GCC. +# And in all cases the reports appear to be clear false positives. +build:gcc --copt=-Wno-error=restrict +build:gcc --copt=-Wno-error=uninitialized +build:gcc --cxxopt=-Wno-missing-requires +build:gcc --cxxopt=-Wno-dangling-reference +build:gcc --cxxopt=-Wno-nonnull-compare +build:gcc --cxxopt=-Wno-trigraphs +build:gcc --linkopt=-fuse-ld=gold --host_linkopt=-fuse-ld=gold +build:gcc --host_platform=@envoy//bazel/rbe/toolchains:rbe_linux_gcc_platform +build:gcc --action_env=BAZEL_LINKOPTS=-lm:-fuse-ld=gold + +# libc++ - default for clang +common:libc++ --action_env=CXXFLAGS=-stdlib=libc++ +common:libc++ --action_env=LDFLAGS="-stdlib=libc++ -fuse-ld=lld" +common:libc++ --action_env=BAZEL_CXXOPTS=-stdlib=libc++ +common:libc++ --action_env=BAZEL_LINKLIBS=-l%:libc++.a:-l%:libc++abi.a +common:libc++ --action_env=BAZEL_LINKOPTS=-lm:-pthread +common:libc++ --define force_libcpp=enabled +common:libc++ --@envoy//bazel:libc++=true + +# libstdc++ - currently only used for gcc +build:libstdc++ --action_env=BAZEL_LINKLIBS=-l%:libstdc++.a +build:libstdc++ --@envoy//bazel:libc++=false +build:libstdc++ --@envoy//bazel:libstdc++=true + + +############################################################################# +# tests +############################################################################# + +# Coverage +build:coverage --action_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 +build:coverage --action_env=GCOV=llvm-profdata +build:coverage --copt=-DNDEBUG +# 1.5x original timeout + 300s for trace merger in all categories +build:coverage --test_timeout=390,750,1500,5700 +build:coverage --define=ENVOY_CONFIG_COVERAGE=1 +build:coverage --cxxopt="-DENVOY_CONFIG_COVERAGE=1" +build:coverage --test_env=HEAPCHECK= +build:coverage --combined_report=lcov +build:coverage --strategy=TestRunner=remote,sandboxed,local +build:coverage --strategy=CoverageReport=sandboxed,local +build:coverage --experimental_use_llvm_covmap +build:coverage --experimental_generate_llvm_lcov +build:coverage --experimental_split_coverage_postprocessing +build:coverage --experimental_fetch_all_coverage_outputs +build:coverage --collect_code_coverage +build:coverage --instrumentation_filter="^//source(?!/common/quic/platform)[/:],^//envoy[/:],^//contrib(?!/.*/test)[/:]" +build:coverage --remote_download_minimal +build:coverage --define=tcmalloc=gperftools +build:coverage --define=no_debug_info=1 +# `--no-relax` is required for coverage to not err with `relocation R_X86_64_REX_GOTPCRELX` +build:coverage --linkopt=-Wl,-s,--no-relax +build:coverage --test_env=ENVOY_IP_TEST_VERSIONS=v4only +build:coverage --define=dynamic_link_tests=false +# Use custom report generator that also generates HTML +build:coverage --coverage_report_generator=@envoy//tools/coverage:report_generator + +build:test-coverage --test_arg="-l trace" +build:test-coverage --test_arg="--log-path /dev/null" +build:test-coverage --test_tag_filters=-nocoverage,-fuzz_target + +## Compile-time-options testing +# Right now, none of the available compile-time options conflict with each other. If this +# changes, this build type may need to be broken up. +build:compile-time-options --define=admin_html=disabled +build:compile-time-options --define=signal_trace=disabled +build:compile-time-options --define=hot_restart=disabled +build:compile-time-options --define=google_grpc=disabled +build:compile-time-options --config=boringssl-fips +build:compile-time-options --define=log_debug_assert_in_release=enabled +build:compile-time-options --define=path_normalization_by_default=true +build:compile-time-options --define=deprecated_features=disabled +build:compile-time-options --define=uhv=enabled +# gRPC has a lot of deprecated-enum-enum-conversion warnings with C++20 +build:compile-time-options --copt=-Wno-error=deprecated-enum-enum-conversion +build:compile-time-options --test_env=ENVOY_HAS_EXTRA_EXTENSIONS=true +build:compile-time-options --@envoy//bazel:http3=False +build:compile-time-options --@envoy//source/extensions/filters/http/kill_request:enabled + + +############################################################################# +# SSL +############################################################################# + +common:fips-common --test_tag_filters=-nofips +common:fips-common --build_tag_filters=-nofips +common:fips-common --@envoy//bazel:fips=True + +# BoringSSL FIPS +common:boringssl-fips --config=fips-common +common:boringssl-fips --@envoy//bazel:ssl=@boringssl_fips//:ssl +common:boringssl-fips --@envoy//bazel:crypto=@boringssl_fips//:crypto + +# AWS-LC FIPS +common:aws-lc-fips --config=fips-common +common:aws-lc-fips --@envoy//bazel:ssl=@aws_lc//:ssl +common:aws-lc-fips --@envoy//bazel:crypto=@aws_lc//:crypto +common:aws-lc-fips --@envoy//bazel:http3=False + +# OpenSSL +common:openssl --//bazel:ssl=//compat/openssl:ssl +common:openssl --//bazel:crypto=//compat/openssl:crypto +common:openssl --copt=-DENVOY_SSL_OPENSSL +common:openssl --host_copt=-DENVOY_SSL_OPENSSL +common:openssl --@envoy//bazel:http3=False +common:openssl --test_tag_filters=-nofips +common:openssl --build_tag_filters=-nofips + + +############################################################################# +# sanitizers +############################################################################# + +# Common flags for sanitizers +build:sanitizer --define tcmalloc=disabled +build:sanitizer --linkopt -ldl +test:sanitizer --build_tests_only + +# ASAN config with clang runtime +build:asan --config=asan-common +build:asan --linkopt --rtlib=compiler-rt +build:asan --linkopt --unwindlib=libgcc +build:asan --linkopt=-l:libclang_rt.ubsan_standalone.a +build:asan --linkopt=-l:libclang_rt.ubsan_standalone_cxx.a +build:asan --action_env=ENVOY_UBSAN_VPTR=1 +build:asan --copt=-fsanitize=vptr,function +build:asan --linkopt=-fsanitize=vptr,function +build:asan --linkopt='-L/opt/llvm/lib/clang/18/lib/x86_64-unknown-linux-gnu' + +# Basic ASAN/UBSAN that works for gcc or llvm +build:asan-common --config=sanitizer +# ASAN install its signal handler, disable ours so the stacktrace will be printed by ASAN +build:asan-common --define signal_trace=disabled +build:asan-common --define ENVOY_CONFIG_ASAN=1 +build:asan-common --build_tag_filters=-no_san +build:asan-common --test_tag_filters=-no_san +build:asan-common --copt -fsanitize=address,undefined +build:asan-common --linkopt -fsanitize=address,undefined +# vptr and function sanitizer are enabled in asan when using --config=clang. +build:asan-common --copt -fno-sanitize=vptr,function +build:asan-common --linkopt -fno-sanitize=vptr,function +build:asan-common --copt -DADDRESS_SANITIZER=1 +build:asan-common --copt -DUNDEFINED_SANITIZER=1 +build:asan-common --copt -D__SANITIZE_ADDRESS__ +build:asan-common --test_env=ASAN_OPTIONS=handle_abort=1:allow_addr2line=true:check_initialization_order=true:strict_init_order=true:detect_odr_violation=1 +build:asan-common --test_env=UBSAN_OPTIONS=halt_on_error=true:print_stacktrace=1 +build:asan-common --test_env=ASAN_SYMBOLIZER_PATH +# ASAN needs -O1 to get reasonable performance. +build:asan-common --copt -O1 +build:asan-common --copt -fno-optimize-sibling-calls + +# macOS ASAN/UBSAN +build:macos-asan --config=asan +# Workaround, see https://github.com/bazelbuild/bazel/issues/6932 +build:macos-asan --copt -Wno-macro-redefined +build:macos-asan --copt -D_FORTIFY_SOURCE=0 +# Workaround, see https://github.com/bazelbuild/bazel/issues/4341 +build:macos-asan --copt -DGRPC_BAZEL_BUILD +# Dynamic link cause issues like: `dyld: malformed mach-o: load commands size (59272) > 32768` +build:macos-asan --dynamic_mode=off + +# Base MSAN config +build:msan --action_env=ENVOY_MSAN=1 +build:msan --config=sanitizer +build:msan --build_tag_filters=-no_san +build:msan --test_tag_filters=-no_san +build:msan --define ENVOY_CONFIG_MSAN=1 +build:msan --copt -fsanitize=memory +build:msan --linkopt -fsanitize=memory +build:msan --copt -fsanitize-memory-track-origins=2 +build:msan --copt -DMEMORY_SANITIZER=1 +build:msan --test_env=MSAN_SYMBOLIZER_PATH +# MSAN needs -O1 to get reasonable performance. +build:msan --copt -O1 +build:msan --copt -fno-optimize-sibling-calls + +# Base TSAN config +build:tsan --action_env=ENVOY_TSAN=1 +build:tsan --config=sanitizer +build:tsan --define ENVOY_CONFIG_TSAN=1 +build:tsan --copt -fsanitize=thread +build:tsan --linkopt -fsanitize=thread +build:tsan --copt -DTHREAD_SANITIZER=1 +build:tsan --build_tag_filters=-no_san,-no_tsan +build:tsan --test_tag_filters=-no_san,-no_tsan +# Needed due to https://github.com/libevent/libevent/issues/777 +build:tsan --copt -DEVENT__DISABLE_DEBUG_MODE +# https://github.com/abseil/abseil-cpp/issues/760 +# https://github.com/google/sanitizers/issues/953 +build:tsan --test_env="TSAN_OPTIONS=report_atomic_races=0" +build:tsan --test_timeout=120,600,1500,4800 + + +############################################################################# +# fuzzing +############################################################################# + +## Fuzz builds +# Shared fuzzing configuration. +build:fuzzing --define=ENVOY_CONFIG_ASAN=1 +build:fuzzing --copt=-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + +# ASAN fuzzer +build:asan-fuzzer --config=plain-fuzzer +build:asan-fuzzer --config=asan +build:asan-fuzzer --copt=-fno-omit-frame-pointer +# Remove UBSAN halt_on_error to avoid crashing on protobuf errors. +build:asan-fuzzer --test_env=UBSAN_OPTIONS=print_stacktrace=1 +build:asan-fuzzer --linkopt=-lc++ + +build:fuzz-coverage --config=plain-fuzzer +build:fuzz-coverage --run_under=@envoy//bazel/coverage:fuzz_coverage_wrapper.sh +build:fuzz-coverage --test_tag_filters=-nocoverage +# Existing fuzz tests don't need a full WASM runtime and in generally we don't really want to +# fuzz dependencies anyways. On the other hand, disabling WASM reduces the build time and +# resources required to build and run the tests. +build:fuzz-coverage --define=wasm=disabled +build:fuzz-coverage --config=fuzz-coverage-config +build:fuzz-coverage-config --//tools/coverage:config=@envoy//test:fuzz_coverage_config + +build:oss-fuzz --config=fuzzing +build:oss-fuzz --config=libc++ +build:oss-fuzz --define=FUZZING_ENGINE=oss-fuzz +build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=oss-fuzz +build:oss-fuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=none +build:oss-fuzz --dynamic_mode=off +build:oss-fuzz --strip=never +build:oss-fuzz --copt=-fno-sanitize=vptr +build:oss-fuzz --linkopt=-fno-sanitize=vptr +build:oss-fuzz --define=tcmalloc=disabled +build:oss-fuzz --define=signal_trace=disabled +build:oss-fuzz --copt=-D_LIBCPP_DISABLE_DEPRECATION_WARNINGS +build:oss-fuzz --define=force_libcpp=enabled +build:oss-fuzz --linkopt=-lc++ +build:oss-fuzz --linkopt=-pthread + +# Fuzzing without ASAN. This is useful for profiling fuzzers without any ASAN artifacts. +build:plain-fuzzer --config=fuzzing +build:plain-fuzzer --define=FUZZING_ENGINE=libfuzzer +# The fuzzing rules provide their own instrumentation, but it is currently +# disabled due to bazelbuild/bazel#12888. Instead, we provide instrumentation at +# the top level through these options. +build:plain-fuzzer --copt=-fsanitize=fuzzer-no-link +build:plain-fuzzer --linkopt=-fsanitize=fuzzer-no-link + + +############################################################################# +# miscellaneous +############################################################################# + +build:cache-local --remote_cache=grpc://localhost:9092 + +# Flags for Clang + PCH +build:clang-pch --spawn_strategy=local +build:clang-pch --define=ENVOY_CLANG_PCH=1 + +# Clang-tidy +build:clang-tidy --@envoy_toolshed//format/clang_tidy:executable=@envoy//tools/clang-tidy +build:clang-tidy --@envoy_toolshed//format/clang_tidy:config=@envoy//:clang_tidy_config +build:clang-tidy --aspects @envoy_toolshed//format/clang_tidy:clang_tidy.bzl%clang_tidy_aspect +build:clang-tidy --output_groups=report +build:clang-tidy --build_tag_filters=-notidy + +# Compile database generation config +build:compdb --build_tag_filters=-nocompdb + +common:cves --//tools/dependency:cve-data=//tools/dependency:cve-data-dir + +build:docs-ci --action_env=DOCS_RST_CHECK=1 --host_action_env=DOCS_RST_CHECK=1 + +# Optimize build for binary size reduction. +build:sizeopt -c opt --copt -Os + + +############################################################################# +# remote: Setup for cache, BES, RBE, and Docker workers +############################################################################# + +build:remote --spawn_strategy=remote,sandboxed,local +build:remote --strategy=Javac=remote,sandboxed,local +build:remote --strategy=Closure=remote,sandboxed,local +build:remote --strategy=Genrule=remote,sandboxed,local +build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +# This flag may be more generally useful - it sets foreign_cc builds -jauto. +# It is only set here because if it were the default it risks OOMing on local builds. +build:remote --@envoy//bazel/foreign_cc:parallel_builds + +## RBE (Engflow Envoy) + +# this is not included in the `--config=rbe` target - set it to publish to engflow ui +common:bes --bes_backend=grpcs://mordenite.cluster.engflow.com/ +common:bes --bes_results_url=https://mordenite.cluster.engflow.com/invocation/ +common:bes --bes_timeout=3600s +common:bes --bes_upload_mode=fully_async +common:bes --nolegacy_important_outputs + +common:engflow-common --google_default_credentials=false +common:engflow-common --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +common:engflow-common --grpc_keepalive_time=60s +common:engflow-common --grpc_keepalive_timeout=30s +common:engflow-common --remote_cache_compression +# Recover from transient gRPC failures (e.g. UNAVAILABLE: io exception) talking to engflow. +common:engflow-common --remote_retries=10 +common:engflow-common --remote_retry_max_delay=60s +# Recover when blobs are evicted from the CAS mid-build (common during patch releases). +common:engflow-common --experimental_remote_cache_eviction_retries=5 + +# this provides access to RBE+cache +common:rbe --config=remote-cache +common:rbe --config=remote-exec + +# this provides access to just cache +common:remote-cache --config=engflow-common +common:remote-cache --remote_cache=grpcs://mordenite.cluster.engflow.com +common:remote-cache --experimental_remote_downloader=grpcs://mordenite.cluster.engflow.com +common:remote-cache --remote_timeout=3600s + +common:remote-exec --remote_executor=grpcs://mordenite.cluster.engflow.com +common:remote-exec --jobs=200 +common:remote-exec --define=engflow_rbe=true + +# Docker sandboxes +build:docker-sandbox --spawn_strategy=docker +build:docker-sandbox --strategy=Javac=docker +build:docker-sandbox --strategy=Closure=docker +build:docker-sandbox --strategy=Genrule=docker +build:docker-sandbox --define=EXECUTOR=remote +build:docker-sandbox --experimental_docker_verbose +build:docker-sandbox --experimental_enable_docker_sandbox + +build:docker-clang --config=docker-sandbox +build:docker-clang --config=clang + +build:docker-gcc --config=docker-sandbox +build:docker-gcc --config=gcc + +build:docker-asan --config=docker-sandbox +build:docker-asan --config=clang +build:docker-asan --config=asan + +build:docker-msan --config=docker-sandbox +build:docker-msan --config=clang +build:docker-msan --config=msan + +build:docker-tsan --config=docker-sandbox +build:docker-tsan --config=clang +build:docker-tsan --config=tsan + + +############################################################################# +# ci +############################################################################# + +# CI configurations +build:remote-ci --config=ci +build:remote-ci --remote_download_minimal + +# Note this config is used by mobile CI also. +common:ci --noshow_progress +common:ci --noshow_loading_progress +common:ci --test_output=errors + + +############################################################################# +# debug: Various Bazel debugging flags +############################################################################# +# debug/bazel +common:debug-bazel --announce_rc +common:debug-bazel -s +# debug/sandbox +common:debug-sandbox --verbose_failures +common:debug-sandbox --sandbox_debug +# debug/coverage +common:debug-coverage --action_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=VERBOSE_COVERAGE=true +common:debug-coverage --test_env=DISPLAY_LCOV_CMD=true +common:debug-coverage --config=debug-tests +# debug/tests +common:debug-tests --test_output=all +# debug/everything +common:debug --config=debug-bazel +common:debug --config=debug-sandbox +common:debug --config=debug-coverage +common:debug --config=debug-tests + +try-import %workspace%/repo.bazelrc +try-import %workspace%/clang.bazelrc +try-import %workspace%/user.bazelrc +try-import %workspace%/local_tsan.bazelrc diff --git a/extensions/common/BUILD b/extensions/common/BUILD new file mode 100644 index 00000000000..fc48db977dc --- /dev/null +++ b/extensions/common/BUILD @@ -0,0 +1,52 @@ +# Copyright 2019 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "metadata_object_lib", + srcs = ["metadata_object.cc"], + hdrs = ["metadata_object.h"], + repository = "@envoy", + deps = [ + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/types:optional", + "@envoy//envoy/common:hashable_interface", + "@envoy//envoy/local_info:local_info_interface", + "@envoy//envoy/registry", + "@envoy//envoy/stream_info:filter_state_interface", + "@envoy//source/common/common:hash_lib", + ], +) + +envoy_cc_test( + name = "metadata_object_test", + srcs = ["metadata_object_test.cc"], + repository = "@envoy", + deps = [ + ":metadata_object_lib", + "@envoy//envoy/registry", + ], +) diff --git a/extensions/common/metadata_object.cc b/extensions/common/metadata_object.cc new file mode 100644 index 00000000000..ea021bcb004 --- /dev/null +++ b/extensions/common/metadata_object.cc @@ -0,0 +1,548 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "extensions/common/metadata_object.h" + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/registry/registry.h" +#include "source/common/common/hash.h" +#include "source/common/protobuf/utility.h" + +#include "absl/strings/str_join.h" + +namespace Istio { +namespace Common { + +namespace { + +// This maps field names into baggage tokens. We use it to decode field names +// when WorkloadMetadataObject content is accessed through the Envoy API. +static absl::flat_hash_map ALL_METADATA_FIELDS = { + {NamespaceNameToken, BaggageToken::NamespaceName}, + {ClusterNameToken, BaggageToken::ClusterName}, + {ServiceNameToken, BaggageToken::ServiceName}, + {ServiceVersionToken, BaggageToken::ServiceVersion}, + {AppNameToken, BaggageToken::AppName}, + {AppVersionToken, BaggageToken::AppVersion}, + {WorkloadNameToken, BaggageToken::WorkloadName}, + {WorkloadTypeToken, BaggageToken::WorkloadType}, + {InstanceNameToken, BaggageToken::InstanceName}, + {RegionToken, BaggageToken::LocalityRegion}, + {ZoneToken, BaggageToken::LocalityZone}, +}; + +// This maps baggage keys into baggage tokens. We use it to decode baggage keys +// coming over the wire when building WorkloadMetadataObject. +static absl::flat_hash_map ALL_BAGGAGE_TOKENS = { + {NamespaceNameBaggageToken, BaggageToken::NamespaceName}, + {ClusterNameBaggageToken, BaggageToken::ClusterName}, + {ServiceNameBaggageToken, BaggageToken::ServiceName}, + {ServiceVersionBaggageToken, BaggageToken::ServiceVersion}, + {AppNameBaggageToken, BaggageToken::AppName}, + {AppVersionBaggageToken, BaggageToken::AppVersion}, + {DeploymentNameBaggageToken, BaggageToken::WorkloadName}, + {PodNameBaggageToken, BaggageToken::WorkloadName}, + {CronjobNameBaggageToken, BaggageToken::WorkloadName}, + {JobNameBaggageToken, BaggageToken::WorkloadName}, + {InstanceNameBaggageToken, BaggageToken::InstanceName}, + {LocalityRegionBaggageToken, BaggageToken::LocalityRegion}, + {LocalityZoneBaggageToken, BaggageToken::LocalityZone}, + +}; + +static absl::flat_hash_map ALL_WORKLOAD_TOKENS = { + {PodSuffix, WorkloadType::Pod}, + {DeploymentSuffix, WorkloadType::Deployment}, + {JobSuffix, WorkloadType::Job}, + {CronJobSuffix, WorkloadType::CronJob}, +}; + +absl::optional toSuffix(WorkloadType workload_type) { + switch (workload_type) { + case WorkloadType::Deployment: + return DeploymentSuffix; + case WorkloadType::CronJob: + return CronJobSuffix; + case WorkloadType::Job: + return JobSuffix; + case WorkloadType::Pod: + return PodSuffix; + case WorkloadType::Unknown: + default: + return {}; + } +} + +} // namespace + +std::string WorkloadMetadataObject::baggage() const { + const auto workload_type = toSuffix(workload_type_).value_or(PodSuffix); + std::vector parts; + if (!workload_name_.empty()) { + parts.push_back("k8s." + std::string(workload_type) + ".name=" + std::string(workload_name_)); + } + + const auto appName = field(Istio::Common::AppNameToken).value_or(""); + const auto serviceName = field(Istio::Common::ServiceNameToken).value_or(appName); + + if (!serviceName.empty()) { + parts.push_back(absl::StrCat(Istio::Common::ServiceNameBaggageToken, "=", serviceName)); + } + + if (!appName.empty() && appName != serviceName) { + parts.push_back(absl::StrCat(Istio::Common::AppNameBaggageToken, "=", appName)); + } + + const auto appVersion = field(Istio::Common::AppVersionToken).value_or(""); + const auto serviceVersion = field(Istio::Common::ServiceVersionToken).value_or(appVersion); + + if (!serviceVersion.empty()) { + parts.push_back(absl::StrCat(Istio::Common::ServiceVersionBaggageToken, "=", serviceVersion)); + } + + if (!appVersion.empty() && appVersion != serviceVersion) { + parts.push_back(absl::StrCat(Istio::Common::AppVersionBaggageToken, "=", appVersion)); + } + + // Map the workload metadata fields to baggage tokens + const std::vector> field_to_baggage = { + {Istio::Common::NamespaceNameToken, Istio::Common::NamespaceNameBaggageToken}, + {Istio::Common::ClusterNameToken, Istio::Common::ClusterNameBaggageToken}, + {Istio::Common::InstanceNameToken, Istio::Common::InstanceNameBaggageToken}, + {Istio::Common::RegionToken, Istio::Common::LocalityRegionBaggageToken}, + {Istio::Common::ZoneToken, Istio::Common::LocalityZoneBaggageToken}, + }; + + for (const auto& [field_name, baggage_key] : field_to_baggage) { + const auto value = field(field_name); + if (value && !value->empty()) { + parts.push_back(absl::StrCat(baggage_key, "=", *value)); + } + } + return absl::StrJoin(parts, ","); +} + +Envoy::ProtobufTypes::MessagePtr WorkloadMetadataObject::serializeAsProto() const { + auto message = std::make_unique(); + const auto suffix = toSuffix(workload_type_); + if (suffix) { + (*message->mutable_fields())[WorkloadTypeToken].set_string_value(*suffix); + } + if (!workload_name_.empty()) { + (*message->mutable_fields())[WorkloadNameToken].set_string_value(workload_name_); + } + if (!cluster_name_.empty()) { + (*message->mutable_fields())[InstanceNameToken].set_string_value(instance_name_); + } + if (!cluster_name_.empty()) { + (*message->mutable_fields())[ClusterNameToken].set_string_value(cluster_name_); + } + if (!namespace_name_.empty()) { + (*message->mutable_fields())[NamespaceNameToken].set_string_value(namespace_name_); + } + if (!canonical_name_.empty()) { + (*message->mutable_fields())[ServiceNameToken].set_string_value(canonical_name_); + } + if (!canonical_revision_.empty()) { + (*message->mutable_fields())[ServiceVersionToken].set_string_value(canonical_revision_); + } + if (!app_name_.empty()) { + (*message->mutable_fields())[AppNameToken].set_string_value(app_name_); + } + if (!app_version_.empty()) { + (*message->mutable_fields())[AppVersionToken].set_string_value(app_version_); + } + if (!identity_.empty()) { + (*message->mutable_fields())[IdentityToken].set_string_value(identity_); + } + if (!locality_region_.empty()) { + (*message->mutable_fields())[RegionToken].set_string_value(locality_region_); + } + if (!locality_zone_.empty()) { + (*message->mutable_fields())[ZoneToken].set_string_value(locality_zone_); + } + + if (!labels_.empty()) { + auto* labels = (*message->mutable_fields())[LabelsToken].mutable_struct_value(); + for (const auto& l : labels_) { + (*labels->mutable_fields())[std::string(l.first)].set_string_value(std::string(l.second)); + } + } + + return message; +} + +std::vector> +WorkloadMetadataObject::serializeAsPairs() const { + std::vector> parts; + const auto suffix = toSuffix(workload_type_); + if (suffix) { + parts.push_back({WorkloadTypeToken, *suffix}); + } + if (!workload_name_.empty()) { + parts.push_back({WorkloadNameToken, workload_name_}); + } + if (!instance_name_.empty()) { + parts.push_back({InstanceNameToken, instance_name_}); + } + if (!cluster_name_.empty()) { + parts.push_back({ClusterNameToken, cluster_name_}); + } + if (!namespace_name_.empty()) { + parts.push_back({NamespaceNameToken, namespace_name_}); + } + if (!canonical_name_.empty()) { + parts.push_back({ServiceNameToken, canonical_name_}); + } + if (!canonical_revision_.empty()) { + parts.push_back({ServiceVersionToken, canonical_revision_}); + } + if (!app_name_.empty()) { + parts.push_back({AppNameToken, app_name_}); + } + if (!app_version_.empty()) { + parts.push_back({AppVersionToken, app_version_}); + } + if (!locality_region_.empty()) { + parts.push_back({RegionToken, locality_region_}); + } + if (!locality_zone_.empty()) { + parts.push_back({ZoneToken, locality_zone_}); + } + if (!labels_.empty()) { + for (const auto& l : labels_) { + parts.push_back({absl::StrCat("labels[]", l.first), absl::string_view(l.second)}); + } + } + return parts; +} + +absl::optional WorkloadMetadataObject::serializeAsString() const { + const auto parts = serializeAsPairs(); + return absl::StrJoin(parts, ",", absl::PairFormatter("=")); +} + +absl::optional WorkloadMetadataObject::hash() const { + return Envoy::HashUtil::xxHash64(*serializeAsString()); +} + +absl::optional WorkloadMetadataObject::owner() const { + const auto suffix = toSuffix(workload_type_); + if (suffix) { + return absl::StrCat(OwnerPrefix, namespace_name_, "/", *suffix, "s/", workload_name_); + } + return {}; +} + +std::string WorkloadMetadataObject::identity() const { return identity_; } + +WorkloadType fromSuffix(absl::string_view suffix) { + const auto it = ALL_WORKLOAD_TOKENS.find(suffix); + if (it != ALL_WORKLOAD_TOKENS.end()) { + return it->second; + } + return WorkloadType::Unknown; +} + +WorkloadType parseOwner(absl::string_view owner, absl::string_view workload) { + // Strip "s/workload_name" and check for workload type. + if (owner.size() > workload.size() + 2) { + owner.remove_suffix(workload.size() + 2); + size_t last = owner.rfind('/'); + if (last != absl::string_view::npos) { + return fromSuffix(owner.substr(last + 1)); + } + } + return WorkloadType::Unknown; +} + +google::protobuf::Struct convertWorkloadMetadataToStruct(const WorkloadMetadataObject& obj) { + google::protobuf::Struct metadata; + if (!obj.instance_name_.empty()) { + (*metadata.mutable_fields())[InstanceMetadataField].set_string_value(obj.instance_name_); + } + if (!obj.namespace_name_.empty()) { + (*metadata.mutable_fields())[NamespaceMetadataField].set_string_value(obj.namespace_name_); + } + if (!obj.workload_name_.empty()) { + (*metadata.mutable_fields())[WorkloadMetadataField].set_string_value(obj.workload_name_); + } + if (!obj.cluster_name_.empty()) { + (*metadata.mutable_fields())[ClusterMetadataField].set_string_value(obj.cluster_name_); + } + if (!obj.identity_.empty()) { + (*metadata.mutable_fields())[IdentityMetadataField].set_string_value(obj.identity_); + } + auto* labels = (*metadata.mutable_fields())[LabelsMetadataField].mutable_struct_value(); + if (!obj.canonical_name_.empty()) { + (*labels->mutable_fields())[CanonicalNameLabel].set_string_value(obj.canonical_name_); + } + if (!obj.canonical_revision_.empty()) { + (*labels->mutable_fields())[CanonicalRevisionLabel].set_string_value(obj.canonical_revision_); + } + if (!obj.app_name_.empty()) { + (*labels->mutable_fields())[AppNameLabel].set_string_value(obj.app_name_); + } + if (!obj.app_version_.empty()) { + (*labels->mutable_fields())[AppVersionLabel].set_string_value(obj.app_version_); + } + if (!obj.getLabels().empty()) { + for (const auto& lbl : obj.getLabels()) { + (*labels->mutable_fields())[std::string(lbl.first)].set_string_value(std::string(lbl.second)); + } + } + if (const auto owner = obj.owner(); owner.has_value()) { + (*metadata.mutable_fields())[OwnerMetadataField].set_string_value(*owner); + } + if (!obj.locality_region_.empty()) { + (*metadata.mutable_fields())[RegionMetadataField].set_string_value(obj.locality_region_); + } + if (!obj.locality_zone_.empty()) { + (*metadata.mutable_fields())[ZoneMetadataField].set_string_value(obj.locality_zone_); + } + return metadata; +} + +// Convert struct to a metadata object. +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata) { + return convertStructToWorkloadMetadata(metadata, {}); +} + +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels) { + return convertStructToWorkloadMetadata(metadata, additional_labels, {}); +} + +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels, + const absl::optional locality) { + absl::string_view instance, namespace_name, owner, workload, cluster, identity, canonical_name, + canonical_revision, app_name, app_version, region, zone; + std::vector> labels; + for (const auto& it : metadata.fields()) { + if (it.first == InstanceMetadataField) { + instance = it.second.string_value(); + } else if (it.first == NamespaceMetadataField) { + namespace_name = it.second.string_value(); + } else if (it.first == OwnerMetadataField) { + owner = it.second.string_value(); + } else if (it.first == WorkloadMetadataField) { + workload = it.second.string_value(); + } else if (it.first == ClusterMetadataField) { + cluster = it.second.string_value(); + } else if (it.first == IdentityMetadataField) { + identity = it.second.string_value(); + } else if (it.first == RegionMetadataField) { + // This (and zone below) are for the case where locality is propagated + // via a downstream MX header. For propagation, the locality is passed + // via the locality argument and these fields shouldn't be used, but + // we have no way to distinguish locality when we just get a header. + region = it.second.string_value(); + } else if (it.first == ZoneMetadataField) { + zone = it.second.string_value(); + } else if (it.first == LabelsMetadataField) { + for (const auto& labels_it : it.second.struct_value().fields()) { + if (labels_it.first == CanonicalNameLabel) { + canonical_name = labels_it.second.string_value(); + } else if (labels_it.first == CanonicalRevisionLabel) { + canonical_revision = labels_it.second.string_value(); + } else if (labels_it.first == AppNameQualifiedLabel) { + app_name = labels_it.second.string_value(); + } else if (labels_it.first == AppNameLabel && app_name.empty()) { + app_name = labels_it.second.string_value(); + } else if (labels_it.first == AppVersionQualifiedLabel) { + app_version = labels_it.second.string_value(); + } else if (labels_it.first == AppVersionLabel && app_version.empty()) { + app_version = labels_it.second.string_value(); + } else if (!additional_labels.empty() && + additional_labels.contains(std::string(labels_it.first))) { + labels.push_back( + {std::string(labels_it.first), std::string(labels_it.second.string_value())}); + } + } + } + } + std::string locality_region = std::string(region); + std::string locality_zone = std::string(zone); + if (locality.has_value()) { + if (!locality->region().empty() && locality_region.empty()) { + locality_region = locality->region(); + } + if (!locality->zone().empty() && locality_zone.empty()) { + locality_zone = locality->zone(); + } + } + auto obj = std::make_unique( + instance, cluster, namespace_name, workload, canonical_name, canonical_revision, app_name, + app_version, parseOwner(owner, workload), identity, locality_region, locality_zone); + obj->setLabels(labels); + return obj; +} + +absl::optional +convertEndpointMetadata(const std::string& endpoint_encoding) { + std::vector parts = absl::StrSplit(endpoint_encoding, ';'); + if (parts.size() < 5) { + return {}; + } + return absl::make_optional("", parts[4], parts[1], parts[0], parts[2], + parts[3], "", "", WorkloadType::Unknown, "", + "", ""); +} + +std::string serializeToStringDeterministic(const google::protobuf::Struct& metadata) { + std::string out; + { + google::protobuf::io::StringOutputStream md(&out); + google::protobuf::io::CodedOutputStream mcs(&md); + mcs.SetSerializationDeterministic(true); + if (!metadata.SerializeToCodedStream(&mcs)) { + out.clear(); + } + } + return out; +} + +absl::optional +WorkloadMetadataObject::field(absl::string_view field_name) const { + const auto it = ALL_METADATA_FIELDS.find(field_name); + if (it != ALL_METADATA_FIELDS.end()) { + switch (it->second) { + case BaggageToken::NamespaceName: + return namespace_name_; + case BaggageToken::ClusterName: + return cluster_name_; + case BaggageToken::ServiceName: + return canonical_name_; + case BaggageToken::ServiceVersion: + return canonical_revision_; + case BaggageToken::AppName: + return app_name_; + case BaggageToken::AppVersion: + return app_version_; + case BaggageToken::WorkloadName: + return workload_name_; + case BaggageToken::WorkloadType: + if (const auto value = toSuffix(workload_type_); value.has_value()) { + return *value; + } + return "unknown"; + case BaggageToken::InstanceName: + return instance_name_; + case BaggageToken::LocalityRegion: + return locality_region_; + case BaggageToken::LocalityZone: + return locality_zone_; + } + } + return absl::nullopt; +} + +WorkloadMetadataObject::FieldType +WorkloadMetadataObject::getField(absl::string_view field_name) const { + const auto value = field(field_name); + if (value) { + return *value; + } + return {}; +} + +std::unique_ptr +convertBaggageToWorkloadMetadata(absl::string_view baggage) { + return convertBaggageToWorkloadMetadata(baggage, ""); +} + +std::unique_ptr +convertBaggageToWorkloadMetadata(absl::string_view data, absl::string_view identity) { + absl::string_view instance; + absl::string_view cluster; + absl::string_view workload; + absl::string_view namespace_name; + absl::string_view canonical_name; + absl::string_view canonical_revision; + absl::string_view app_name; + absl::string_view app_version; + absl::string_view region; + absl::string_view zone; + WorkloadType workload_type = WorkloadType::Unknown; + std::vector properties = absl::StrSplit(data, ','); + for (absl::string_view property : properties) { + std::pair parts = absl::StrSplit(property, '='); + const auto it = ALL_BAGGAGE_TOKENS.find(parts.first); + if (it != ALL_BAGGAGE_TOKENS.end()) { + switch (it->second) { + case BaggageToken::NamespaceName: + namespace_name = parts.second; + break; + case BaggageToken::ClusterName: + cluster = parts.second; + break; + case BaggageToken::ServiceName: + // canonical name and app name are always the same + canonical_name = parts.second; + if (app_name.empty()) { + app_name = parts.second; + } + break; + case BaggageToken::ServiceVersion: + // canonical revision and app version are always the same + canonical_revision = parts.second; + if (app_version.empty()) { + app_version = parts.second; + } + break; + case BaggageToken::AppName: + app_name = parts.second; + if (canonical_name.empty()) { + canonical_name = parts.second; + } + break; + case BaggageToken::AppVersion: + app_version = parts.second; + if (canonical_revision.empty()) { + canonical_revision = parts.second; + } + break; + case BaggageToken::WorkloadName: { + workload = parts.second; + std::vector splitWorkloadKey = absl::StrSplit(parts.first, "."); + if (splitWorkloadKey.size() >= 2 && splitWorkloadKey[0] == "k8s") { + workload_type = fromSuffix(splitWorkloadKey[1]); + } + break; + } + case BaggageToken::InstanceName: + instance = parts.second; + break; + case BaggageToken::LocalityRegion: + region = parts.second; + break; + case BaggageToken::LocalityZone: + zone = parts.second; + break; + default: + break; + } + } + } + return std::make_unique( + instance, cluster, namespace_name, workload, canonical_name, canonical_revision, app_name, + app_version, workload_type, identity, region, zone); +} + +} // namespace Common +} // namespace Istio diff --git a/extensions/common/metadata_object.h b/extensions/common/metadata_object.h new file mode 100644 index 00000000000..f24f3bcd252 --- /dev/null +++ b/extensions/common/metadata_object.h @@ -0,0 +1,204 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "envoy/common/hashable.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/stream_info/filter_state.h" + +#include "source/common/protobuf/protobuf.h" + +#include "absl/types/optional.h" + +#include "google/protobuf/struct.pb.h" + +namespace Istio { +namespace Common { + +// Filter state key to store the peer metadata under. +// CelState is stored under these keys for CEL expression support. +constexpr absl::string_view DownstreamPeer = "downstream_peer"; +constexpr absl::string_view UpstreamPeer = "upstream_peer"; + +// Filter state keys for WorkloadMetadataObject (FIELD accessor support). +constexpr absl::string_view DownstreamPeerObj = "downstream_peer_obj"; +constexpr absl::string_view UpstreamPeerObj = "upstream_peer_obj"; + +// Special filter state key to indicate the filter is done looking for peer metadata. +// This is used by network metadata exchange on failure. +constexpr absl::string_view NoPeer = "peer_not_found"; + +// Special labels used in the peer metadata. +constexpr absl::string_view CanonicalNameLabel = "service.istio.io/canonical-name"; +constexpr absl::string_view CanonicalRevisionLabel = "service.istio.io/canonical-revision"; +constexpr absl::string_view AppNameQualifiedLabel = "app.kubernetes.io/name"; +constexpr absl::string_view AppNameLabel = "app"; +constexpr absl::string_view AppVersionQualifiedLabel = "app.kubernetes.io/version"; +constexpr absl::string_view AppVersionLabel = "version"; + +enum class WorkloadType { + Unknown, + Pod, + Deployment, + Job, + CronJob, +}; + +constexpr absl::string_view OwnerPrefix = "kubernetes://apis/apps/v1/namespaces/"; + +constexpr absl::string_view PodSuffix = "pod"; +constexpr absl::string_view DeploymentSuffix = "deployment"; +constexpr absl::string_view JobSuffix = "job"; +constexpr absl::string_view CronJobSuffix = "cronjob"; + +enum class BaggageToken { + NamespaceName, + ClusterName, + ServiceName, + ServiceVersion, + AppName, + AppVersion, + WorkloadName, + WorkloadType, + InstanceName, + LocalityZone, + LocalityRegion +}; + +// Field names accessible from WorkloadMetadataObject. +constexpr absl::string_view NamespaceNameToken = "namespace"; +constexpr absl::string_view ClusterNameToken = "cluster"; +constexpr absl::string_view ServiceNameToken = "service"; +constexpr absl::string_view ServiceVersionToken = "revision"; +constexpr absl::string_view AppNameToken = "app"; +constexpr absl::string_view AppVersionToken = "version"; +constexpr absl::string_view WorkloadNameToken = "workload"; +constexpr absl::string_view WorkloadTypeToken = "type"; +constexpr absl::string_view InstanceNameToken = "name"; +constexpr absl::string_view LabelsToken = "labels"; +constexpr absl::string_view IdentityToken = "identity"; +constexpr absl::string_view RegionToken = "region"; +constexpr absl::string_view ZoneToken = "availability_zone"; + +// Field names used to translate baggage content into +// WorkloadMetadataObject information. +constexpr absl::string_view NamespaceNameBaggageToken = "k8s.namespace.name"; +constexpr absl::string_view ClusterNameBaggageToken = "k8s.cluster.name"; +constexpr absl::string_view ServiceNameBaggageToken = "service.name"; +constexpr absl::string_view ServiceVersionBaggageToken = "service.version"; +constexpr absl::string_view AppNameBaggageToken = "app.name"; +constexpr absl::string_view AppVersionBaggageToken = "app.version"; +constexpr absl::string_view DeploymentNameBaggageToken = "k8s.deployment.name"; +constexpr absl::string_view PodNameBaggageToken = "k8s.pod.name"; +constexpr absl::string_view CronjobNameBaggageToken = "k8s.cronjob.name"; +constexpr absl::string_view JobNameBaggageToken = "k8s.job.name"; +constexpr absl::string_view InstanceNameBaggageToken = "k8s.instance.name"; +constexpr absl::string_view LocalityRegionBaggageToken = "cloud.region"; +constexpr absl::string_view LocalityZoneBaggageToken = "cloud.availability_zone"; + +constexpr absl::string_view InstanceMetadataField = "NAME"; +constexpr absl::string_view NamespaceMetadataField = "NAMESPACE"; +constexpr absl::string_view ClusterMetadataField = "CLUSTER_ID"; +constexpr absl::string_view IdentityMetadataField = "IDENTITY"; +constexpr absl::string_view OwnerMetadataField = "OWNER"; +constexpr absl::string_view WorkloadMetadataField = "WORKLOAD_NAME"; +constexpr absl::string_view LabelsMetadataField = "LABELS"; +constexpr absl::string_view RegionMetadataField = "REGION"; +constexpr absl::string_view ZoneMetadataField = "AVAILABILITY_ZONE"; + +class WorkloadMetadataObject : public Envoy::StreamInfo::FilterState::Object, + public Envoy::Hashable { +public: + explicit WorkloadMetadataObject(absl::string_view instance_name, absl::string_view cluster_name, + absl::string_view namespace_name, absl::string_view workload_name, + absl::string_view canonical_name, + absl::string_view canonical_revision, absl::string_view app_name, + absl::string_view app_version, WorkloadType workload_type, + absl::string_view identity, absl::string_view region, + absl::string_view zone) + : instance_name_(instance_name), cluster_name_(cluster_name), namespace_name_(namespace_name), + workload_name_(workload_name), canonical_name_(canonical_name), + canonical_revision_(canonical_revision), app_name_(app_name), app_version_(app_version), + workload_type_(workload_type), identity_(identity), locality_region_(region), + locality_zone_(zone) {} + + absl::optional hash() const override; + Envoy::ProtobufTypes::MessagePtr serializeAsProto() const override; + std::vector> serializeAsPairs() const; + absl::optional serializeAsString() const override; + absl::optional owner() const; + std::string identity() const; + bool hasFieldSupport() const override { return true; } + using Envoy::StreamInfo::FilterState::Object::FieldType; + FieldType getField(absl::string_view) const override; + absl::optional field(absl::string_view field_name) const; + void setLabels(std::vector> labels) { labels_ = labels; } + std::vector> getLabels() const { return labels_; } + std::string baggage() const; + + const std::string instance_name_; + const std::string cluster_name_; + const std::string namespace_name_; + const std::string workload_name_; + const std::string canonical_name_; + const std::string canonical_revision_; + const std::string app_name_; + const std::string app_version_; + const WorkloadType workload_type_; + const std::string identity_; + const std::string locality_region_; + const std::string locality_zone_; + std::vector> labels_; +}; + +// Parse string workload type. +WorkloadType fromSuffix(absl::string_view suffix); + +// Parse owner field from kubernetes to detect the workload type. +WorkloadType parseOwner(absl::string_view owner, absl::string_view workload); + +// Convert a metadata object to a struct. +google::protobuf::Struct convertWorkloadMetadataToStruct(const WorkloadMetadataObject& obj); + +// Convert struct to a metadata object. +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata); + +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels); + +std::unique_ptr +convertStructToWorkloadMetadata(const google::protobuf::Struct& metadata, + const absl::flat_hash_set& additional_labels, + const absl::optional locality); + +// Convert endpoint metadata string to a metadata object. +// Telemetry metadata is compressed into a semicolon separated string: +// workload-name;namespace;canonical-service-name;canonical-service-revision;cluster-id. +// Telemetry metadata is stored as a string under "istio", "workload" field +// path. +absl::optional +convertEndpointMetadata(const std::string& endpoint_encoding); + +std::string serializeToStringDeterministic(const google::protobuf::Struct& metadata); + +// Convert from baggage encoding. +std::unique_ptr convertBaggageToWorkloadMetadata(absl::string_view baggage); +std::unique_ptr +convertBaggageToWorkloadMetadata(absl::string_view baggage, absl::string_view identity); + +} // namespace Common +} // namespace Istio diff --git a/extensions/common/metadata_object_test.cc b/extensions/common/metadata_object_test.cc new file mode 100644 index 00000000000..097e962dfd1 --- /dev/null +++ b/extensions/common/metadata_object_test.cc @@ -0,0 +1,224 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "extensions/common/metadata_object.h" + +#include "envoy/registry/registry.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Istio { +namespace Common { + +using Envoy::Protobuf::util::MessageDifferencer; +using ::testing::NiceMock; + +TEST(WorkloadMetadataObjectTest, SerializeAsString) { + constexpr absl::string_view identity = "spiffe://cluster.local/ns/default/sa/default"; + WorkloadMetadataObject deploy("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", WorkloadType::Deployment, identity, "", ""); + + WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", WorkloadType::Pod, identity, "", ""); + + WorkloadMetadataObject cronjob("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "foo-app", "v1", WorkloadType::CronJob, identity, "", + ""); + + WorkloadMetadataObject job("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", WorkloadType::Job, identity, "", ""); + + EXPECT_EQ(deploy.serializeAsString(), + absl::StrCat("type=deployment,workload=foo,name=pod-foo-1234,cluster=my-cluster,", + "namespace=default,service=foo-service,revision=v1alpha3")); + + EXPECT_EQ(pod.serializeAsString(), + absl::StrCat("type=pod,workload=foo,name=pod-foo-1234,cluster=my-cluster,", + "namespace=default,service=foo-service,revision=v1alpha3")); + + EXPECT_EQ(cronjob.serializeAsString(), + absl::StrCat("type=cronjob,workload=foo,name=pod-foo-1234,cluster=my-cluster,", + "namespace=default,service=foo-service,revision=v1alpha3,", + "app=foo-app,version=v1")); + + EXPECT_EQ(job.serializeAsString(), + absl::StrCat("type=job,workload=foo,name=pod-foo-1234,cluster=my-cluster,", + "namespace=default,service=foo-service,revision=v1alpha3")); +} + +void checkStructConversion(const Envoy::StreamInfo::FilterState::Object& data) { + const auto& obj = dynamic_cast(data); + auto pb = convertWorkloadMetadataToStruct(obj); + auto obj2 = convertStructToWorkloadMetadata(pb); + EXPECT_EQ(obj2->serializeAsString(), obj.serializeAsString()); + MessageDifferencer::Equals(*(obj2->serializeAsProto()), *(obj.serializeAsProto())); + EXPECT_EQ(obj2->hash(), obj.hash()); +} + +TEST(WorkloadMetadataObjectTest, ConversionWithLabels) { + constexpr absl::string_view identity = "spiffe://cluster.local/ns/default/sa/default"; + WorkloadMetadataObject deploy("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", WorkloadType::Deployment, identity, "", ""); + deploy.setLabels({{"label1", "value1"}, {"label2", "value2"}}); + auto pb = convertWorkloadMetadataToStruct(deploy); + auto obj1 = convertStructToWorkloadMetadata(pb, {"label1", "label2"}); + EXPECT_EQ(obj1->getLabels().size(), 2); + auto obj2 = convertStructToWorkloadMetadata(pb, {"label1"}); + EXPECT_EQ(obj2->getLabels().size(), 1); + absl::flat_hash_set empty; + auto obj3 = convertStructToWorkloadMetadata(pb, empty); + EXPECT_EQ(obj3->getLabels().size(), 0); +} + +TEST(WorkloadMetadataObjectTest, Conversion) { + { + constexpr absl::string_view identity = "spiffe://cluster.local/ns/default/sa/default"; + const auto r = convertBaggageToWorkloadMetadata( + "k8s.deployment.name=foo,k8s.cluster.name=my-cluster," + "k8s.namespace.name=default,service.name=foo-service,service.version=v1alpha3,app.name=foo-" + "app,app.version=latest", + identity); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1alpha3"); + EXPECT_EQ(absl::get(r->getField("type")), DeploymentSuffix); + EXPECT_EQ(absl::get(r->getField("workload")), "foo"); + EXPECT_EQ(absl::get(r->getField("name")), ""); + EXPECT_EQ(absl::get(r->getField("namespace")), "default"); + EXPECT_EQ(absl::get(r->getField("cluster")), "my-cluster"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-app"); + EXPECT_EQ(absl::get(r->getField("version")), "latest"); + EXPECT_EQ(r->identity(), identity); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata( + "k8s.pod.name=foo-pod-435,k8s.cluster.name=my-cluster,k8s.namespace.name=" + "test,k8s.instance.name=foo-instance-435,service.name=foo-service,service.version=v1beta2"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1beta2"); + EXPECT_EQ(absl::get(r->getField("type")), PodSuffix); + EXPECT_EQ(absl::get(r->getField("workload")), "foo-pod-435"); + EXPECT_EQ(absl::get(r->getField("name")), "foo-instance-435"); + EXPECT_EQ(absl::get(r->getField("namespace")), "test"); + EXPECT_EQ(absl::get(r->getField("cluster")), "my-cluster"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("version")), "v1beta2"); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata( + "k8s.job.name=foo-job-435,k8s.cluster.name=my-cluster,k8s.namespace.name=" + "test,k8s.instance.name=foo-instance-435,service.name=foo-service,service.version=v1beta4"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1beta4"); + EXPECT_EQ(absl::get(r->getField("type")), JobSuffix); + EXPECT_EQ(absl::get(r->getField("workload")), "foo-job-435"); + EXPECT_EQ(absl::get(r->getField("name")), "foo-instance-435"); + EXPECT_EQ(absl::get(r->getField("namespace")), "test"); + EXPECT_EQ(absl::get(r->getField("cluster")), "my-cluster"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("version")), "v1beta4"); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata( + "k8s.cronjob.name=foo-cronjob,k8s.cluster.name=my-cluster," + "k8s.namespace.name=test,service.name=foo-service,service.version=v1beta4"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1beta4"); + EXPECT_EQ(absl::get(r->getField("type")), CronJobSuffix); + EXPECT_EQ(absl::get(r->getField("workload")), "foo-cronjob"); + EXPECT_EQ(absl::get(r->getField("name")), ""); + EXPECT_EQ(absl::get(r->getField("namespace")), "test"); + EXPECT_EQ(absl::get(r->getField("cluster")), "my-cluster"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("version")), "v1beta4"); + checkStructConversion(*r); + } + + { + const auto r = + convertBaggageToWorkloadMetadata("k8s.deployment.name=foo,k8s.namespace.name=default," + "service.name=foo-service,service.version=v1alpha3"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1alpha3"); + EXPECT_EQ(absl::get(r->getField("type")), DeploymentSuffix); + EXPECT_EQ(absl::get(r->getField("workload")), "foo"); + EXPECT_EQ(absl::get(r->getField("namespace")), "default"); + EXPECT_EQ(absl::get(r->getField("cluster")), ""); + EXPECT_EQ(absl::get(r->getField("app")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("version")), "v1alpha3"); + checkStructConversion(*r); + } + + { + const auto r = + convertBaggageToWorkloadMetadata("service.name=foo-service,service.version=v1alpha3"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1alpha3"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("version")), "v1alpha3"); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata("app.name=foo-app,app.version=latest"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-app"); + EXPECT_EQ(absl::get(r->getField("revision")), "latest"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-app"); + EXPECT_EQ(absl::get(r->getField("version")), "latest"); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata( + "service.name=foo-service,service.version=v1alpha3,app.name=foo-app,app.version=latest"); + EXPECT_EQ(absl::get(r->getField("service")), "foo-service"); + EXPECT_EQ(absl::get(r->getField("revision")), "v1alpha3"); + EXPECT_EQ(absl::get(r->getField("app")), "foo-app"); + EXPECT_EQ(absl::get(r->getField("version")), "latest"); + checkStructConversion(*r); + } + + { + const auto r = convertBaggageToWorkloadMetadata("k8s.namespace.name=default"); + EXPECT_EQ(absl::get(r->getField("namespace")), "default"); + checkStructConversion(*r); + } +} + +TEST(WorkloadMetadataObjectTest, ConvertFromEmpty) { + google::protobuf::Struct node; + auto obj = convertStructToWorkloadMetadata(node); + EXPECT_EQ(obj->serializeAsString(), ""); + checkStructConversion(*obj); +} + +TEST(WorkloadMetadataObjectTest, ConvertFromEndpointMetadata) { + EXPECT_EQ(absl::nullopt, convertEndpointMetadata("")); + EXPECT_EQ(absl::nullopt, convertEndpointMetadata("a;b")); + EXPECT_EQ(absl::nullopt, convertEndpointMetadata("a;;;b")); + EXPECT_EQ(absl::nullopt, convertEndpointMetadata("a;b;c;d")); + auto obj = convertEndpointMetadata("foo-pod;default;foo-service;v1;my-cluster"); + ASSERT_TRUE(obj.has_value()); + EXPECT_EQ(obj->serializeAsString(), "workload=foo-pod,cluster=my-cluster," + "namespace=default,service=foo-service,revision=v1"); +} + +} // namespace Common +} // namespace Istio diff --git a/go.mod b/go.mod new file mode 100644 index 00000000000..f8e7ff4a8b4 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module istio.io/proxy + +go 1.25.0 + +require ( + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 + github.com/envoyproxy/go-control-plane v0.14.1-0.20260103185439-d6ff64e48402 + github.com/envoyproxy/go-control-plane/envoy v1.37.1-0.20260508085106-dc0bef478b3d + github.com/golang/protobuf v1.5.4 + github.com/google/go-cmp v0.7.0 + github.com/prometheus/client_model v0.6.2 + github.com/prometheus/common v0.46.0 + go.opentelemetry.io/proto/otlp v1.10.0 + go.starlark.net v0.0.0-20240123142251-f86470692795 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 + google.golang.org/grpc v1.81.0 + google.golang.org/protobuf v1.36.11 + gopkg.in/yaml.v2 v2.4.0 + sigs.k8s.io/yaml v1.4.0 +) + +require ( + cel.dev/expr v0.25.1 // indirect + github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000000..c8ea912e707 --- /dev/null +++ b/go.sum @@ -0,0 +1,89 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.14.1-0.20260103185439-d6ff64e48402 h1:Jm3kw/Enxm3pcwPwKpjNanZSVq6N/XyA0pkcLI9BVpk= +github.com/envoyproxy/go-control-plane v0.14.1-0.20260103185439-d6ff64e48402/go.mod h1:iuP4OVLgz85ISHlL+dS0cf6wg5cCz/KmuySk+g+F3uY= +github.com/envoyproxy/go-control-plane/envoy v1.37.1-0.20260508085106-dc0bef478b3d h1:c5+/ys+AHIPZWxo1vxZ20D/AIwbTfEiZXPXwu8Kmlj0= +github.com/envoyproxy/go-control-plane/envoy v1.37.1-0.20260508085106-dc0bef478b3d/go.mod h1:APnisR67BBt4fAcUT4yxY1IxYPCfM5VItdproxYa6G0= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.starlark.net v0.0.0-20240123142251-f86470692795 h1:LmbG8Pq7KDGkglKVn8VpZOZj6vb9b8nKEGcg9l03epM= +go.starlark.net v0.0.0-20240123142251-f86470692795/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/googleapis.bzl b/googleapis.bzl deleted file mode 100644 index 62cbd0e33ab..00000000000 --- a/googleapis.bzl +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") - -def googleapis_repositories(bind=True): - GOOGLEAPIS_BUILD_FILE = """ -package(default_visibility = ["//visibility:public"]) - -load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library") - -cc_proto_library( - name = "rpc_status_proto", - srcs = [ - "google/rpc/status.proto", - ], - visibility = ["//visibility:public"], - protoc = "//external:protoc", - default_runtime = "//external:protobuf", - deps = [ - "//external:cc_wkt_protos", - ], -) - -""" - new_git_repository( - name = "com_github_googleapis_googleapis", - build_file_content = GOOGLEAPIS_BUILD_FILE, - commit = "13ac2436c5e3d568bd0e938f6ed58b77a48aba15", # Oct 21, 2016 (only release pre-dates sha) - remote = "https://github.com/googleapis/googleapis.git", - ) - - if bind: - native.bind( - name = "rpc_status_proto", - actual = "@com_github_googleapis_googleapis//:rpc_status_proto", - ) - native.bind( - name = "rpc_status_proto_genproto", - actual = "@com_github_googleapis_googleapis//:rpc_status_proto_genproto", - ) diff --git a/include/istio/api_spec/http_api_spec_parser.h b/include/istio/api_spec/http_api_spec_parser.h deleted file mode 100644 index 9d841baebfe..00000000000 --- a/include/istio/api_spec/http_api_spec_parser.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_API_SPEC_HTTP_API_SPEC_PARSER_H_ -#define ISTIO_API_SPEC_HTTP_API_SPEC_PARSER_H_ - -#include - -#include "include/istio/control/http/check_data.h" -#include "mixer/v1/attributes.pb.h" -#include "mixer/v1/config/client/api_spec.pb.h" - -namespace istio { -namespace api_spec { - -// The interface to parse HTTPAPISpec and generate api attributes. -class HttpApiSpecParser { - public: - virtual ~HttpApiSpecParser() {} - - // Extract API attributes based on the APISpec. - // For a given http_method and path. - virtual void AddAttributes(const std::string& http_method, - const std::string& path, - ::istio::mixer::v1::Attributes* attributes) = 0; - - // Extract api key, return true if api key is extracted. - virtual bool ExtractApiKey(::istio::control::http::CheckData* check_data, - std::string* api_key) = 0; - - // The factory function to create an instance. - static std::unique_ptr Create( - const ::istio::mixer::v1::config::client::HTTPAPISpec& api_spec); -}; - -} // namespace api_spec -} // namespace istio - -#endif // ISTIO_API_SPEC_HTTP_API_SPEC_PARSER_H_ diff --git a/include/istio/control/http/check_data.h b/include/istio/control/http/check_data.h deleted file mode 100644 index e113cb986c6..00000000000 --- a/include/istio/control/http/check_data.h +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_CHECK_DATA_H -#define ISTIO_CONTROL_HTTP_CHECK_DATA_H - -#include -#include - -#include "src/istio/authn/context.pb.h" - -namespace istio { -namespace control { -namespace http { - -// The interface to extract HTTP data for Mixer check. -// Implemented by the environment (Envoy) and used by the library. -class CheckData { - public: - virtual ~CheckData() {} - - // Find "x-istio-attributes" HTTP header. - // If found, base64 decode its value, pass it out - virtual bool ExtractIstioAttributes(std::string *data) const = 0; - - // Get downstream tcp connection ip and port. - virtual bool GetSourceIpPort(std::string *ip, int *port) const = 0; - - // If SSL is used, get origin user name. - virtual bool GetSourceUser(std::string *user) const = 0; - - // Get request HTTP headers - virtual std::map GetRequestHeaders() const = 0; - - // Returns true if connection is mutual TLS enabled. - virtual bool IsMutualTLS() const = 0; - - // These headers are extracted into top level attributes. - // This is for standard HTTP headers. It supports both HTTP/1.1 and HTTP2 - // They can be retrieved at O(1) speed by environment (Envoy). - // It is faster to use the map from GetRequestHeader() call. - // - enum HeaderType { - HEADER_PATH = 0, - HEADER_HOST, - HEADER_SCHEME, - HEADER_USER_AGENT, - HEADER_METHOD, - HEADER_REFERER, - }; - virtual bool FindHeaderByType(HeaderType header_type, - std::string *value) const = 0; - - // A generic way to find any HTTP header. - // This is for custom HTTP headers, such as x-api-key - // Envoy platform requires "name" to be lower_case. - virtual bool FindHeaderByName(const std::string &name, - std::string *value) const = 0; - - // Find query parameter by name. - virtual bool FindQueryParameter(const std::string &name, - std::string *value) const = 0; - - // Find Cookie header. - virtual bool FindCookie(const std::string &name, - std::string *value) const = 0; - - // If the request has a JWT token and it is verified, get its payload as - // string map, and return true. Otherwise return false. - virtual bool GetJWTPayload( - std::map *payload) const = 0; - - // If the request has authentication result in header, parses data into the - // output result; returns true if success. Otherwise, returns false. - virtual bool GetAuthenticationResult(istio::authn::Result *result) const = 0; -}; - -// An interfact to update request HTTP headers with Istio attributes. -class HeaderUpdate { - public: - virtual ~HeaderUpdate() {} - - // Remove "x-istio-attributes" HTTP header. - virtual void RemoveIstioAttributes() = 0; - - // Base64 encode data, and add it as "x-istio-attributes" HTTP header. - virtual void AddIstioAttributes(const std::string &data) = 0; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_CHECK_DATA_H diff --git a/include/istio/control/http/controller.h b/include/istio/control/http/controller.h deleted file mode 100644 index cf2c8757b05..00000000000 --- a/include/istio/control/http/controller.h +++ /dev/null @@ -1,97 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_CONTROLLER_H -#define ISTIO_CONTROL_HTTP_CONTROLLER_H - -#include "include/istio/control/http/request_handler.h" -#include "include/istio/mixerclient/client.h" -#include "mixer/v1/config/client/client_config.pb.h" - -namespace istio { -namespace control { -namespace http { - -// An interface to support Mixer control. -// It takes MixerFitlerConfig and performs tasks to enforce -// mixer control over HTTP and TCP requests. -class Controller { - public: - virtual ~Controller() {} - - // Following two functions are used to manage service configs. - // Callers should call LookupServiceConfig to lookup the config with id - // and use AddServiceConfig to add the config if it is new. - - // Lookup a service config by its config id. Return true if found. - virtual bool LookupServiceConfig(const std::string& service_config_id) = 0; - - // Add a new service config. - virtual void AddServiceConfig( - const std::string& service_config_id, - const ::istio::mixer::v1::config::client::ServiceConfig& config) = 0; - - // A data struct to pass in per-route config. - struct PerRouteConfig { - // The per route destination.server name. - // It will be used to lookup per route config map. - std::string destination_service; - - // A unique ID to identify a config version for a service. - // Usually it is a sha of the whole service config. - // The config should have been added by AddServiceConfig(). - // If it is empty, destination_service is used to lookup - // service_configs map in the HttpClientConfig. - std::string service_config_id; - }; - - // Creates a HTTP request handler. - // The handler supports making Check and Report calls to Mixer. - // "per_route_config" is for supporting older version of Pilot which - // set per-route config in route opaque data. - virtual std::unique_ptr CreateRequestHandler( - const PerRouteConfig& per_route_config) = 0; - - // The initial data required by the Controller. It needs: - // * client_config: the mixer client config. - // * some functions provided by the environment (Envoy) - // * optional service config cache size. - struct Options { - Options(const ::istio::mixer::v1::config::client::HttpClientConfig& config) - : config(config) {} - - // Mixer filter config - const ::istio::mixer::v1::config::client::HttpClientConfig& config; - - // Some plaform functions for mixer client library. - ::istio::mixerclient::Environment env; - - // The LRU cache size for service config. - // If not set or is 0 default value, the cache size is 1000. - int service_config_cache_size{}; - }; - - // The factory function to create a new instance of the controller. - static std::unique_ptr Create(const Options& options); - - // Get statistics. - virtual void GetStatistics(::istio::mixerclient::Statistics* stat) const = 0; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_CONTROLLER_H diff --git a/include/istio/control/http/report_data.h b/include/istio/control/http/report_data.h deleted file mode 100644 index 0fabd7a8211..00000000000 --- a/include/istio/control/http/report_data.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_REPORT_DATA_H -#define ISTIO_CONTROL_HTTP_REPORT_DATA_H - -#include -#include - -namespace istio { -namespace control { -namespace http { - -// The interface to extract HTTP data for Mixer report. -// Implemented by the environment (Envoy) and used by the library. -class ReportData { - public: - virtual ~ReportData() {} - - // Get response HTTP headers. - virtual std::map GetResponseHeaders() const = 0; - - // Get additional report info. - struct ReportInfo { - uint64_t response_total_size; - uint64_t request_total_size; - uint64_t request_body_size; - uint64_t response_body_size; - std::chrono::nanoseconds duration; - int response_code; - }; - virtual void GetReportInfo(ReportInfo* info) const = 0; - - virtual bool GetDestinationIpPort(std::string* ip, int* port) const = 0; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_REPORT_DATA_H diff --git a/include/istio/control/http/request_handler.h b/include/istio/control/http/request_handler.h deleted file mode 100644 index 7974a8054a4..00000000000 --- a/include/istio/control/http/request_handler.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_REQUEST_HANDLER_H -#define ISTIO_CONTROL_HTTP_REQUEST_HANDLER_H - -#include "include/istio/control/http/check_data.h" -#include "include/istio/control/http/report_data.h" -#include "include/istio/mixerclient/client.h" - -namespace istio { -namespace control { -namespace http { - -// The interface to handle a HTTP request. -class RequestHandler { - public: - virtual ~RequestHandler() {} - - // Perform a Check call. It will: - // * extract forwarded attributes from client proxy - // * extract attributes from the request - // * extract attributes from the config. - // * if necessary, forward some attributes to downstream - // * make a Check call. - virtual ::istio::mixerclient::CancelFunc Check( - CheckData* check_data, HeaderUpdate* header_update, - ::istio::mixerclient::TransportCheckFunc transport, - ::istio::mixerclient::DoneFunc on_done) = 0; - - // Make a Report call. It will: - // * check service config to see if Report is required - // * extract more report attributes - // * make a Report call. - virtual void Report(ReportData* report_data) = 0; - - // Extract the request attributes for Report() call. - // This is called at Report time when Check() is not called. - // Normally request attributes are extracted at Check() call. - // This is for cases the requests are rejected by http filters - // before mixer, such as fault injection, or auth. - // - // Usage: at Envoy filter::log() function - // if (!hander) { - // handle = control->CreateHandler(); - // handler->ExtractRequestAttributes(); - // } - // handler->Report(); - // - virtual void ExtractRequestAttributes(CheckData* check_data) = 0; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_REQUEST_HANDLER_H diff --git a/include/istio/control/tcp/check_data.h b/include/istio/control/tcp/check_data.h deleted file mode 100644 index 991b6f8d421..00000000000 --- a/include/istio/control/tcp/check_data.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_CHECK_DATA_H -#define ISTIO_CONTROL_TCP_CHECK_DATA_H - -#include - -namespace istio { -namespace control { -namespace tcp { - -// The interface to extract TCP data for Mixer check call. -// Implemented by the environment (Envoy) and used by the library. -class CheckData { - public: - virtual ~CheckData() {} - - // Get downstream tcp connection ip and port. - virtual bool GetSourceIpPort(std::string* ip, int* port) const = 0; - - // If SSL is used, get origin user name. - virtual bool GetSourceUser(std::string* user) const = 0; - - // Returns true if connection is mutual TLS enabled. - virtual bool IsMutualTLS() const = 0; - - // Get downstream tcp connection id. - virtual std::string GetConnectionId() const = 0; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_CHECK_DATA_H diff --git a/include/istio/control/tcp/controller.h b/include/istio/control/tcp/controller.h deleted file mode 100644 index ff2e40a203e..00000000000 --- a/include/istio/control/tcp/controller.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_CONTROLLER_H -#define ISTIO_CONTROL_TCP_CONTROLLER_H - -#include "include/istio/control/tcp/request_handler.h" -#include "include/istio/mixerclient/client.h" -#include "mixer/v1/config/client/client_config.pb.h" - -namespace istio { -namespace control { -namespace tcp { - -// An interface to support Mixer control. -// It takes TcpClientConfig and performs tasks to enforce -// mixer control over TCP requests. -class Controller { - public: - virtual ~Controller() {} - - // Creates a TCP request handler. - // The handler supports making Check and Report calls to Mixer. - virtual std::unique_ptr CreateRequestHandler() = 0; - - // The initial data required by the Controller. It needs: - // * mixer_config: the mixer client config. - // * some functions provided by the environment (Envoy) - struct Options { - Options(const ::istio::mixer::v1::config::client::TcpClientConfig& config) - : config(config) {} - - // Mixer filter config - const ::istio::mixer::v1::config::client::TcpClientConfig& config; - - // Some plaform functions for mixer client library. - ::istio::mixerclient::Environment env; - }; - - // The factory function to create a new instance of the controller. - static std::unique_ptr Create(const Options& options); - - // Get statistics. - virtual void GetStatistics(::istio::mixerclient::Statistics* stat) const = 0; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_CONTROLLER_H diff --git a/include/istio/control/tcp/report_data.h b/include/istio/control/tcp/report_data.h deleted file mode 100644 index c97ea52e4ca..00000000000 --- a/include/istio/control/tcp/report_data.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_REPORT_DATA_H -#define ISTIO_CONTROL_TCP_REPORT_DATA_H - -#include -#include - -namespace istio { -namespace control { -namespace tcp { - -// The interface to extract TCP data for Mixer report call. -// Implemented by the environment (Envoy) and used by the library. -class ReportData { - public: - virtual ~ReportData() {} - - // Get upstream tcp connection IP and port. IP is returned in format of bytes. - virtual bool GetDestinationIpPort(std::string* ip, int* port) const = 0; - - // Get additional report data. - struct ReportInfo { - uint64_t send_bytes; - uint64_t received_bytes; - std::chrono::nanoseconds duration; - }; - virtual void GetReportInfo(ReportInfo* info) const = 0; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_REPORT_DATA_H diff --git a/include/istio/control/tcp/request_handler.h b/include/istio/control/tcp/request_handler.h deleted file mode 100644 index acebfd67a36..00000000000 --- a/include/istio/control/tcp/request_handler.h +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_REQUEST_HANDLER_H -#define ISTIO_CONTROL_TCP_REQUEST_HANDLER_H - -#include "include/istio/control/tcp/check_data.h" -#include "include/istio/control/tcp/report_data.h" -#include "include/istio/mixerclient/client.h" - -namespace istio { -namespace control { -namespace tcp { - -// The interface to handle a TCP request. -class RequestHandler { - public: - virtual ~RequestHandler() {} - - // Perform a Check call. It will: - // * extract downstream tcp connection attributes - // * check config, make a Check call if necessary. - virtual ::istio::mixerclient::CancelFunc Check( - CheckData* check_data, ::istio::mixerclient::DoneFunc on_done) = 0; - - // Make report call. - // This can be called multiple times for long connection. - // TODO(JimmyCYJ): Let TCP filter use - // void Report(ReportData* report_data, bool is_final_report), and deprecate - // this method. - virtual void Report(ReportData* report_data) = 0; - - // Make report call. - // If is_final_report is true, report all attributes. Otherwise, report delta - // attributes. - virtual void Report(ReportData* report_data, bool is_final_report) = 0; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_REQUEST_HANDLER_H diff --git a/include/istio/mixerclient/check_response.h b/include/istio/mixerclient/check_response.h deleted file mode 100644 index 20a661c4a26..00000000000 --- a/include/istio/mixerclient/check_response.h +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_CHECK_RESPONSE_H -#define ISTIO_MIXERCLIENT_CHECK_RESPONSE_H - -#include "google/protobuf/stubs/status.h" - -namespace istio { -namespace mixerclient { - -// The CheckResponseInfo holds response information in detail. -struct CheckResponseInfo { - // Whether this check response is from cache. - bool is_check_cache_hit{false}; - - // Whether this quota response is from cache. - bool is_quota_cache_hit{false}; - - // The check and quota response status. - ::google::protobuf::util::Status response_status{ - ::google::protobuf::util::Status::UNKNOWN}; -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_CHECK_RESPONSE_H diff --git a/include/istio/mixerclient/client.h b/include/istio/mixerclient/client.h deleted file mode 100644 index d869a1144f0..00000000000 --- a/include/istio/mixerclient/client.h +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_CLIENT_H -#define ISTIO_MIXERCLIENT_CLIENT_H - -#include "environment.h" -#include "include/istio/quota_config/requirement.h" -#include "options.h" - -#include - -namespace istio { -namespace mixerclient { - -// Defines the options to create an instance of MixerClient interface. -struct MixerClientOptions { - // Default constructor with default values. - MixerClientOptions() {} - - // Constructor with specified option values. - MixerClientOptions(const CheckOptions& check_options, - const ReportOptions& report_options, - const QuotaOptions& quota_options) - : check_options(check_options), - report_options(report_options), - quota_options(quota_options) {} - - // Check options. - CheckOptions check_options; - // Report options. - ReportOptions report_options; - // Quota options. - QuotaOptions quota_options; - // The environment functions. - Environment env; -}; - -// The statistics recorded by mixerclient library. -struct Statistics { - // Total number of check calls. - uint64_t total_check_calls; - // Total number of remote check calls. - uint64_t total_remote_check_calls; - // Total number of remote check calls that blocking origin requests. - uint64_t total_blocking_remote_check_calls; - - // Total number of quota calls. - uint64_t total_quota_calls; - // Total number of remote quota calls. - uint64_t total_remote_quota_calls; - // Total number of remote quota calls that blocking origin requests. - uint64_t total_blocking_remote_quota_calls; - - // Total number of report calls. - uint64_t total_report_calls; - // Total number of remote report calls. - uint64_t total_remote_report_calls; -}; - -class MixerClient { - public: - // Destructor - virtual ~MixerClient() {} - - // Attribute based calls will be used. - // Callers should pass in the full set of attributes for the call. - // The client will use the full set attributes to check cache. If cache - // miss, an attribute context based on the underlying gRPC stream will - // be used to generate attribute_update and send that to Mixer server. - // Callers don't need response data, they only need success or failure. - // The response data from mixer will be consumed by mixer client. - - // A check call. - virtual CancelFunc Check( - const ::istio::mixer::v1::Attributes& attributes, - const std::vector<::istio::quota_config::Requirement>& quotas, - TransportCheckFunc transport, CheckDoneFunc on_done) = 0; - - // A report call. - virtual void Report(const ::istio::mixer::v1::Attributes& attributes) = 0; - - // Get statistics. - virtual void GetStatistics(Statistics* stat) const = 0; -}; - -// Creates a MixerClient object. -std::unique_ptr CreateMixerClient( - const MixerClientOptions& options); - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_CLIENT_H diff --git a/include/istio/mixerclient/environment.h b/include/istio/mixerclient/environment.h deleted file mode 100644 index 9e24f9e8d15..00000000000 --- a/include/istio/mixerclient/environment.h +++ /dev/null @@ -1,75 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_ENVIRONMENT_H -#define ISTIO_MIXERCLIENT_ENVIRONMENT_H - -#include "check_response.h" -#include "google/protobuf/stubs/status.h" -#include "mixer/v1/service.pb.h" -#include "timer.h" - -namespace istio { -namespace mixerclient { - -// Defines a function prototype used when an asynchronous transport call -// is completed. -// Uses UNAVAILABLE status code to indicate network failure. -using DoneFunc = std::function; - -// Defines a function prototype used when an asynchronous transport call is -// completed, and passes response information in CheckResponse. -using CheckDoneFunc = std::function; - -// Defines a function prototype used to cancel an asynchronous transport call. -using CancelFunc = std::function; - -// Defines a function prototype to make an asynchronous Check call -using TransportCheckFunc = std::function; - -// Defines a function prototype to make an asynchronous Report call -using TransportReportFunc = std::function; - -// Defines a function prototype to generate an UUID -using UUIDGenerateFunc = std::function; - -// Store functions provided by the Environments, such as -// * transport function to make remote Check and Report calls -// * timer function to create a timer -struct Environment { - // Transport functions. - TransportCheckFunc check_transport; - TransportReportFunc report_transport; - - // Timer create function. - // Usually there are some restrictions on timer_create_func. - // Don't call it at program start, or init time, it is not ready. - // It is safe to call during Check() or Report() calls. - TimerCreateFunc timer_create_func; - - // UUID generating function - UUIDGenerateFunc uuid_generate_func; - - // TODO: Add logging function here. -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_ENVIRONMENT_H diff --git a/include/istio/mixerclient/options.h b/include/istio/mixerclient/options.h deleted file mode 100644 index cac81b392c4..00000000000 --- a/include/istio/mixerclient/options.h +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_OPTIONS_H -#define ISTIO_MIXERCLIENT_OPTIONS_H - -#include -#include -#include - -namespace istio { -namespace mixerclient { - -// Options controlling check behavior. -struct CheckOptions { - // Default constructor. - // Default options are chosen from experience. - CheckOptions() : num_entries(10000) {} - - // Constructor. - // cache_entries is the maximum number of cache entries that can be kept in - // the cache. Cache is disabled when cache_entries <= 0. - CheckOptions(int cache_entries) : num_entries(cache_entries) {} - - // Maximum number of cache entries kept in the cache. - // Set to 0 will disable caching. - const int num_entries; - - // If true, Check is passed for any network failures. - bool network_fail_open = true; -}; - -// Options controlling report batch. -struct ReportOptions { - // Default constructor. - // Default to batch up to 1000 reports or 1 seconds. - ReportOptions() : max_batch_entries(1000), max_batch_time_ms(1000) {} - - // Constructor. - ReportOptions(int max_batch_entries, int max_batch_time_ms) - : max_batch_entries(max_batch_entries), - max_batch_time_ms(max_batch_time_ms) {} - - // Maximum number of reports to be batched. - const int max_batch_entries; - - // Maximum milliseconds a report item stayed in the buffer for batching. - const int max_batch_time_ms; -}; - -// Options controlling quota behavior. -struct QuotaOptions { - // Default constructor. - QuotaOptions() : num_entries(10000), expiration_ms(600000) {} - - // Constructor. - // cache_entries is the maximum number of cache entries that can be kept in - // the cache. Cache is disabled when cache_entries <= 0. - // expiration_ms is the maximum milliseconds an idle cached quota is removed. - QuotaOptions(int cache_entries, int expiration_ms) - : num_entries(cache_entries), expiration_ms(expiration_ms) {} - - // Maximum number of cache entries kept in the cache. - // Set to 0 will disable caching. - const int num_entries; - - // Maximum milliseconds before an idle cached quota should be deleted. - const int expiration_ms; -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_OPTIONS_H diff --git a/include/istio/mixerclient/timer.h b/include/istio/mixerclient/timer.h deleted file mode 100644 index 8dd1256f639..00000000000 --- a/include/istio/mixerclient/timer.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_TIMER_H -#define ISTIO_MIXERCLIENT_TIMER_H - -#include -#include - -namespace istio { -namespace mixerclient { - -// Represent a timer created by caller's environment. -class Timer { - public: - // Delete the timer, stopping it first if needed. - virtual ~Timer() {} - - // Stop a pending timeout without destroying the underlying timer. - virtual void Stop() = 0; - - // Start a pending timeout. If a timeout is already pending, - // it will be reset to the new timeout. - virtual void Start(int interval_ms) = 0; -}; - -// Defines a function to create a timer calling the function -// with desired interval. The returned object can be used to stop -// the timer. -using TimerCreateFunc = - std::function(std::function timer_func)>; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_TIMER_H diff --git a/include/istio/prefetch/quota_prefetch.h b/include/istio/prefetch/quota_prefetch.h deleted file mode 100644 index fa2cb99715b..00000000000 --- a/include/istio/prefetch/quota_prefetch.h +++ /dev/null @@ -1,75 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_PREFETCH_QUOTA_PREFETCH_H_ -#define ISTIO_PREFETCH_QUOTA_PREFETCH_H_ - -#include -#include -#include - -namespace istio { -namespace prefetch { - -// The class to prefetch rate limiting quota. -class QuotaPrefetch { - public: - // Define a time stamp type. - // Ideally, Now() timestamp should be used inside the functions. - // But for easy unit_test, pass the time in. - // The input time should be always increasing. - typedef std::chrono::time_point Tick; - - // Define the options - struct Options { - // The predict window to count number of requests and use it - // as prefetch amount - std::chrono::milliseconds predict_window; - - // The minimum prefetch amount. - int min_prefetch_amount; - - // The wait window for the next prefetch if last prefetch is - // negative. (Its request amount is not granted). - std::chrono::milliseconds close_wait_window; - - // Constructor with default values. - Options(); - }; - - // Define the transport. - // The callback function after quota allocation is done from the server. - // Set amount = -1 If there are any network failures. - typedef std::function - DoneFunc; - // The transport function to send quota allocation to server. - typedef std::function TransportFunc; - - // virtual destructor. - virtual ~QuotaPrefetch() {} - - // The creator for the prefetch class - static std::unique_ptr Create(TransportFunc transport, - const Options& options, Tick t); - - // Perform a quota check with the amount. Return true if granted. - virtual bool Check(int amount, Tick t) = 0; -}; - -} // namespace prefetch -} // namespace istio - -#endif // ISTIO_PREFETCH_QUOTA_PREFETCH_H_ diff --git a/include/istio/quota_config/BUILD b/include/istio/quota_config/BUILD deleted file mode 100644 index 7afae951b7f..00000000000 --- a/include/istio/quota_config/BUILD +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "headers_lib", - hdrs = [ - "config_parser.h", - "requirement.h", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "requirement_header", - hdrs = [ - "requirement.h", - ], - visibility = ["//visibility:public"], -) diff --git a/include/istio/quota_config/config_parser.h b/include/istio/quota_config/config_parser.h deleted file mode 100644 index 8ecf33c0e88..00000000000 --- a/include/istio/quota_config/config_parser.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_QUOTA_CONFIG_CONFIG_PARSER_H_ -#define ISTIO_QUOTA_CONFIG_CONFIG_PARSER_H_ - -#include -#include - -#include "include/istio/quota_config/requirement.h" -#include "mixer/v1/attributes.pb.h" -#include "mixer/v1/config/client/quota.pb.h" - -namespace istio { -namespace quota_config { - -// An object to parse quota config to generate quota requirements. -class ConfigParser { - public: - virtual ~ConfigParser() {} - - // Get quota requirements for a attribute set. - virtual void GetRequirements(const ::istio::mixer::v1::Attributes& attributes, - std::vector* results) const = 0; - - // The factory function to create a new instance of the parser. - static std::unique_ptr Create( - const ::istio::mixer::v1::config::client::QuotaSpec& spec_pb); -}; - -} // namespace quota_config -} // namespace istio - -#endif // ISTIO_QUOTA_CONFIG_CONFIG_PARSER_H_ diff --git a/include/istio/quota_config/requirement.h b/include/istio/quota_config/requirement.h deleted file mode 100644 index 0401f5a70e4..00000000000 --- a/include/istio/quota_config/requirement.h +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_QUOTA_CONFIG_REQUIREMENT_H_ -#define ISTIO_QUOTA_CONFIG_REQUIREMENT_H_ - -#include - -namespace istio { -namespace quota_config { - -// A struct to represent one quota requirement. -struct Requirement { - // The quota name. - std::string quota; - // The amount to charge - int64_t charge; -}; - -} // namespace quota_config -} // namespace istio - -#endif // ISTIO_QUOTA_CONFIG_REQUIREMENT_H_ diff --git a/include/istio/utils/BUILD b/include/istio/utils/BUILD deleted file mode 100644 index ca2723bd115..00000000000 --- a/include/istio/utils/BUILD +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "headers_lib", - hdrs = [ - "attributes_builder.h", - "md5.h", - "protobuf.h", - "status.h", - ], - visibility = ["//visibility:public"], -) - -cc_library( - name = "simple_lru_cache", - srcs = ["google_macros.h"], - hdrs = [ - "simple_lru_cache.h", - "simple_lru_cache_inl.h", - ], - visibility = ["//visibility:public"], -) diff --git a/include/istio/utils/attributes_builder.h b/include/istio/utils/attributes_builder.h deleted file mode 100644 index da124977414..00000000000 --- a/include/istio/utils/attributes_builder.h +++ /dev/null @@ -1,119 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_UTILS_ATTRIBUTES_BUILDER_H -#define ISTIO_UTILS_ATTRIBUTES_BUILDER_H - -#include -#include -#include - -#include "google/protobuf/map.h" -#include "mixer/v1/attributes.pb.h" - -namespace istio { -namespace utils { - -// Builder class to add attribute to protobuf Attributes. -// Its usage: -// builder(attribute).Add("key", value) -// .Add("key2", value2); -class AttributesBuilder { - public: - AttributesBuilder(::istio::mixer::v1::Attributes* attributes) - : attributes_(attributes) {} - - void AddString(const std::string& key, const std::string& str) { - (*attributes_->mutable_attributes())[key].set_string_value(str); - } - - void AddBytes(const std::string& key, const std::string& bytes) { - (*attributes_->mutable_attributes())[key].set_bytes_value(bytes); - } - - void AddInt64(const std::string& key, int64_t value) { - (*attributes_->mutable_attributes())[key].set_int64_value(value); - } - - void AddDouble(const std::string& key, double value) { - (*attributes_->mutable_attributes())[key].set_double_value(value); - } - - void AddBool(const std::string& key, bool value) { - (*attributes_->mutable_attributes())[key].set_bool_value(value); - } - - void AddTimestamp( - const std::string& key, - const std::chrono::time_point& value) { - auto time_stamp = - (*attributes_->mutable_attributes())[key].mutable_timestamp_value(); - long long nanos = std::chrono::duration_cast( - value.time_since_epoch()) - .count(); - time_stamp->set_seconds(nanos / 1000000000); - time_stamp->set_nanos(nanos % 1000000000); - } - - void AddDuration(const std::string& key, - const std::chrono::nanoseconds& value) { - auto duration = - (*attributes_->mutable_attributes())[key].mutable_duration_value(); - duration->set_seconds(value.count() / 1000000000); - duration->set_nanos(value.count() % 1000000000); - } - - void AddStringMap(const std::string& key, - const std::map& string_map) { - if (string_map.size() == 0) { - return; - } - auto entries = (*attributes_->mutable_attributes())[key] - .mutable_string_map_value() - ->mutable_entries(); - entries->clear(); - for (const auto& map_it : string_map) { - (*entries)[map_it.first] = map_it.second; - } - } - - void AddProtobufStringMap( - const std::string& key, - const google::protobuf::Map& string_map) { - if (string_map.empty()) { - return; - } - auto entries = (*attributes_->mutable_attributes())[key] - .mutable_string_map_value() - ->mutable_entries(); - entries->clear(); - for (const auto& map_it : string_map) { - (*entries)[map_it.first] = map_it.second; - } - } - - bool HasAttribute(const std::string& key) const { - const auto& attrs_map = attributes_->attributes(); - return attrs_map.find(key) != attrs_map.end(); - } - - private: - ::istio::mixer::v1::Attributes* attributes_; -}; - -} // namespace utils -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_ATTRIBUTES_BUILDER_H diff --git a/include/istio/utils/google_macros.h b/include/istio/utils/google_macros.h deleted file mode 100644 index e2ffd5c781e..00000000000 --- a/include/istio/utils/google_macros.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_UTILS_GOOGLE_MACROS_H_ -#define ISTIO_UTILS_GOOGLE_MACROS_H_ - -#undef GOOGLE_DISALLOW_EVIL_CONSTRUCTORS -#define GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) - -#endif // ISTIO_UTILS_GOOGLE_MACROS_H_ diff --git a/include/istio/utils/md5.h b/include/istio/utils/md5.h deleted file mode 100644 index 19b1c9dbe8f..00000000000 --- a/include/istio/utils/md5.h +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_UTILS_MD5_H_ -#define ISTIO_UTILS_MD5_H_ - -#include -#include -#include "openssl/md5.h" - -namespace istio { -namespace utils { - -// Define a MD5 Digest by calling OpenSSL -class MD5 { - public: - MD5(); - - // Updates the context with data. - MD5& Update(const void* data, size_t size); - - // A helper function for const char* - MD5& Update(const char* str) { return Update(str, strlen(str)); } - - // A helper function for const string - MD5& Update(const std::string& str) { return Update(str.data(), str.size()); } - - // A helper function for int - MD5& Update(int d) { return Update(&d, sizeof(d)); } - - // The MD5 digest is always 128 bits = 16 bytes - static const int kDigestLength = 16; - - // Returns the digest as string. - std::string Digest(); - - // A short form of generating MD5 for a string - std::string operator()(const void* data, size_t size); - - // Converts a binary digest string to a printable string. - // It is for debugging and unit-test only. - static std::string DebugString(const std::string& digest); - - private: - // MD5 context. - MD5_CTX ctx_; - // The final MD5 digest. - unsigned char digest_[kDigestLength]; - // A flag to indicate if MD5_final is called or not. - bool finalized_; -}; - -} // namespace utils -} // namespace istio - -#endif // ISTIO_UTILS_MD5_H_ diff --git a/include/istio/utils/protobuf.h b/include/istio/utils/protobuf.h deleted file mode 100644 index 4866d3903d4..00000000000 --- a/include/istio/utils/protobuf.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_UTILS_PROTOBUF_H_ -#define ISTIO_UTILS_PROTOBUF_H_ - -#include "google/protobuf/duration.pb.h" -#include "google/protobuf/stubs/status.h" -#include "google/protobuf/timestamp.pb.h" - -#include - -namespace istio { -namespace utils { - -// Convert system_clock time to protobuf timestamp -::google::protobuf::Timestamp CreateTimestamp( - std::chrono::system_clock::time_point tp); - -// Convert from chrono duration to protobuf duration. -::google::protobuf::Duration CreateDuration(std::chrono::nanoseconds value); - -// Convert from prtoobuf duration to chrono duration. -std::chrono::milliseconds ToMilliseonds( - const ::google::protobuf::Duration& duration); - -bool InvalidDictionaryStatus(const ::google::protobuf::util::Status& status); - -} // namespace utils -} // namespace istio - -#endif // ISTIO_UTILS_PROTOBUF_H_ diff --git a/include/istio/utils/simple_lru_cache.h b/include/istio/utils/simple_lru_cache.h deleted file mode 100644 index 983a9994dff..00000000000 --- a/include/istio/utils/simple_lru_cache.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// For inclusion in .h files. The real class definition is in -// simple_lru_cache_inl.h. - -#ifndef ISTIO_UTILS_SIMPLE_LRU_CACHE_H_ -#define ISTIO_UTILS_SIMPLE_LRU_CACHE_H_ - -#include -#include // for hash<> - -namespace istio { -namespace utils { - -namespace internal { -template -struct SimpleLRUHash : public std::hash {}; -} // namespace internal - -template , - typename EQ = std::equal_to > -class SimpleLRUCache; - -// Deleter is a functor that defines how to delete a Value*. That is, it -// contains a public method: -// operator() (Value* value) -// See example in the associated unittest. -template , - typename EQ = std::equal_to > -class SimpleLRUCacheWithDeleter; - -} // namespace utils -} // namespace istio - -#endif // ISTIO_UTILS_SIMPLE_LRU_CACHE_H_ diff --git a/include/istio/utils/simple_lru_cache_inl.h b/include/istio/utils/simple_lru_cache_inl.h deleted file mode 100644 index 09eea44fac2..00000000000 --- a/include/istio/utils/simple_lru_cache_inl.h +++ /dev/null @@ -1,1090 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// A generic LRU cache that maps from type Key to Value*. -// -// . Memory usage is fairly high: on a 64-bit architecture, a cache with -// 8-byte keys can use 108 bytes per element, not counting the -// size of the values. This overhead can be significant if many small -// elements are stored in the cache. -// -// . Lookup returns a "Value*". Client should call "Release" when done. -// -// . Override "RemoveElement" if you want to be notified when an -// element is being removed. The default implementation simply calls -// "delete" on the pointer. -// -// . Call Clear() before destruction. -// -// . No internal locking is done: if the same cache will be shared -// by multiple threads, the caller should perform the required -// synchronization before invoking any operations on the cache. -// Note a reader lock is not sufficient as Lookup() updates the pin count. -// -// . We provide support for setting a "max_idle_time". Entries -// are discarded when they have not been used for a time -// greater than the specified max idle time. If you do not -// call SetMaxIdleSeconds(), entries never expire (they can -// only be removed to meet size constraints). -// -// . We also provide support for a strict age-based eviction policy -// instead of LRU. See SetAgeBasedEviction(). - -#ifndef ISTIO_UTILS_SIMPLE_LRU_CACHE_INL_H_ -#define ISTIO_UTILS_SIMPLE_LRU_CACHE_INL_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "google_macros.h" -#include "simple_lru_cache.h" - -namespace istio { -namespace utils { - -// Define number of microseconds for a second. -const int64_t kSecToUsec = 1000000; - -// Define a simple cycle timer interface to encapsulate timer related code. -// The concept is from CPU cycle. The cycle clock code from -// https://github.com/google/benchmark/blob/master/src/cycleclock.h can be used. -// But that code only works for some platforms. To make code works for all -// platforms, SimpleCycleTimer class uses a fake CPU cycle each taking a -// microsecond. If needed, this timer class can be easily replaced by a -// real cycle_clock. -class SimpleCycleTimer { - public: - // Return the current cycle in microseconds. - static int64_t Now() { - struct timeval tv; - gettimeofday(&tv, NULL); - return static_cast(tv.tv_sec * kSecToUsec + tv.tv_usec); - } - // Return number of cycles in a second. - static int64_t Frequency() { return kSecToUsec; } - - private: - SimpleCycleTimer(); // no instances -}; - -// A constant iterator. a client of SimpleLRUCache should not create these -// objects directly, instead, create objects of type -// SimpleLRUCache::const_iterator. This is created inside of -// SimpleLRUCache::begin(),end(). Key and Value are the same as the template -// args to SimpleLRUCache Elem - the Value type for the internal hash_map that -// the SimpleLRUCache maintains H and EQ are the same as the template arguments -// for SimpleLRUCache -// -// NOTE: the iterator needs to keep a copy of end() for the Cache it is -// iterating over this is so SimpleLRUCacheConstIterator does not try to update -// its internal pair if its internal hash_map iterator is pointing -// to end see the implementation of operator++ for an example. -// -// NOTE: DO NOT SAVE POINTERS TO THE ITEM RETURNED BY THIS ITERATOR -// e.g. SimpleLRUCacheConstIterator it = something; do not say KeyToSave -// &something->first this will NOT work., as soon as you increment the iterator -// this will be gone. :( - -template -class SimpleLRUCacheConstIterator - : public std::iterator> { - public: - typedef typename MapType::const_iterator HashMapConstIterator; - // Allow parent template's types to be referenced without qualification. - typedef typename SimpleLRUCacheConstIterator::reference reference; - typedef typename SimpleLRUCacheConstIterator::pointer pointer; - - // This default constructed Iterator can only be assigned to or destroyed. - // All other operations give undefined behaviour. - SimpleLRUCacheConstIterator() {} - SimpleLRUCacheConstIterator(HashMapConstIterator it, - HashMapConstIterator end); - SimpleLRUCacheConstIterator& operator++(); - - reference operator*() { return external_view_; } - pointer operator->() { return &external_view_; } - - // For LRU mode, last_use_time() returns elements last use time. - // See GetLastUseTime() description for more information. - int64_t last_use_time() const { return last_use_; } - - // For age-based mode, insertion_time() returns elements insertion time. - // See GetInsertionTime() description for more information. - int64_t insertion_time() const { return last_use_; } - - friend bool operator==(const SimpleLRUCacheConstIterator& a, - const SimpleLRUCacheConstIterator& b) { - return a.it_ == b.it_; - } - - friend bool operator!=(const SimpleLRUCacheConstIterator& a, - const SimpleLRUCacheConstIterator& b) { - return !(a == b); - } - - private: - HashMapConstIterator it_; - HashMapConstIterator end_; - std::pair external_view_; - int64_t last_use_; -}; - -// Each entry uses the following structure -template -struct SimpleLRUCacheElem { - Key key; // The key - Value* value; // The stored value - int pin; // Number of outstanding releases - size_t units; // Number of units for this value - SimpleLRUCacheElem* next = nullptr; // Next entry in LRU chain - SimpleLRUCacheElem* prev = nullptr; // Prev entry in LRU chain - int64_t last_use_; // Timestamp of last use (in LRU mode) - // or creation (in age-based mode) - - SimpleLRUCacheElem(const Key& k, Value* v, int p, size_t u, int64_t last_use) - : key(k), value(v), pin(p), units(u), last_use_(last_use) {} - - bool IsLinked() const { - // If we are in the LRU then next and prev should be non-NULL. Otherwise - // both should be properly initialized to nullptr. - assert(static_cast(next == nullptr) == - static_cast(prev == nullptr)); - return next != nullptr; - } - - void Unlink() { - if (!IsLinked()) return; - prev->next = next; - next->prev = prev; - prev = nullptr; - next = nullptr; - } - - void Link(SimpleLRUCacheElem* head) { - next = head->next; - prev = head; - next->prev = this; // i.e. head->next->prev = this; - prev->next = this; // i.e. head->next = this; - } - static const int64_t kNeverUsed = -1; -}; - -template -const int64_t SimpleLRUCacheElem::kNeverUsed; - -// A simple class passed into various cache methods to change the -// behavior for that single call. -class SimpleLRUCacheOptions { - public: - SimpleLRUCacheOptions() : update_eviction_order_(true) {} - - // If false neither the last modified time (for based age eviction) nor - // the element ordering (for LRU eviction) will be updated. - // This value must be the same for both Lookup and Release. - // The default is true. - bool update_eviction_order() const { return update_eviction_order_; } - void set_update_eviction_order(bool v) { update_eviction_order_ = v; } - - private: - bool update_eviction_order_; -}; - -// The MapType's value_type must be pair -template -class SimpleLRUCacheBase { - public: - // class ScopedLookup - // If you have some code that looks like this: - // val = c->Lookup(key); - // if (val) { - // if (something) { - // c->Release(key, val); - // return; - // } - // if (something else) { - // c->Release(key, val); - // return; - // } - // Then ScopedLookup will make the code simpler. It automatically - // releases the value when the instance goes out of scope. - // Example: - // ScopedLookup lookup(c, key); - // if (lookup.Found()) { - // ... - // - // NOTE: Be extremely careful when using ScopedLookup with Mutexes. This - // code is safe since the lock will be released after the ScopedLookup is - // destroyed. - // MutexLock l(&mu_); - // ScopedLookup lookup(....); - // - // This is NOT safe since the lock is released before the ScopedLookup is - // destroyed, and consequently the value will be unpinned without the lock - // being held. - // mu_.Lock(); - // ScopedLookup lookup(....); - // ... - // mu_.Unlock(); - class ScopedLookup { - public: - ScopedLookup(SimpleLRUCacheBase* cache, const Key& key) - : cache_(cache), - key_(key), - value_(cache_->LookupWithOptions(key_, options_)) {} - - ScopedLookup(SimpleLRUCacheBase* cache, const Key& key, - const SimpleLRUCacheOptions& options) - : cache_(cache), - key_(key), - options_(options), - value_(cache_->LookupWithOptions(key_, options_)) {} - - ~ScopedLookup() { - if (value_ != nullptr) cache_->ReleaseWithOptions(key_, value_, options_); - } - const Key& key() const { return key_; } - Value* value() const { return value_; } - bool Found() const { return value_ != nullptr; } - const SimpleLRUCacheOptions& options() const { return options_; } - - private: - SimpleLRUCacheBase* const cache_; - const Key key_; - const SimpleLRUCacheOptions options_; - Value* const value_; - - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ScopedLookup); - }; - - // Create a cache that will hold up to the specified number of units. - // Usually the units will be byte sizes, but in some caches different - // units may be used. For instance, we may want each open file to - // be one unit in an open-file cache. - // - // By default, the max_idle_time is infinity; i.e. entries will - // stick around in the cache regardless of how old they are. - explicit SimpleLRUCacheBase(int64_t total_units); - - // Release all resources. Cache must have been "Clear"ed. This - // requirement is imposed because "Clear()" will call - // "RemoveElement" for each element in the cache. The destructor - // cannot do that because it runs after any subclass destructor. - virtual ~SimpleLRUCacheBase() { - assert(table_.size() == 0); - assert(defer_.size() == 0); - } - - // Change the maximum size of the cache to the specified number of units. - // If necessary, entries will be evicted to comply with the new size. - void SetMaxSize(int64_t total_units) { - max_units_ = total_units; - GarbageCollect(); - } - - // Change the max idle time to the specified number of seconds. - // If "seconds" is a negative number, it sets the max idle time - // to infinity. - void SetMaxIdleSeconds(double seconds) { - SetTimeout(seconds, true /* lru */); - } - - // Stop using the LRU eviction policy and instead expire anything - // that has been in the cache for more than the specified number - // of seconds. - // If "seconds" is a negative number, entries don't expire but if - // we need to make room the oldest entries will be removed first. - // You can't set both a max idle time and age-based eviction. - void SetAgeBasedEviction(double seconds) { - SetTimeout(seconds, false /* lru */); - } - - // If cache contains an entry for "k", return a pointer to it. - // Else return nullptr. - // - // If a value is returned, the caller must call "Release" when it no - // longer needs that value. This functionality is useful to prevent - // the value from being evicted from the cache until it is no longer - // being used. - Value* Lookup(const Key& k) { - return LookupWithOptions(k, SimpleLRUCacheOptions()); - } - - // Same as "Lookup(Key)" but allows for additional options. See - // the SimpleLRUCacheOptions object for more information. - Value* LookupWithOptions(const Key& k, const SimpleLRUCacheOptions& options); - - // Removes the pinning done by an earlier "Lookup". After this call, - // the caller should no longer depend on the value sticking around. - // - // If there are no more pins on this entry, it may be deleted if - // either it has been "Remove"d, or the cache is overfull. - // In this case "RemoveElement" will be called. - void Release(const Key& k, Value* value) { - ReleaseWithOptions(k, value, SimpleLRUCacheOptions()); - } - - // Same as "Release(Key, Value)" but allows for additional options. See - // the SimpleLRUCacheOptions object for more information. Take care - // that the SimpleLRUCacheOptions object passed into this method is - // compatible with SimpleLRUCacheOptions object passed into Lookup. - // If they are incompatible it can put the cache into some unexpected - // states. Better yet, just use a ScopedLookup which takes care of this - // for you. - void ReleaseWithOptions(const Key& k, Value* value, - const SimpleLRUCacheOptions& options); - - // Insert the specified "k,value" pair in the cache. Remembers that - // the value occupies "units" units. For "InsertPinned", the newly - // inserted value will be pinned in the cache: the caller should - // call "Release" when it wants to remove the pin. - // - // Any old entry for "k" is "Remove"d. - // - // If the insertion causes the cache to become overfull, unpinned - // entries will be deleted in an LRU order to make room. - // "RemoveElement" will be called for each such entry. - void Insert(const Key& k, Value* value, size_t units) { - InsertPinned(k, value, units); - Release(k, value); - } - void InsertPinned(const Key& k, Value* value, size_t units); - - // Change the reported size of an object. - void UpdateSize(const Key& k, const Value* value, size_t units); - - // return true iff pair is still in use - // (i.e., either in the table or the deferred list) - // Note, if (value == nullptr), only key is used for matching - bool StillInUse(const Key& k) const { return StillInUse(k, nullptr); } - bool StillInUse(const Key& k, const Value* value) const; - - // Remove any entry corresponding to "k" from the cache. Note that - // if the entry is pinned because of an earlier Lookup or - // InsertPinned operation, the entry will disappear from further - // Lookups, but will not actually be deleted until all of the pins - // are released. - // - // "RemoveElement" will be called if an entry is actually removed. - void Remove(const Key& k); - - // Removes all entries from the cache. The pinned entries will - // disappear from further Lookups, but will not actually be deleted - // until all of the pins are released. This is different from Clear() - // because Clear() cleans up everything and requires that all Values are - // unpinned. - // - // "Remove" will be called for each cache entry. - void RemoveAll(); - - // Remove all unpinned entries from the cache. - // "RemoveElement" will be called for each such entry. - void RemoveUnpinned(); - - // Remove all entries from the cache. It is an error to call this - // operation if any entry in the cache is currently pinned. - // - // "RemoveElement" will be called for all removed entries. - void Clear(); - - // Remove all entries which have exceeded their max idle time or age - // set using SetMaxIdleSeconds() or SetAgeBasedEviction() respectively. - void RemoveExpiredEntries() { - if (max_idle_ >= 0) DiscardIdle(max_idle_); - } - - // Return current size of cache - int64_t Size() const { return units_; } - - // Return number of entries in the cache. This value may differ - // from Size() if some of the elements have a cost != 1. - int64_t Entries() const { return table_.size(); } - - // Return size of deferred deletions - int64_t DeferredSize() const; - - // Return number of deferred deletions - int64_t DeferredEntries() const; - - // Return size of entries that are pinned but not deferred - int64_t PinnedSize() const { return pinned_units_; } - - // Return maximum size of cache - int64_t MaxSize() const { return max_units_; } - - // Return the age (in microseconds) of the least recently used element in - // the cache. If the cache is empty, zero (0) is returned. - int64_t AgeOfLRUItemInMicroseconds() const; - - // In LRU mode, this is the time of last use in cycles. Last use is defined - // as time of last Release(), Insert() or InsertPinned() methods. - // - // The timer is not updated on Lookup(), so GetLastUseTime() will - // still return time of previous access until Release(). - // - // Returns -1 if key was not found, CycleClock cycle count otherwise. - // REQUIRES: LRU mode - int64_t GetLastUseTime(const Key& k) const; - - // For age-based mode, this is the time of element insertion in cycles, - // set by Insert() and InsertPinned() methods. - // Returns -1 if key was not found, CycleClock cycle count otherwise. - // REQUIRES: age-based mode - int64_t GetInsertionTime(const Key& k) const; - - // Invokes 'DebugIterator' on each element in the cache. The - // 'pin_count' argument supplied will be the pending reference count - // for the element. The 'is_deferred' argument will be true for - // elements that have been removed but whose removal is deferred. - // The supplied value for "ouput" will be passed to the DebugIterator. - void DebugOutput(std::string* output) const; - - // Return a std::string that summarizes the contents of the cache. - // - // The output of this function is not suitable for parsing by borgmon. For an - // example of exporting the summary information in a borgmon mapped-value - // format, see GFS_CS_BufferCache::ExportSummaryAsMap in - // file/gfs/chunkserver/gfs_chunkserver.{cc,h} - std::string Summary() const { - std::stringstream ss; - ss << PinnedSize() << "/" << DeferredSize() << "/" << Size() << " p/d/a"; - return ss.str(); - } - - // STL style const_iterator support - typedef SimpleLRUCacheConstIterator const_iterator; - friend class SimpleLRUCacheConstIterator; - const_iterator begin() const { - return const_iterator(table_.begin(), table_.end()); - } - const_iterator end() const { - return const_iterator(table_.end(), table_.end()); - } - - // Invokes the 'resize' operation on the underlying map with the given - // size hint. The exact meaning of this operation and its availability - // depends on the supplied MapType. - void ResizeTable(typename MapType::size_type size_hint) { - table_.resize(size_hint); - } - - protected: - // Override this operation if you want to control how a value is - // cleaned up. For example, if the value is a "File", you may want - // to "Close" it instead of "delete"ing it. - // - // Not actually implemented here because often value's destructor is - // protected, and the derived SimpleLRUCache is declared a friend, - // so we implement it in the derived SimpleLRUCache. - virtual void RemoveElement(const Key& k, Value* value) = 0; - - virtual void DebugIterator(const Key& k, const Value* value, int pin_count, - int64_t last_timestamp, bool is_deferred, - std::string* output) const { - std::stringstream ss; - ss << "ox" << std::hex << value << std::dec << ": pin: " << pin_count; - ss << ", is_deferred: " << is_deferred; - ss << ", last_use: " << last_timestamp << std::endl; - *output += ss.str(); - } - - // Override this operation if you want to evict cache entries - // based on parameters other than the total units stored. - // For example, if the cache stores open sstables, where the cost - // is the size in bytes of the open sstable, you may want to evict - // entries from the cache not only before the max size in bytes - // is reached but also before reaching the limit of open file - // descriptors. Thus, you may want to override this function in a - // subclass and return true if either Size() is too large or - // Entries() is too large. - virtual bool IsOverfull() const { return units_ > max_units_; } - - private: - typedef SimpleLRUCacheElem Elem; - typedef MapType Table; - typedef typename Table::iterator TableIterator; - typedef typename Table::const_iterator TableConstIterator; - typedef MapType DeferredTable; - typedef typename DeferredTable::iterator DeferredTableIterator; - typedef typename DeferredTable::const_iterator DeferredTableConstIterator; - - Table table_; // Main table - // Pinned entries awaiting to be released before they can be discarded. - // This is a key -> list mapping (multiple deferred entries for the same key) - // The machinery used to maintain main LRU list is reused here, though this - // list is not necessarily LRU and we don't care about the order of elements. - DeferredTable defer_; - int64_t units_; // Combined units of all elements - int64_t max_units_; // Max allowed units - int64_t pinned_units_; // Combined units of all pinned elements - Elem head_; // Dummy head of LRU list (next is mru elem) - int64_t max_idle_; // Maximum number of idle cycles - bool lru_; // LRU or age-based eviction? - - // Representation invariants: - // . LRU list is circular doubly-linked list - // . Each live "Elem" is either in "table_" or "defer_" - // . LRU list contains elements in "table_" that can be removed to free space - // . Each "Elem" in "defer_" has a non-zero pin count - - void Discard(Elem* e) { - assert(e->pin == 0); - units_ -= e->units; - RemoveElement(e->key, e->value); - delete e; - } - - // Count the number and total size of the elements in the deferred table. - void CountDeferredEntries(int64_t* num_entries, int64_t* total_size) const; - - // Currently in deferred table? - // Note, if (value == nullptr), only key is used for matching. - bool InDeferredTable(const Key& k, const Value* value) const; - - void GarbageCollect(); // Discard to meet space constraints - void DiscardIdle(int64_t max_idle); // Discard to meet idle-time constraints - - void SetTimeout(double seconds, bool lru); - - bool IsOverfullInternal() const { - return ((units_ > max_units_) || IsOverfull()); - } - void Remove(Elem* e); - - public: - static const size_t kElemSize = sizeof(Elem); - - private: - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SimpleLRUCacheBase); -}; - -template -SimpleLRUCacheBase::SimpleLRUCacheBase( - int64_t total_units) - : head_(Key(), nullptr, 0, 0, Elem::kNeverUsed) { - units_ = 0; - pinned_units_ = 0; - max_units_ = total_units; - head_.next = &head_; - head_.prev = &head_; - max_idle_ = -1; // Stands for "no expiration" - lru_ = true; // default to LRU, not age-based -} - -template -void SimpleLRUCacheBase::SetTimeout(double seconds, - bool lru) { - if (seconds < 0 || std::isinf(seconds)) { - // Treat as no expiration based on idle time - lru_ = lru; - max_idle_ = -1; - } else if (max_idle_ >= 0 && lru != lru_) { - // LOG(DFATAL) << "Can't SetMaxIdleSeconds() and SetAgeBasedEviction()"; - // In production we'll just ignore the second call - assert(0); - } else { - lru_ = lru; - - // Convert to cycles ourselves in order to perform all calculations in - // floating point so that we avoid integer overflow. - // NOTE: The largest representable int64_t cannot be represented exactly as - // a - // double, so the cast results in a slightly larger value which cannot be - // converted back to an int64_t. The next smallest double is representable - // as - // an int64_t, however, so if we make sure that `timeout_cycles` is strictly - // smaller than the result of the cast, we know that casting - // `timeout_cycles` to int64_t will not overflow. - // NOTE 2: If you modify the computation here, make sure to update the - // GetBoundaryTimeout() method in the test as well. - const double timeout_cycles = seconds * SimpleCycleTimer::Frequency(); - if (timeout_cycles >= std::numeric_limits::max()) { - // The value is outside the range of int64_t, so "round" down to something - // that can be represented. - max_idle_ = std::numeric_limits::max(); - } else { - max_idle_ = static_cast(timeout_cycles); - } - DiscardIdle(max_idle_); - } -} - -template -void SimpleLRUCacheBase::RemoveAll() { - // For each element: call "Remove" - for (TableIterator iter = table_.begin(); iter != table_.end(); ++iter) { - Remove(iter->second); - } - table_.clear(); -} - -template -void SimpleLRUCacheBase::RemoveUnpinned() { - for (Elem* e = head_.next; e != &head_;) { - Elem* next = e->next; - if (e->pin == 0) Remove(e->key); - e = next; - } -} - -template -void SimpleLRUCacheBase::Clear() { - // For each element: call "RemoveElement" and delete it - for (TableConstIterator iter = table_.begin(); iter != table_.end();) { - Elem* e = iter->second; - // Pre-increment the iterator to avoid possible - // accesses to deleted memory in cases where the - // key is a pointer to the memory that is freed by - // Discard. - ++iter; - Discard(e); - } - // Pinned entries cannot be Discarded and defer_ contains nothing but pinned - // entries. Therefore, it must be already be empty at this point. - assert(defer_.empty()); - // Get back into pristine state - table_.clear(); - head_.next = &head_; - head_.prev = &head_; - units_ = 0; - pinned_units_ = 0; -} - -template -Value* SimpleLRUCacheBase::LookupWithOptions( - const Key& k, const SimpleLRUCacheOptions& options) { - RemoveExpiredEntries(); - - TableIterator iter = table_.find(k); - if (iter != table_.end()) { - // We set last_use_ upon Release, not during Lookup. - Elem* e = iter->second; - if (e->pin == 0) { - pinned_units_ += e->units; - // We are pinning this entry, take it off the LRU list if we are in LRU - // mode. In strict age-based mode entries stay on the list while pinned. - if (lru_ && options.update_eviction_order()) e->Unlink(); - } - e->pin++; - return e->value; - } - return nullptr; -} - -template -void SimpleLRUCacheBase::ReleaseWithOptions( - const Key& k, Value* value, const SimpleLRUCacheOptions& options) { - { // First check to see if this is a deferred value - DeferredTableIterator iter = defer_.find(k); - if (iter != defer_.end()) { - const Elem* const head = iter->second; - // Go from oldest to newest, assuming that oldest entries get released - // first. This may or may not be true and makes no semantic difference. - Elem* e = head->prev; - while (e != head && e->value != value) { - e = e->prev; - } - if (e->value == value) { - // Found in deferred list: release it - assert(e->pin > 0); - e->pin--; - if (e->pin == 0) { - if (e == head) { - // When changing the head, remove the head item and re-insert the - // second item on the list (if there are any left). Do not re-use - // the key from the first item. - // Even though the two keys compare equal, the lifetimes may be - // different (such as a key of Std::StringPiece). - defer_.erase(iter); - if (e->prev != e) { - defer_[e->prev->key] = e->prev; - } - } - e->Unlink(); - Discard(e); - } - return; - } - } - } - { // Not deferred; so look in hash table - TableIterator iter = table_.find(k); - assert(iter != table_.end()); - Elem* e = iter->second; - assert(e->value == value); - assert(e->pin > 0); - if (lru_ && options.update_eviction_order()) { - e->last_use_ = SimpleCycleTimer::Now(); - } - e->pin--; - - if (e->pin == 0) { - if (lru_ && options.update_eviction_order()) e->Link(&head_); - pinned_units_ -= e->units; - if (IsOverfullInternal()) { - // This element is no longer needed, and we are full. So kick it out. - Remove(k); - } - } - } -} - -template -void SimpleLRUCacheBase::InsertPinned(const Key& k, - Value* value, - size_t units) { - // Get rid of older entry (if any) from table - Remove(k); - - // Make new element - Elem* e = new Elem(k, value, 1, units, SimpleCycleTimer::Now()); - - // Adjust table, total units fields. - units_ += units; - pinned_units_ += units; - table_[k] = e; - - // If we are in the strict age-based eviction mode, the entry goes on the LRU - // list now and is never removed. In the LRU mode, the list will only contain - // unpinned entries. - if (!lru_) e->Link(&head_); - GarbageCollect(); -} - -template -void SimpleLRUCacheBase::UpdateSize(const Key& k, - const Value* value, - size_t units) { - TableIterator table_iter = table_.find(k); - if ((table_iter != table_.end()) && - ((value == nullptr) || (value == table_iter->second->value))) { - Elem* e = table_iter->second; - units_ -= e->units; - if (e->pin > 0) { - pinned_units_ -= e->units; - } - e->units = units; - units_ += e->units; - if (e->pin > 0) { - pinned_units_ += e->units; - } - } else { - const DeferredTableIterator iter = defer_.find(k); - if (iter != defer_.end()) { - const Elem* const head = iter->second; - Elem* e = iter->second; - do { - if (e->value == value || value == nullptr) { - units_ -= e->units; - e->units = units; - units_ += e->units; - } - e = e->prev; - } while (e != head); - } - } - GarbageCollect(); -} - -template -bool SimpleLRUCacheBase::StillInUse( - const Key& k, const Value* value) const { - TableConstIterator iter = table_.find(k); - if ((iter != table_.end()) && - ((value == nullptr) || (value == iter->second->value))) { - return true; - } else { - return InDeferredTable(k, value); - } -} - -template -bool SimpleLRUCacheBase::InDeferredTable( - const Key& k, const Value* value) const { - const DeferredTableConstIterator iter = defer_.find(k); - if (iter != defer_.end()) { - const Elem* const head = iter->second; - const Elem* e = head; - do { - if (e->value == value || value == nullptr) return true; - e = e->prev; - } while (e != head); - } - return false; -} - -template -void SimpleLRUCacheBase::Remove(const Key& k) { - TableIterator iter = table_.find(k); - if (iter != table_.end()) { - Elem* e = iter->second; - table_.erase(iter); - Remove(e); - } -} - -template -void SimpleLRUCacheBase::Remove(Elem* e) { - // Unlink e whether it is in the LRU or the deferred list. It is safe to call - // Unlink() if it is not in either list. - e->Unlink(); - if (e->pin > 0) { - pinned_units_ -= e->units; - - // Now add it to the deferred table. - DeferredTableIterator iter = defer_.find(e->key); - if (iter == defer_.end()) { - // Inserting a new key, the element becomes the head of the list. - e->prev = e->next = e; - defer_[e->key] = e; - } else { - // There is already a deferred list for this key, attach the element to it - Elem* head = iter->second; - e->Link(head); - } - } else { - Discard(e); - } -} - -template -void SimpleLRUCacheBase::GarbageCollect() { - Elem* e = head_.prev; - while (IsOverfullInternal() && (e != &head_)) { - Elem* prev = e->prev; - if (e->pin == 0) { - // Erase from hash-table - TableIterator iter = table_.find(e->key); - assert(iter != table_.end()); - assert(iter->second == e); - table_.erase(iter); - e->Unlink(); - Discard(e); - } - e = prev; - } -} - -// Not using cycle. Instead using second from time() -static const int kAcceptableClockSynchronizationDriftCycles = 1; - -template -void SimpleLRUCacheBase::DiscardIdle( - int64_t max_idle) { - if (max_idle < 0) return; - - Elem* e = head_.prev; - const int64_t threshold = SimpleCycleTimer::Now() - max_idle; -#ifndef NDEBUG - int64_t last = 0; -#endif - while ((e != &head_) && (e->last_use_ < threshold)) { -// Sanity check: LRU list should be sorted by last_use_. We could -// check the entire list, but that gives quadratic behavior. -// -// TSCs on different cores of multi-core machines sometime get slightly out -// of sync; compensate for this by allowing clock to go backwards by up to -// kAcceptableClockSynchronizationDriftCycles CPU cycles. -// -// A kernel bug (http://b/issue?id=777807) sometimes causes TSCs to become -// widely unsynchronized, in which case this CHECK will fail. As a -// temporary work-around, running -// -// $ sudo bash -// # echo performance>/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor -// # /etc/init.d/cpufrequtils restart -// -// fixes the problem. -#ifndef NDEBUG - assert(last <= e->last_use_ + kAcceptableClockSynchronizationDriftCycles); - last = e->last_use_; -#endif - - Elem* prev = e->prev; - // There are no pinned elements on the list in the LRU mode, and in the - // age-based mode we push them out of the main table regardless of pinning. - assert(e->pin == 0 || !lru_); - Remove(e->key); - e = prev; - } -} - -template -void SimpleLRUCacheBase::CountDeferredEntries( - int64_t* num_entries, int64_t* total_size) const { - *num_entries = *total_size = 0; - for (DeferredTableConstIterator iter = defer_.begin(); iter != defer_.end(); - ++iter) { - const Elem* const head = iter->second; - const Elem* e = head; - do { - (*num_entries)++; - *total_size += e->units; - e = e->prev; - } while (e != head); - } -} - -template -int64_t SimpleLRUCacheBase::DeferredSize() const { - int64_t entries, size; - CountDeferredEntries(&entries, &size); - return size; -} - -template -int64_t SimpleLRUCacheBase::DeferredEntries() const { - int64_t entries, size; - CountDeferredEntries(&entries, &size); - return entries; -} - -template -int64_t SimpleLRUCacheBase::AgeOfLRUItemInMicroseconds() const { - if (head_.prev == &head_) return 0; - return kSecToUsec * (SimpleCycleTimer::Now() - head_.prev->last_use_) / - SimpleCycleTimer::Frequency(); -} - -template -int64_t SimpleLRUCacheBase::GetLastUseTime( - const Key& k) const { - // GetLastUseTime works only in LRU mode - assert(lru_); - TableConstIterator iter = table_.find(k); - if (iter == table_.end()) return -1; - const Elem* e = iter->second; - return e->last_use_; -} - -template -int64_t SimpleLRUCacheBase::GetInsertionTime( - const Key& k) const { - // GetInsertionTime works only in age-based mode - assert(!lru_); - TableConstIterator iter = table_.find(k); - if (iter == table_.end()) return -1; - const Elem* e = iter->second; - return e->last_use_; -} - -template -void SimpleLRUCacheBase::DebugOutput( - std::string* output) const { - std::stringstream ss; - ss << "SimpleLRUCache of " << table_.size(); - ss << " elements plus " << DeferredEntries(); - ss << " deferred elements (" << Size(); - ss << " units, " << MaxSize() << " max units)"; - *output += ss.str(); - for (TableConstIterator iter = table_.begin(); iter != table_.end(); ++iter) { - const Elem* e = iter->second; - DebugIterator(e->key, e->value, e->pin, e->last_use_, false, output); - } - *output += "Deferred elements\n"; - for (DeferredTableConstIterator iter = defer_.begin(); iter != defer_.end(); - ++iter) { - const Elem* const head = iter->second; - const Elem* e = head; - do { - DebugIterator(e->key, e->value, e->pin, e->last_use_, true, output); - e = e->prev; - } while (e != head); - } -} - -// construct an iterator be sure to save a copy of end() as well, so we don't -// update external_view_ in that case. this is b/c if it_ == end(), calling -// it_->first segfaults. we could do this by making sure a specific field in -// it_ is not nullptr but that relies on the internal implementation of it_, so -// we pass in end() instead -template -SimpleLRUCacheConstIterator::SimpleLRUCacheConstIterator( - HashMapConstIterator it, HashMapConstIterator end) - : it_(it), end_(end) { - if (it_ != end_) { - external_view_.first = it_->first; - external_view_.second = it_->second->value; - last_use_ = it_->second->last_use_; - } -} - -template -auto SimpleLRUCacheConstIterator::operator++() - -> SimpleLRUCacheConstIterator& { - it_++; - if (it_ != end_) { - external_view_.first = it_->first; - external_view_.second = it_->second->value; - last_use_ = it_->second->last_use_; - } - return *this; -} - -template -class SimpleLRUCache - : public SimpleLRUCacheBase< - Key, Value, - std::unordered_map*, H, EQ>, EQ> { - public: - explicit SimpleLRUCache(int64_t total_units) - : SimpleLRUCacheBase< - Key, Value, - std::unordered_map*, H, EQ>, - EQ>(total_units) {} - - protected: - virtual void RemoveElement(const Key& k, Value* value) { delete value; } - - private: - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SimpleLRUCache); -}; - -template -class SimpleLRUCacheWithDeleter - : public SimpleLRUCacheBase< - Key, Value, - std::unordered_map*, H, EQ>, EQ> { - typedef std::unordered_map*, H, EQ> - HashMap; - typedef SimpleLRUCacheBase Base; - - public: - explicit SimpleLRUCacheWithDeleter(int64_t total_units) - : Base(total_units), deleter_() {} - - SimpleLRUCacheWithDeleter(int64_t total_units, Deleter deleter) - : Base(total_units), deleter_(deleter) {} - - protected: - virtual void RemoveElement(const Key& k, Value* value) { deleter_(value); } - - private: - Deleter deleter_; - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SimpleLRUCacheWithDeleter); -}; - -} // namespace utils -} // namespace istio - -#endif // ISTIO_UTILS_SIMPLE_LRU_CACHE_INL_H_ diff --git a/istio.deps b/istio.deps deleted file mode 100644 index d646d377bc2..00000000000 --- a/istio.deps +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "_comment": "", - "name": "ISTIO_API", - "repoName": "api", - "file": "repositories.bzl", - "lastStableSHA": "78da6e6eb4ad4f158fb58e02f94efde4abf4cabf" - }, - { - "_comment": "", - "name": "ENVOY_SHA", - "repoName": "envoyproxy/envoy", - "file": "WORKSPACE", - "lastStableSHA": "2b2c299144600fb9e525d21aabf39bf48e64fb1f" - } -] \ No newline at end of file diff --git a/protobuf.bzl b/protobuf.bzl deleted file mode 100644 index 6273a3d6629..00000000000 --- a/protobuf.bzl +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") - -def protobuf_repositories(load_repo=True, bind=True): - if load_repo: - git_repository( - name = "com_google_protobuf", - commit = "2761122b810fe8861004ae785cc3ab39f384d342", # v3.5.0 - remote = "https://github.com/google/protobuf.git", - ) - - if bind: - native.bind( - name = "protoc", - actual = "@com_google_protobuf//:protoc", - ) - - native.bind( - name = "protocol_compiler", - actual = "@com_google_protobuf//:protoc", - ) - - native.bind( - name = "protobuf", - actual = "@com_google_protobuf//:protobuf", - ) - - native.bind( - name = "cc_wkt_protos", - actual = "@com_google_protobuf//:cc_wkt_protos", - ) - - native.bind( - name = "cc_wkt_protos_genproto", - actual = "@com_google_protobuf//:cc_wkt_protos_genproto", - ) - - native.bind( - name = "protobuf_compiler", - actual = "@com_google_protobuf//:protoc_lib", - ) - - native.bind( - name = "protobuf_clib", - actual = "@com_google_protobuf//:protoc_lib", - ) diff --git a/prow/proxy-common.inc b/prow/proxy-common.inc new file mode 100755 index 00000000000..5a3f6b9725b --- /dev/null +++ b/prow/proxy-common.inc @@ -0,0 +1,85 @@ +#!/bin/bash +# +# Copyright 2017 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1; pwd) +ROOT=$(dirname "$WD") +WORKSPACE="${ROOT}/WORKSPACE" + +# Exit immediately for non zero status +set -e +# Check unset variables +set -u +# Print commands +set -x + +# shellcheck disable=SC2034 +GOPATH=/home/prow/go +ROOT=/go/src + +# Configure available resources and disable IPv6 tests. +BAZEL_BUILD_ARGS="${BAZEL_BUILD_EXTRA_ARGS:-} --verbose_failures --test_env=ENVOY_IP_TEST_VERSIONS=v4only --test_output=errors" +export BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS# }" + +# Override envoy. +if [[ "${ENVOY_REPOSITORY:-}" && "${ENVOY_PREFIX:-}" ]]; then + # Legacy path, keep around for a few releases to allow folks to migrate + # The other path is preferred as it uses an API intended for programmatic access and allows fine-grained access token usage. + TMP_DIR=$(mktemp -d -t envoy-XXXXXXXXXX) + trap 'rm -rf ${TMP_DIR:?}' EXIT + ENVOY_SHA="${ENVOY_SHA:-$(grep -Pom1 "^ENVOY_SHA = \"\K[a-zA-Z0-9]{40}" "$WORKSPACE")}" + BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS} --override_repository=envoy=${TMP_DIR}/${ENVOY_PREFIX}-${ENVOY_SHA}" + curl -nsSfL "${ENVOY_REPOSITORY}/archive/${ENVOY_SHA}.tar.gz" | tar -C "${TMP_DIR}" -xz +elif [[ "${ENVOY_REPOSITORY:-}" ]]; then + TMP_DIR=$(mktemp -d -t envoy-XXXXXXXXXX) + trap 'rm -rf ${TMP_DIR:?}' EXIT + ENVOY_SHA="${ENVOY_SHA:-$(grep -Pom1 "^ENVOY_SHA = \"\K[a-zA-Z0-9]{40}" "$WORKSPACE")}" + BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS} --override_repository=envoy=${TMP_DIR}" + # User passes a URl like https://github.com/foo/bar, we translate it to https://api.github.com/foo/bar/repos. + api_url=${ENVOY_REPOSITORY/https:\/\/github.com/https:\/\/api.github.com/repos} + # Contents in the tarball will be in a folder, so --strip=1 it so we don't have to deal with the changing name + curl -nsSfL "${api_url}/tarball/${ENVOY_SHA}" | tar -C "${TMP_DIR}" -xz --strip=1 +fi + +# e2e tests under //test/envoye2e/... use Bazel artifacts. +# shellcheck disable=SC2086 +BAZEL_OUT="$(bazel info ${BAZEL_BUILD_ARGS} output_path)/k8-fastbuild/bin" +export BAZEL_OUT + +# Disable RBE execution because the tool chain used by RBE is too new. +export BAZEL_BUILD_RBE_JOBS="${BAZEL_BUILD_RBE_JOBS:-0}" + +BAZEL_BUILD_RBE_INSTANCE="${BAZEL_BUILD_RBE_INSTANCE-projects/istio-testing/instances/default_instance}" +BAZEL_BUILD_RBE_CACHE="${BAZEL_BUILD_RBE_CACHE-grpcs://remotebuildexecution.googleapis.com}" + +CREDS="google_default_credentials" +# Use GCP service account when available. +if [[ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]]; then + echo "Using legacy GOOGLE_APPLICATION_CREDENTIALS. Move to workload identity!" >&2 + gcloud auth activate-service-account --key-file="${GOOGLE_APPLICATION_CREDENTIALS}" + CREDS="google_credentials=${GOOGLE_APPLICATION_CREDENTIALS}" +fi + +BAZEL_BUILD_RBE_JOBS="${BAZEL_BUILD_RBE_JOBS:-200}" +if [[ -n "${BAZEL_BUILD_RBE_INSTANCE}" ]]; then + if [[ "${BAZEL_BUILD_RBE_JOBS}" -gt 0 ]]; then + echo "Using RBE: ${BAZEL_BUILD_RBE_INSTANCE} (execute)" + export BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS} --${CREDS} --config=remote-clang-libc++ --config=remote-ci --remote_instance_name=${BAZEL_BUILD_RBE_INSTANCE} --jobs=${BAZEL_BUILD_RBE_JOBS}" + elif [[ -n "${BAZEL_BUILD_RBE_CACHE}" ]]; then + echo "Using RBE: ${BAZEL_BUILD_RBE_INSTANCE} (cache)" + export BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS} --${CREDS} --remote_instance_name=${BAZEL_BUILD_RBE_INSTANCE} --remote_cache=${BAZEL_BUILD_RBE_CACHE}" + fi +fi diff --git a/prow/proxy-postsubmit.sh b/prow/proxy-postsubmit.sh index bf9b7826d63..642b8104ac7 100755 --- a/prow/proxy-postsubmit.sh +++ b/prow/proxy-postsubmit.sh @@ -1,49 +1,37 @@ #!/bin/bash - -# Copyright 2017 Istio Authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -WD=$(dirname $0) -WD=$(cd $WD; pwd) -ROOT=$(dirname $WD) +# +# Copyright 2017 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) ######################################## # Postsubmit script triggered by Prow. # ######################################## - -# Exit immediately for non zero status -set -e -# Check unset variables -set -u -# Print commands -set -x - -if [ "${CI:-}" == 'bootstrap' ]; then - # Test harness will checkout code to directory $GOPATH/src/github.com/istio/proxy - # but we depend on being at path $GOPATH/src/istio.io/proxy for imports - ln -sf ${GOPATH}/src/github.com/istio ${GOPATH}/src/istio.io - ROOT=${GOPATH}/src/istio.io/proxy - cd ${GOPATH}/src/istio.io/proxy - - # Setup bazel.rc - cp "${ROOT}/tools/bazel.rc.ci" "${HOME}/.bazelrc" +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" + +if [[ $(command -v gcloud) ]]; then + gcloud auth configure-docker -q +elif [[ $(command -v docker-credential-gcr) ]]; then + docker-credential-gcr configure-docker +else + echo "No credential helpers found, push to docker may not function properly" fi -GIT_SHA="$(git rev-parse --verify HEAD)" - -cd $ROOT +GCS_BUILD_BUCKET="${GCS_BUILD_BUCKET:-istio-build}" echo 'Create and push artifacts' -script/release-binary -ARTIFACTS_DIR="gs://istio-artifacts/proxy/${GIT_SHA}/artifacts/debs" make artifacts +make push_release RELEASE_GCS_PATH="gs://${GCS_BUILD_BUCKET}/proxy" diff --git a/prow/proxy-presubmit-asan.sh b/prow/proxy-presubmit-asan.sh index d1a93258a6c..adc19ecae75 100755 --- a/prow/proxy-presubmit-asan.sh +++ b/prow/proxy-presubmit-asan.sh @@ -1,54 +1,27 @@ #!/bin/bash - -# Copyright 2017 Istio Authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -WD=$(dirname $0) -WD=$(cd $WD; pwd) -ROOT=$(dirname $WD) +# +# Copyright 2017 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) ####################################### # Presubmit script triggered by Prow. # ####################################### - -# Exit immediately for non zero status -set -e -# Check unset variables -set -u -# Print commands -set -x - -if [ "${CI:-}" == 'bootstrap' ]; then - # Test harness will checkout code to directory $GOPATH/src/github.com/istio/proxy - # but we depend on being at path $GOPATH/src/istio.io/proxy for imports - ln -sf ${GOPATH}/src/github.com/istio ${GOPATH}/src/istio.io - ROOT=${GOPATH}/src/istio.io/proxy - cd ${GOPATH}/src/istio.io/proxy - - # Setup bazel.rc - cp "${ROOT}/tools/bazel.rc.ci" "${HOME}/.bazelrc" - - # Use the provided pull head sha, from prow. - GIT_SHA="${PULL_PULL_SHA}" -else - # Use the current commit. - GIT_SHA="$(git rev-parse --verify HEAD)" -fi - -cd $ROOT - -export BAZEL_TEST_ARGS="--test_output=errors" +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" echo 'Bazel Tests' make test_asan diff --git a/include/istio/control/http/BUILD b/prow/proxy-presubmit-release.sh old mode 100644 new mode 100755 similarity index 60% rename from include/istio/control/http/BUILD rename to prow/proxy-presubmit-release.sh index 2bf7160ec04..4e66795ea75 --- a/include/istio/control/http/BUILD +++ b/prow/proxy-presubmit-release.sh @@ -1,27 +1,27 @@ +#!/bin/bash +# # Copyright 2017 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 - +# http://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -licenses(["notice"]) +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) + +####################################### +# Presubmit script triggered by Prow. # +####################################### +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" -cc_library( - name = "headers_lib", - hdrs = [ - "check_data.h", - "controller.h", - "report_data.h", - "request_handler.h", - ], - visibility = ["//visibility:public"], - deps = ["//src/istio/authn:context_proto"], -) +echo 'Test building release artifacts' +make test_release diff --git a/prow/proxy-presubmit-tsan.sh b/prow/proxy-presubmit-tsan.sh index 6c7a4e89e8e..f6d598f9c8a 100755 --- a/prow/proxy-presubmit-tsan.sh +++ b/prow/proxy-presubmit-tsan.sh @@ -1,54 +1,27 @@ #!/bin/bash - -# Copyright 2017 Istio Authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -WD=$(dirname $0) -WD=$(cd $WD; pwd) -ROOT=$(dirname $WD) +# +# Copyright 2017 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) ####################################### # Presubmit script triggered by Prow. # ####################################### - -# Exit immediately for non zero status -set -e -# Check unset variables -set -u -# Print commands -set -x - -if [ "${CI:-}" == 'bootstrap' ]; then - # Test harness will checkout code to directory $GOPATH/src/github.com/istio/proxy - # but we depend on being at path $GOPATH/src/istio.io/proxy for imports - ln -sf ${GOPATH}/src/github.com/istio ${GOPATH}/src/istio.io - ROOT=${GOPATH}/src/istio.io/proxy - cd ${GOPATH}/src/istio.io/proxy - - # Setup bazel.rc - cp "${ROOT}/tools/bazel.rc.ci" "${HOME}/.bazelrc" - - # Use the provided pull head sha, from prow. - GIT_SHA="${PULL_PULL_SHA}" -else - # Use the current commit. - GIT_SHA="$(git rev-parse --verify HEAD)" -fi - -cd $ROOT - -export BAZEL_TEST_ARGS="--test_output=errors" +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" echo 'Bazel Tests' make test_tsan diff --git a/include/istio/mixerclient/BUILD b/prow/proxy-presubmit-wasm.sh old mode 100644 new mode 100755 similarity index 53% rename from include/istio/mixerclient/BUILD rename to prow/proxy-presubmit-wasm.sh index ed5183384a7..83800ff9c64 --- a/include/istio/mixerclient/BUILD +++ b/prow/proxy-presubmit-wasm.sh @@ -1,30 +1,27 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. +#!/bin/bash +# +# Copyright 2020 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 - +# http://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -licenses(["notice"]) +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) + +####################################### +# Presubmit script triggered by Prow. # +####################################### +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" -cc_library( - name = "headers_lib", - hdrs = [ - "client.h", - "check_response.h", - "environment.h", - "options.h", - "timer.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//include/istio/quota_config:requirement_header", - ], -) +echo 'Generate Wasm module files and run Wasm related test' +make check_wasm diff --git a/prow/proxy-presubmit.sh b/prow/proxy-presubmit.sh index 36f18441d46..0b6291c0285 100755 --- a/prow/proxy-presubmit.sh +++ b/prow/proxy-presubmit.sh @@ -1,57 +1,31 @@ #!/bin/bash - -# Copyright 2017 Istio Authors - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -WD=$(dirname $0) -WD=$(cd $WD; pwd) -ROOT=$(dirname $WD) +# +# Copyright 2017 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +WD=$(dirname "$0") +WD=$(cd "$WD" || exit 1 ; pwd) ####################################### # Presubmit script triggered by Prow. # ####################################### - -# Exit immediately for non zero status -set -e -# Check unset variables -set -u -# Print commands -set -x - -if [ "${CI:-}" == 'bootstrap' ]; then - # Test harness will checkout code to directory $GOPATH/src/github.com/istio/proxy - # but we depend on being at path $GOPATH/src/istio.io/proxy for imports - ln -sf ${GOPATH}/src/github.com/istio ${GOPATH}/src/istio.io - ROOT=${GOPATH}/src/istio.io/proxy - cd ${GOPATH}/src/istio.io/proxy - - # Setup bazel.rc - cp "${ROOT}/tools/bazel.rc.ci" "${HOME}/.bazelrc" - - # Use the provided pull head sha, from prow. - GIT_SHA="${PULL_PULL_SHA}" -else - # Use the current commit. - GIT_SHA="$(git rev-parse --verify HEAD)" -fi - -cd $ROOT - -export BAZEL_TEST_ARGS="--test_output=errors" +# shellcheck disable=SC1090,SC1091 +source "${WD}/proxy-common.inc" echo 'Code Check' -make check +make lint +make gen-check echo 'Bazel Build' make build diff --git a/repositories.bzl b/repositories.bzl deleted file mode 100644 index a3cd41685c5..00000000000 --- a/repositories.bzl +++ /dev/null @@ -1,218 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") - -def boringssl_repositories(bind=True): - git_repository( - name = "boringssl", - commit = "12c35d69008ae6b8486e435447445240509f7662", # 2016-10-24 - remote = "https://boringssl.googlesource.com/boringssl", - ) - - if bind: - native.bind( - name = "boringssl_crypto", - actual = "@boringssl//:crypto", - ) - - native.bind( - name = "libssl", - actual = "@boringssl//:ssl", - ) - -def googletest_repositories(bind=True): - BUILD = """ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -cc_library( - name = "googletest", - srcs = [ - "googletest/src/gtest-all.cc", - "googlemock/src/gmock-all.cc", - ], - hdrs = glob([ - "googletest/include/**/*.h", - "googlemock/include/**/*.h", - "googletest/src/*.cc", - "googletest/src/*.h", - "googlemock/src/*.cc", - ]), - includes = [ - "googlemock", - "googletest", - "googletest/include", - "googlemock/include", - ], - visibility = ["//visibility:public"], -) -cc_library( - name = "googletest_main", - srcs = ["googlemock/src/gmock_main.cc"], - visibility = ["//visibility:public"], - deps = [":googletest"], -) -cc_library( - name = "googletest_prod", - hdrs = [ - "googletest/include/gtest/gtest_prod.h", - ], - includes = [ - "googletest/include", - ], - visibility = ["//visibility:public"], -) -""" - native.new_git_repository( - name = "googletest_git", - build_file_content = BUILD, - commit = "d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0", - remote = "https://github.com/google/googletest.git", - ) - - if bind: - native.bind( - name = "googletest", - actual = "@googletest_git//:googletest", - ) - - native.bind( - name = "googletest_main", - actual = "@googletest_git//:googletest_main", - ) - - native.bind( - name = "googletest_prod", - actual = "@googletest_git//:googletest_prod", - ) - -ISTIO_API = "78da6e6eb4ad4f158fb58e02f94efde4abf4cabf" - -def mixerapi_repositories(bind=True): - BUILD = """ -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library") - -cc_proto_library( - name = "mixer_api_cc_proto", - srcs = glob( - ["mixer/v1/*.proto"], - ), - default_runtime = "//external:protobuf", - protoc = "//external:protoc", - visibility = ["//visibility:public"], - deps = [ - "//external:cc_gogoproto", - "//external:cc_wkt_protos", - "//external:rpc_status_proto", - ], -) - -cc_proto_library( - name = "mixer_client_config_cc_proto", - srcs = glob( - ["mixer/v1/config/client/*.proto"], - ), - default_runtime = "//external:protobuf", - protoc = "//external:protoc", - visibility = ["//visibility:public"], - deps = [ - ":mixer_api_cc_proto", - ], -) - -cc_proto_library( - name = "authentication_policy_config_cc_proto", - srcs = glob( - ["envoy/config/filter/http/authn/v2alpha1/*.proto", - "authentication/v1alpha1/*.proto", - ], - ), - default_runtime = "//external:protobuf", - protoc = "//external:protoc", - visibility = ["//visibility:public"], - deps = [ - "//external:cc_gogoproto", - ], -) - -filegroup( - name = "global_dictionary_file", - srcs = ["mixer/v1/global_dictionary.yaml"], - visibility = ["//visibility:public"], -) - -""" - native.new_git_repository( - name = "mixerapi_git", - build_file_content = BUILD, - commit = ISTIO_API, - remote = "https://github.com/istio/api.git", - ) - if bind: - native.bind( - name = "mixer_api_cc_proto", - actual = "@mixerapi_git//:mixer_api_cc_proto", - ) - native.bind( - name = "mixer_client_config_cc_proto", - actual = "@mixerapi_git//:mixer_client_config_cc_proto", - ) - native.bind( - name = "authentication_policy_config_cc_proto", - actual = "@mixerapi_git//:authentication_policy_config_cc_proto", - ) - -load(":protobuf.bzl", "protobuf_repositories") -load(":cc_gogo_protobuf.bzl", "cc_gogoproto_repositories") -load(":x_tools_imports.bzl", "go_x_tools_imports_repositories") -load(":googleapis.bzl", "googleapis_repositories") - -def mixerapi_dependencies(): - protobuf_repositories(load_repo=True, bind=True) - cc_gogoproto_repositories() - go_x_tools_imports_repositories() - googleapis_repositories() - mixerapi_repositories() diff --git a/script/build_proxy_artifacts.yaml b/script/build_proxy_artifacts.yaml deleted file mode 100644 index 3b94a57097c..00000000000 --- a/script/build_proxy_artifacts.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Used for automated builds triggered on git commit - -steps: -- name: gcr.io/istio-io/istio-builder:0.4.4 - args: - - "-c" - - "cp tools/bazel.rc.cloudbuilder \"$${HOME}/.bazelrc\" && ./script/release-binary" - entrypoint: bash -options: - machineType: N1_HIGHCPU_32 -timeout: 1800s diff --git a/script/check-license-headers b/script/check-license-headers deleted file mode 100755 index 5bd9cf665e7..00000000000 --- a/script/check-license-headers +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -# Checks the source code files for proper license headers. - -ISTIO_COPYRIGHT='Copyright 20[0-9][0-9] Istio Authors\. All Rights Reserved\.' -ISTIO_LICENSE='Apache License, Version 2\.0' - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -function is_source_code_file() { - first_line="$(head -1 ${1} | xargs --null)" - filename="$(basename ${1})" - [[ "${filename}" == 'BUILD' ]] && return 0 - [[ "${first_line}" == '#!/bin/bash' ]] && return 0 - extension="$(echo ${1} | awk -F . '{if (NF>1) {print $NF}}')" - [[ "${extension}" == "c" || - "${extension}" == "cc" || - "${extension}" == "h" || - "${extension}" == "py" || - "${extension}" == "go" || - "${extension}" == "bzl" ]] && return 0 - return 1 -} - -BAD_LICENSE=0 - -for file in $(git ls-files) -do - base="$(echo ${file} | awk -F / '{print $1}')" - [[ "${base}" == "contrib" || "${base}" == "google" || "${base}" == "third_party" ]] && continue - if is_source_code_file "${file}"; then - istio_copyright_count="$(head -n 20 ${file} | grep "${ISTIO_COPYRIGHT}" | wc -l)" - istio_license_count="$(head -n 20 ${file} | grep "${ISTIO_LICENSE}" | wc -l)" - if [[ "${istio_copyright_count}" != 1 || "${istio_license_count}" != 1 ]]; then - echo ${file} - BAD_LICENSE=1 - fi - fi -done - -[[ ${BAD_LICENSE} == 0 ]] || echo "have invalid license headers". diff --git a/script/check-style b/script/check-style deleted file mode 100755 index e3999a96ff5..00000000000 --- a/script/check-style +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# -# Copyright 2016 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -# Install required clang version to a folder and cache it. -CLANG_VERSION_REQUIRED="5.0.0" -CLANG_DIRECTORY="${HOME}/clang" -CLANG_FORMAT="${CLANG_DIRECTORY}/bin/clang-format" - -if [ "$(uname)" == "Darwin" ]; then - CLANG_BIN="x86_64-apple-darwin.tar.xz" -elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then - CLANG_BIN="linux-x86_64-ubuntu14.04.tar.xz" -else - echo "Unsupported environment." ; exit 1 ; -fi - -echo "clang-bin: http://releases.llvm.org/${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}" - - -CLANG_VERSION="$(${CLANG_FORMAT} -version | cut -d ' ' -f 3)" -if [[ "${CLANG_VERSION}" != "${CLANG_VERSION_REQUIRED}" ]]; then - echo "Installing required clang-format ${CLANG_VERSION_REQUIRED} to ${CLANG_DIRECTORY}" - mkdir -p ${CLANG_DIRECTORY} - curl --silent --show-error --retry 10 \ - "http://releases.llvm.org/${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}" \ - | tar Jx -C "${CLANG_DIRECTORY}" --strip=1 \ - || { echo "Could not install required clang-format. Skip formating." ; exit 0 ; } -fi - -echo "Checking file format ..." - -pushd ${ROOT} > /dev/null - -SOURCE_FILES=($(git ls-tree -r HEAD --name-only | grep -E '\.(h|c|cc|proto)$')) -"${CLANG_FORMAT}" -style=Google -i "${SOURCE_FILES[@]}" \ - || { echo "Could not run clang-format." ; exit 1 ; } - -CHANGED_FILES=($(git diff HEAD --name-only | grep -E '\.(h|c|cc|proto)$')) - -if [[ "${#CHANGED_FILES}" -ne 0 ]]; then - echo "Files not formatted: ${CHANGED_FILES[@]}" - exit 1 -fi -echo "All files are properly formatted." - -popd diff --git a/script/push-debian.sh b/script/push-debian.sh deleted file mode 100755 index 96c831788ab..00000000000 --- a/script/push-debian.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ - -# Example usage: -# -# bin/push-debian.sh \ -# -c opt -# -v 0.2.1 -# -p gs://istio-release/release/0.2.1/deb - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" -BAZEL_ARGS="" -BAZEL_TARGET='//tools/deb:istio-proxy' -BAZEL_BINARY="${ROOT}/bazel-bin/tools/deb/istio-proxy" -ISTIO_VERSION='' -GCS_PATH="" -OUTPUT_DIR="" - -set -o errexit -set -o nounset -set -o pipefail -set -x - -function usage() { - echo "$0 \ - -c \ - -o directory to copy files \ - -p \ - -v " - exit 1 -} - -while getopts ":c:o:p:v:" arg; do - case ${arg} in - c) BAZEL_ARGS+=" -c ${OPTARG}";; - o) OUTPUT_DIR="${OPTARG}";; - p) GCS_PATH="${OPTARG}";; - v) ISTIO_VERSION="${OPTARG}";; - *) usage;; - esac -done - -if [[ -n "${ISTIO_VERSION}" ]]; then - BAZEL_ARGS+=" --action_env=ISTIO_VERSION" - export ISTIO_VERSION -fi - -[[ -z "${GCS_PATH}" ]] && [[ -z "${OUTPUT_DIR}" ]] && usage - -bazel build ${BAZEL_ARGS} ${BAZEL_TARGET} - -if [[ -n "${GCS_PATH}" ]]; then - gsutil -m cp -r "${BAZEL_BINARY}.deb" ${GCS_PATH}/ -fi - -if [[ -n "${OUTPUT_DIR}" ]]; then - cp "${BAZEL_BINARY}.deb" "${OUTPUT_DIR}/" -fi diff --git a/script/release-binary b/script/release-binary deleted file mode 100755 index da638b29beb..00000000000 --- a/script/release-binary +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# -# Copyright 2016 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -set -ex -# The bucket name to store proxy binary -DST="gs://istio-build/proxy" - -function usage() { - echo "$0 - -d The bucket name to store proxy binary (optional)" - exit 1 -} - -while getopts d: arg ; do - case "${arg}" in - d) DST="${OPTARG}";; - *) usage;; - esac -done - -echo "Destination bucket: $DST" - -# The bucket name to store proxy binary -# DST="gs://istio-build/proxy" - -# Make sure to this script on x86_64 Ubuntu Xenial -UBUNTU_RELEASE=${UBUNTU_RELEASE:-$(lsb_release -c -s)} -[[ "${UBUNTU_RELEASE}" == 'xenial' ]] || { echo 'must run on Ubuntu Xenial'; exit 1; } - -# The proxy binary name. -SHA="$(git rev-parse --verify HEAD)" -BINARY_NAME="envoy-alpha-${SHA}.tar.gz" -SHA256_NAME="envoy-alpha-${SHA}.sha256" - -# If binary already exists skip. -gsutil stat "${DST}/${BINARY_NAME}" \ - && { echo 'Binary already exists'; exit 0; } \ - || echo 'Building a new binary.' - -# Build the release binary -bazel --batch build --config=release //src/envoy:envoy_tar -BAZEL_TARGET="bazel-bin/src/envoy/envoy_tar.tar.gz" -cp -f "${BAZEL_TARGET}" "${BINARY_NAME}" -sha256sum "${BINARY_NAME}" > "${SHA256_NAME}" - -# Copy it to the bucket. -echo "Copying ${BINARY_NAME} ${SHA256_NAME} to ${DST}/" -gsutil cp "${BINARY_NAME}" "${SHA256_NAME}" "${DST}/" - -BINARY_NAME="istio-proxy-${SHA}.deb" -SHA256_NAME="istio-proxy-${SHA}.sha256" -bazel --batch build --config=release //tools/deb:istio-proxy -BAZEL_TARGET="bazel-bin/tools/deb/istio-proxy.deb" -cp -f "${BAZEL_TARGET}" "${BINARY_NAME}" -sha256sum "${BINARY_NAME}" > "${SHA256_NAME}" - -# Copy it to the bucket. -echo "Copying ${BINARY_NAME} ${SHA256_NAME} to ${DST}/" -gsutil cp "${BINARY_NAME}" "${SHA256_NAME}" "${DST}/" - - -# Build the debug binary -BINARY_NAME="envoy-debug-${SHA}.tar.gz" -SHA256_NAME="envoy-debug-${SHA}.sha256" -bazel --batch build -c dbg //src/envoy:envoy_tar -BAZEL_TARGET="bazel-bin/src/envoy/envoy_tar.tar.gz" -cp -f "${BAZEL_TARGET}" "${BINARY_NAME}" -sha256sum "${BINARY_NAME}" > "${SHA256_NAME}" - -# Copy it to the bucket. -echo "Copying ${BINARY_NAME} ${SHA256_NAME} to ${DST}/" -gsutil cp "${BINARY_NAME}" "${SHA256_NAME}" "${DST}/" - -BINARY_NAME="istio-proxy-debug-${SHA}.deb" -SHA256_NAME="istio-proxy-debug${SHA}.sha256" -bazel --batch build -c dbg //tools/deb:istio-proxy -BAZEL_TARGET="bazel-bin/tools/deb/istio-proxy.deb" -cp -f "${BAZEL_TARGET}" "${BINARY_NAME}" -sha256sum "${BINARY_NAME}" > "${SHA256_NAME}" - -# Copy it to the bucket. -echo "Copying ${BINARY_NAME} ${SHA256_NAME} to ${DST}/" -gsutil cp "${BINARY_NAME}" "${SHA256_NAME}" "${DST}/" diff --git a/scripts/check-repository.sh b/scripts/check-repository.sh new file mode 100755 index 00000000000..eee4f093b99 --- /dev/null +++ b/scripts/check-repository.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright 2018 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +set -eu + +# Check whether any git repositories are defined. +# Git repository definition contains `commit` and `remote` fields. +if grep -nr "commit =\|remote =" --include=WORKSPACE --include=*.bzl .; then + echo "Using git repositories is not allowed." + echo "To ensure that all dependencies can be stored offline in distdir, only HTTP repositories are allowed." + exit 1 +fi + +# Check whether workspace file has `ENVOY_SHA = "` presented. +# This is needed by release builder to resolve envoy dep sha to tag. +if ! grep -Pq "ENVOY_SHA = \"[a-zA-Z0-9]{40}\"" WORKSPACE; then + echo "'ENVOY_SHA' is not set properly in WORKSPACE file, release builder depends on it to resolve envoy dep sha to tag." + exit 1 +fi diff --git a/scripts/check-style.sh b/scripts/check-style.sh new file mode 100755 index 00000000000..939442061cc --- /dev/null +++ b/scripts/check-style.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# +# Copyright 2016 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +CLANG_VERSION_REQUIRED="14.0.0" +CLANG_DIRECTORY="${HOME}/clang-${CLANG_VERSION_REQUIRED}" +CLANG_FORMAT=$(command -v clang-format) +CLANG_VERSION="$(${CLANG_FORMAT} -version 2>/dev/null | cut -d ' ' -f 3 | cut -d '-' -f 1)" +# Try system clang first. +if [[ ! -x "${CLANG_FORMAT}" || "${CLANG_VERSION}" != "${CLANG_VERSION_REQUIRED}" ]]; then + CLANG_FORMAT="${CLANG_DIRECTORY}/bin/clang-format" + # Try cached clang second. + CLANG_VERSION="$(${CLANG_FORMAT} -version 2>/dev/null | cut -d ' ' -f 3 | cut -d '-' -f 1)" + if [[ ! -x "${CLANG_FORMAT}" || "${CLANG_VERSION}" != "${CLANG_VERSION_REQUIRED}" ]]; then + # Install required clang version to a folder and cache it. + if [ "$(uname)" == "Darwin" ]; then + CLANG_BIN="x86_64-darwin-apple.tar.xz" + elif [[ "$(uname -s)" =~ Linux* ]]; then + if [ "$(uname -m)" == "aarch64" ]; then + CLANG_BIN="aarch64-linux-gnu.tar.xz" + else + CLANG_BIN="x86_64-linux-gnu-ubuntu-18.04.tar.xz" + fi + else + echo "Unsupported environment." ; exit 1 ; + fi + + LLVM_URL_PREFIX="https://github.com/llvm/llvm-project/releases/download/llvmorg" + echo "Downloading clang-format: ${LLVM_URL_PREFIX}-${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}" + echo "Installing required clang-format ${CLANG_VERSION_REQUIRED} to ${CLANG_DIRECTORY}" + + mkdir -p "${CLANG_DIRECTORY}" + curl -L --silent --show-error --retry 10 \ + "${LLVM_URL_PREFIX}-${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}" \ + | tar Jx -C "${CLANG_DIRECTORY}" --strip=1 \ + || { echo "Could not install required clang-format. Skip formatting." ; exit 1 ; } + fi +fi + +BUILDIFIER=$(command -v buildifier) +if [[ ! -x "${BUILDIFIER}" ]]; then + BUILDIFIER="${HOME}/bin/buildifier" + if [[ ! -x "${BUILDIFIER}" ]]; then + + if [ "$(uname)" == "Darwin" ]; then + BUILDIFIER_BIN="buildifier-darwin-amd64" + elif [[ "$(uname -s)" =~ Linux* ]]; then + if [ "$(uname -m)" == "aarch64" ]; then + BUILDIFIER_BIN="buildifier-linux-arm64" + else + BUILDIFIER_BIN="buildifier-linux-amd64" + fi + else + echo "Unsupported environment." ; exit 1 ; + fi + + echo "Downloading buildifier" + + mkdir -p "${HOME}/bin" + curl --silent --show-error --retry 10 --location \ + "https://github.com/bazelbuild/buildtools/releases/download/v6.1.2/${BUILDIFIER_BIN}" \ + -o "${BUILDIFIER}" \ + || { echo "Could not install required buildifier. Skip formatting." ; exit 1 ; } + chmod +x "${BUILDIFIER}" + fi +fi + +echo "Checking file format ..." + +pushd "${ROOT}" > /dev/null || exit 1 + +SOURCE_FILES=() +while IFS='' read -r line; do SOURCE_FILES+=("$line"); done < <(git ls-tree -r HEAD --name-only | grep -E '\.(h|c|cc|proto)$') + +"${CLANG_FORMAT}" -i "${SOURCE_FILES[@]}" \ + || { echo "Could not run clang-format." ; exit 1 ; } + +CHANGED_SOURCE_FILES=() +while IFS='' read -r line; do CHANGED_SOURCE_FILES+=("$line"); done < <(git diff HEAD --name-only | grep -E '\.(h|c|cc|proto)$') + +BAZEL_FILES=() +while IFS='' read -r line; do BAZEL_FILES+=("$line"); done < <(git ls-tree -r HEAD --name-only | grep -E '(\.bzl|BUILD|WORKSPACE)$' |grep -v 'extensions_build_config.bzl') + +"${BUILDIFIER}" "${BAZEL_FILES[@]}" \ + || { echo "Could not run buildifier." ; exit 1 ; } + +CHANGED_BAZEL_FILES=() +while IFS='' read -r line; do CHANGED_BAZEL_FILES+=("$line"); done < <(git diff HEAD --name-only | grep -E '(\.bzl|BUILD|WORKSPACE)$') + +if [[ "${#CHANGED_SOURCE_FILES}" -ne 0 ]]; then + echo -e "Source file(s) not formatted:\n${CHANGED_SOURCE_FILES[*]}" + git diff HEAD -- "${CHANGED_SOURCE_FILES[@]}" +fi +if [[ "${#CHANGED_BAZEL_FILES}" -ne 0 ]]; then + echo -e "Bazel file(s) not formatted:\n${CHANGED_BAZEL_FILES[*]}" +fi +if [[ "${#CHANGED_SOURCE_FILES}" -ne 0 || "${#CHANGED_BAZEL_FILES}" -ne 0 ]]; then + exit 1 +fi +echo "All files are properly formatted." + +popd || exit 1 diff --git a/scripts/gen-testdata.sh b/scripts/gen-testdata.sh new file mode 100755 index 00000000000..6b1de2f3605 --- /dev/null +++ b/scripts/gen-testdata.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -u +set -e + +function usage() { + echo "$0 + -c control whether to check generated file having diff or not." + exit 1 +} + +CHECK=0 + +while getopts c arg ; do + case "${arg}" in + c) CHECK=1;; + *) usage;; + esac +done + +OUT_DIR=$(mktemp -d -t testdata.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; } + +SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOTDIR="${SCRIPTPATH}/.." + +mkdir -p "${OUT_DIR}" +cp -R "${ROOTDIR}/testdata/bootstrap" "${OUT_DIR}" +cp -R "${ROOTDIR}/testdata/listener" "${OUT_DIR}" + +cd "${OUT_DIR}" || exit +go-bindata --nocompress --nometadata --pkg testdata -o "${ROOTDIR}/testdata/testdata.gen.go" ./... + +if [[ "${CHECK}" == "1" ]]; then + pushd "$ROOTDIR" || exit + CHANGED=$(git diff-index --name-only HEAD --) + popd || exit + if [[ -z "${CHANGED}" ]]; then + echo "generated test config is not up to date, run 'make gen' to update." + exit 1 + fi +fi + +rm -Rf "${OUT_DIR}" diff --git a/script/pre-commit b/scripts/pre-commit similarity index 98% rename from script/pre-commit rename to scripts/pre-commit index e0a5db52927..423e517754b 100755 --- a/script/pre-commit +++ b/scripts/pre-commit @@ -38,7 +38,7 @@ if [[ ! -x "${CLANG_FORMAT}" ]]; then fi CLANG_FORMAT_VERSION="$(${CLANG_FORMAT} -version | cut -d ' ' -f 3)" -CLANG_FORMAT_VERSION_REQUIRED="5.0.0" +CLANG_FORMAT_VERSION_REQUIRED="8.0.0" if ! [[ "${CLANG_FORMAT_VERSION}" =~ "${CLANG_FORMAT_VERSION_REQUIRED}" ]]; then echo "Skipping: clang-format ${CLANG_FORMAT_VERSION_REQUIRED} required." exit 0 diff --git a/scripts/release-binary.sh b/scripts/release-binary.sh new file mode 100755 index 00000000000..02cd9839aa9 --- /dev/null +++ b/scripts/release-binary.sh @@ -0,0 +1,196 @@ +#!/bin/bash +# +# Copyright 2016 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# +set -e +set +x + +# Use clang for the release builds. +export PATH=/usr/lib/llvm/bin:$PATH +export CC=${CC:-/usr/lib/llvm/bin/clang} +export CXX=${CXX:-/usr/lib/llvm/bin/clang++} + +# ARCH_SUFFIX allows optionally appending a -{ARCH} suffix to published binaries. +# For backwards compatibility, Istio skips this for amd64. +# Note: user provides "arm64"; we expand to "-arm64" for simple usage in script. +export ARCH_SUFFIX="${ARCH_SUFFIX+-${ARCH_SUFFIX}}" + +# Expliticly stamp. +BAZEL_BUILD_ARGS="${BAZEL_BUILD_ARGS} --stamp" + +if [[ "$(uname)" == "Darwin" ]]; then + BAZEL_CONFIG_ASAN="--config=macos-asan" +else + BAZEL_CONFIG_ASAN="--config=clang-asan-ci" +fi + +# The bucket name to store proxy binaries. +DST="" + +# Verify that we're building binaries on Ubuntu 18.04 (Bionic). +CHECK=1 + +# Defines the base binary name for artifacts. For example, this will be "envoy-debug". +BASE_BINARY_NAME="${BASE_BINARY_NAME:-"envoy"}" + +# If enabled, we will just build the Envoy binary rather than wasm, etc +BUILD_ENVOY_BINARY_ONLY="${BUILD_ENVOY_BINARY_ONLY:-0}" + +function usage() { + echo "$0 + -d The bucket name to store proxy binary (optional). + If not provided, both envoy binary push and docker image push are skipped. + -i Skip Ubuntu Bionic check. DO NOT USE THIS FOR RELEASED BINARIES." + exit 1 +} + +while getopts d:i arg ; do + case "${arg}" in + d) DST="${OPTARG}";; + i) CHECK=0;; + *) usage;; + esac +done + +if [[ "${BUILD_ENVOY_BINARY_ONLY}" != 1 && "${ARCH_SUFFIX}" != "" ]]; then + # This is not a fundamental limitation; however, the support for the other release types + # has not been updated to support this. + echo "ARCH_SUFFIX currently requires BUILD_ENVOY_BINARY_ONLY" + exit 1 +fi + +echo "Destination bucket: $DST" + +if [ "${DST}" == "none" ]; then + DST="" +fi + +# Make sure the release binaries are built on x86_64 Ubuntu 18.04 (Bionic) +if [ "${CHECK}" -eq 1 ] ; then + if [[ "${BAZEL_BUILD_ARGS}" != *"--config=remote-"* ]]; then + UBUNTU_RELEASE=${UBUNTU_RELEASE:-$(lsb_release -c -s)} + [[ "${UBUNTU_RELEASE}" == 'bionic' ]] || { echo 'Must run on Ubuntu Bionic.'; exit 1; } + fi + [[ "$(uname -m)" == 'x86_64' ]] || { echo 'Must run on x86_64.'; exit 1; } +fi + +# The proxy binary name. +SHA="$(git rev-parse --verify HEAD)" + +if [ -n "${DST}" ]; then + # If binary already exists skip. + # Use the name of the last artifact to make sure that everything was uploaded. + BINARY_NAME="${HOME}/istio-proxy-debug-${SHA}.deb" + R2_DST="${DST/gs:\/\//s3:\/\/}" + R2_ENDPOINT="https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com" + + GCS_EXISTS=0 + gsutil stat "${DST}/${BINARY_NAME}" && GCS_EXISTS=1 + + R2_EXISTS=0 + AWS_ACCESS_KEY_ID="$CF_ACCESS_KEY_ID" \ + AWS_SECRET_ACCESS_KEY="$CF_ACCESS_KEY_SECRET" \ + aws s3 ls "${R2_DST}/${BINARY_NAME}" \ + --endpoint-url "${R2_ENDPOINT}" && R2_EXISTS=1 + + if [ "${GCS_EXISTS}" -eq 1 ] && [ "${R2_EXISTS}" -eq 1 ]; then + echo 'Binary already exists'; exit 0 + else + echo 'Building a new binary.' + fi +fi + +ARCH_NAME="k8" +case "$(uname -m)" in + aarch64) ARCH_NAME="aarch64";; +esac + +# BAZEL_OUT: Symlinks don't work, use full path as a temporary workaround. +# See: https://github.com/istio/istio/issues/15714 for details. +# k8-opt is the output directory for x86_64 optimized builds (-c opt, so --config=release-symbol and --config=release). +# k8-dbg is the output directory for -c dbg builds. +for config in release release-symbol asan debug +do + case $config in + "release" ) + CONFIG_PARAMS="--config=release" + BINARY_BASE_NAME="${BASE_BINARY_NAME}-alpha" + # shellcheck disable=SC2086 + BAZEL_OUT="$(bazel info ${BAZEL_BUILD_ARGS} output_path)/${ARCH_NAME}-opt/bin" + ;; + "release-symbol") + CONFIG_PARAMS="--config=release-symbol" + BINARY_BASE_NAME="${BASE_BINARY_NAME}-symbol" + # shellcheck disable=SC2086 + BAZEL_OUT="$(bazel info ${BAZEL_BUILD_ARGS} output_path)/${ARCH_NAME}-opt/bin" + ;; + "asan") + # Asan is skipped on ARM64 + if [[ "$(uname -m)" != "aarch64" ]]; then + # NOTE: libc++ is dynamically linked in this build. + CONFIG_PARAMS="${BAZEL_CONFIG_ASAN} --config=release-symbol" + BINARY_BASE_NAME="${BASE_BINARY_NAME}-asan" + # shellcheck disable=SC2086 + BAZEL_OUT="$(bazel info ${BAZEL_BUILD_ARGS} output_path)/${ARCH_NAME}-opt/bin" + fi + ;; + "debug") + CONFIG_PARAMS="-c dbg" + BINARY_BASE_NAME="${BASE_BINARY_NAME}-debug" + # shellcheck disable=SC2086 + BAZEL_OUT="$(bazel info ${BAZEL_BUILD_ARGS} output_path)/${ARCH_NAME}-dbg/bin" + ;; + esac + + export BUILD_CONFIG=${config} + + echo "Building ${config} proxy" + BINARY_NAME="${HOME}/${BINARY_BASE_NAME}-${SHA}${ARCH_SUFFIX}.tar.gz" + DWP_NAME="${HOME}/${BINARY_BASE_NAME}-${SHA}${ARCH_SUFFIX}.dwp" + SHA256_NAME="${HOME}/${BINARY_BASE_NAME}-${SHA}${ARCH_SUFFIX}.sha256" + # shellcheck disable=SC2086 + bazel build ${BAZEL_BUILD_ARGS} ${CONFIG_PARAMS} //:envoy_tar //:envoy.dwp + BAZEL_TARGET="${BAZEL_OUT}/envoy_tar.tar.gz" + DWP_TARGET="${BAZEL_OUT}/envoy.dwp" + cp -f "${BAZEL_TARGET}" "${BINARY_NAME}" + cp -f "${DWP_TARGET}" "${DWP_NAME}" + sha256sum "${BINARY_NAME}" > "${SHA256_NAME}" + + if [ -n "${DST}" ]; then + # Copy it to the bucket. + echo "Copying ${BINARY_NAME} ${SHA256_NAME} to ${DST}/" + gsutil cp "${BINARY_NAME}" "${SHA256_NAME}" "${DWP_NAME}" "${DST}/" + + R2_DST="${DST/gs:\/\//s3:\/\/}" + ENDPOINT="$(echo "${CF_CREDENTIALS}" | jq -r '.endpoint' | tr -d '\n')" + + AWS_ACCESS_KEY_ID="$(echo "${CF_CREDENTIALS}" | jq -r '.access_key' | tr -d '\n')" + AWS_SECRET_ACCESS_KEY="$(echo "${CF_CREDENTIALS}" | jq -r '.secret_key' | tr -d '\n')" + AWS_REGION="$(echo "${CF_CREDENTIALS}" | jq -r '.region' | tr -d '\n')" + AWS_SESSION_TOKEN="$(echo "${CF_CREDENTIALS}" | jq -r '.session_token' | tr -d '\n')" + export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_SESSION_TOKEN + for f in "${BINARY_NAME}" "${SHA256_NAME}" "${DWP_NAME}"; do + echo "Copying $(basename "${f}") to R2" + aws s3 cp "${f}" "${R2_DST}/$(basename "${f}")" --endpoint-url "${ENDPOINT}" + done + fi +done + +# Exit early to skip wasm build +if [ "${BUILD_ENVOY_BINARY_ONLY}" -eq 1 ]; then + exit 0 +fi diff --git a/scripts/update_envoy.sh b/scripts/update_envoy.sh new file mode 100755 index 00000000000..aeb00b3d943 --- /dev/null +++ b/scripts/update_envoy.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright 2020 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Update the Envoy SHA in istio/proxy WORKSPACE with the first argument (aka ENVOY_SHA) and +# the second argument (aka ENVOY_SHA commit date) + +# Exit immediately for non zero status +set -e +# Check unset variables +set -u +# Print commands +set -x + +# Update to main as envoyproxy/proxy has updated. +UPDATE_BRANCH=${UPDATE_BRANCH:-"main"} +ENVOY_SHA=${ENVOY_SHA:-""} + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +WORKSPACE=${ROOT}/WORKSPACE + +ENVOY_ORG="$(grep -Pom1 "^ENVOY_ORG = \"\K[a-zA-Z-]+" "${WORKSPACE}")" +ENVOY_REPO="$(grep -Pom1 "^ENVOY_REPO = \"\K[a-zA-Z-]+" "${WORKSPACE}")" + +# get latest commit for specified org/repo +LATEST_SHA="$(git ls-remote https://github.com/"${ENVOY_ORG}"/"${ENVOY_REPO}" "refs/heads/$UPDATE_BRANCH" | awk '{ print $1}')" +# use ENVOY_SHA if specified +if [[ -n "${ENVOY_SHA}" ]]; then + LATEST_SHA="${ENVOY_SHA}" +fi +DATE=$(curl -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/"${ENVOY_ORG}""/""${ENVOY_REPO}"/commits/"${LATEST_SHA}" | jq '.commit.committer.date') +DATE=$(echo "${DATE/\"/}" | cut -d'T' -f1) + +# Get ENVOY_SHA256 +URL="https://github.com/${ENVOY_ORG}/${ENVOY_REPO}/archive/${LATEST_SHA}.tar.gz" +GETSHA=$(wget -q "${URL}" && sha256sum "${LATEST_SHA}".tar.gz | awk '{ print $1 }') +SHAArr=("${GETSHA}") +SHA256=${SHAArr[0]} +rm "${LATEST_SHA}".tar.gz + +# Update ENVOY_SHA commit date +sed -i "s/Commit date: .*/Commit date: ${DATE}/" "${WORKSPACE}" + +# Update the dependency in istio/proxy WORKSPACE +sed -i 's/ENVOY_SHA = .*/ENVOY_SHA = "'"$LATEST_SHA"'"/' "${WORKSPACE}" +sed -i 's/ENVOY_SHA256 = .*/ENVOY_SHA256 = "'"$SHA256"'"/' "${WORKSPACE}" + +# Update .bazelversion and envoy.bazelrc +curl -sSL "https://raw.githubusercontent.com/${ENVOY_ORG}/${ENVOY_REPO}/${LATEST_SHA}/.bazelversion" > .bazelversion +curl -sSL "https://raw.githubusercontent.com/${ENVOY_ORG}/${ENVOY_REPO}/${LATEST_SHA}/.bazelrc" > envoy.bazelrc diff --git a/source/extensions/common/hashable_string/BUILD b/source/extensions/common/hashable_string/BUILD new file mode 100644 index 00000000000..1dda50ce9d1 --- /dev/null +++ b/source/extensions/common/hashable_string/BUILD @@ -0,0 +1,56 @@ +# Copyright Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "hashable_string", + srcs = ["hashable_string.cc"], + hdrs = ["hashable_string.h"], + repository = "@envoy", + deps = [ + "@envoy//envoy/common:hashable_interface", + "@envoy//envoy/registry", + "@envoy//envoy/stream_info:filter_state_interface", + "@envoy//source/common/common:hash_lib", + "@envoy//source/common/router:string_accessor_lib", + ], +) + +envoy_cc_test( + name = "hashable_string_test", + srcs = ["hashable_string_test.cc"], + repository = "@envoy", + deps = [ + ":hashable_string", + "@com_google_protobuf//:protobuf", + "@envoy//envoy/common:hashable_interface", + "@envoy//source/extensions/filters/common/set_filter_state:filter_config_lib", + "@envoy//test/mocks/server:server_factory_context_mocks", + "@envoy//test/mocks/stream_info:stream_info_mocks", + "@envoy//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/common/set_filter_state/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/common/hashable_string/hashable_string.cc b/source/extensions/common/hashable_string/hashable_string.cc new file mode 100644 index 00000000000..4dc0a37b20b --- /dev/null +++ b/source/extensions/common/hashable_string/hashable_string.cc @@ -0,0 +1,51 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "source/extensions/common/hashable_string/hashable_string.h" + +#include "envoy/registry/registry.h" +#include "envoy/stream_info/filter_state.h" + +#include "source/common/common/hash.h" + +#include +#include + +namespace Istio { +namespace Common { + +HashableString::HashableString(std::string_view value) : Envoy::Router::StringAccessorImpl(value) {} + +std::optional HashableString::hash() const { + return Envoy::HashUtil::xxHash64(asString()); +} + +namespace { + +class HashableStringObjectFactory : public Envoy::StreamInfo::FilterState::ObjectFactory { +public: + // ObjectFactory + std::string name() const override { return "istio.hashable_string"; } + + std::unique_ptr + createFromBytes(std::string_view data) const override { + return std::make_unique(data); + } +}; + +REGISTER_FACTORY(HashableStringObjectFactory, Envoy::StreamInfo::FilterState::ObjectFactory); + +} // namespace + +} // namespace Common +} // namespace Istio diff --git a/source/extensions/common/hashable_string/hashable_string.h b/source/extensions/common/hashable_string/hashable_string.h new file mode 100644 index 00000000000..580f873d106 --- /dev/null +++ b/source/extensions/common/hashable_string/hashable_string.h @@ -0,0 +1,34 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "envoy/common/hashable.h" +#include "source/common/router/string_accessor_impl.h" + +#include +#include + +namespace Istio { +namespace Common { + +class HashableString : public Envoy::Router::StringAccessorImpl, public Envoy::Hashable { +public: + HashableString(std::string_view value); + + // Hashable + std::optional hash() const override; +}; + +} // namespace Common +} // namespace Istio diff --git a/source/extensions/common/hashable_string/hashable_string_test.cc b/source/extensions/common/hashable_string/hashable_string_test.cc new file mode 100644 index 00000000000..4682d1bf4b2 --- /dev/null +++ b/source/extensions/common/hashable_string/hashable_string_test.cc @@ -0,0 +1,68 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "source/extensions/common/hashable_string/hashable_string.h" + +#include "envoy/common/hashable.h" +#include "gmock/gmock.h" +#include "google/protobuf/repeated_field.h" +#include "gtest/gtest.h" +#include "source/extensions/filters/common/set_filter_state/filter_config.h" +#include "test/mocks/server/server_factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include +#include + +namespace Istio { +namespace Common { +namespace { + +TEST(HashableStringTest, TestHashableStringIsHashable) { + using ::envoy::extensions::filters::common::set_filter_state::v3::FilterStateValue; + using ::Envoy::Extensions::Filters::Common::SetFilterState::Config; + using ::google::protobuf::RepeatedPtrField; + + NiceMock context; + Envoy::Http::TestRequestHeaderMapImpl header_map; + NiceMock stream_info; + + FilterStateValue proto; + Envoy::TestUtility::loadFromYaml(R"YAML( + object_key: key + factory_key: istio.hashable_string + format_string: + text_format_source: + inline_string: "value" + )YAML", + proto); + std::vector protos{{proto}}; + + auto config = + std::make_shared(RepeatedPtrField(protos.begin(), protos.end()), + Envoy::StreamInfo::FilterState::LifeSpan::FilterChain, context); + config->updateFilterState({&header_map}, stream_info); + + const auto* s = stream_info.filterState()->getDataReadOnly("key"); + ASSERT_NE(s, nullptr); + + const HashableString* h = dynamic_cast(s); + ASSERT_NE(h, nullptr); + ASSERT_EQ(h->asString(), "value"); + ASSERT_EQ(h->serializeAsString(), "value"); +} + +} // namespace +} // namespace Common +} // namespace Istio diff --git a/source/extensions/common/workload_discovery/BUILD b/source/extensions/common/workload_discovery/BUILD new file mode 100644 index 00000000000..0c6f5d6900c --- /dev/null +++ b/source/extensions/common/workload_discovery/BUILD @@ -0,0 +1,58 @@ +# Copyright Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_proto_library", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "api_lib", + srcs = ["api.cc"], + hdrs = ["api.h"], + repository = "@envoy", + deps = [ + ":discovery_cc_proto", + "//extensions/common:metadata_object_lib", + "@envoy//envoy/registry", + "@envoy//envoy/server:bootstrap_extension_config_interface", + "@envoy//envoy/server:factory_context_interface", + "@envoy//envoy/singleton:manager_interface", + "@envoy//envoy/stats:stats_macros", + "@envoy//envoy/thread_local:thread_local_interface", + "@envoy//source/common/common:non_copyable", + "@envoy//source/common/config:resource_type_helper_lib", + "@envoy//source/common/grpc:common_lib", + "@envoy//source/common/init:target_lib", + ], +) + +envoy_proto_library( + name = "discovery", + srcs = [ + "discovery.proto", + "extension.proto", + ], + deps = [ + "@envoy_api//envoy/config/core/v3:pkg", + ], +) diff --git a/source/extensions/common/workload_discovery/api.cc b/source/extensions/common/workload_discovery/api.cc new file mode 100644 index 00000000000..2f13a629b69 --- /dev/null +++ b/source/extensions/common/workload_discovery/api.cc @@ -0,0 +1,287 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/extensions/common/workload_discovery/api.h" + +#include "envoy/registry/registry.h" +#include "envoy/server/bootstrap_extension_config.h" +#include "envoy/server/factory_context.h" +#include "envoy/singleton/manager.h" +#include "envoy/thread_local/thread_local.h" +#include "source/common/common/non_copyable.h" +#include "source/common/config/resource_type_helper.h" +#include "source/common/grpc/common.h" +#include "source/common/init/target_impl.h" +#include "source/extensions/common/workload_discovery/discovery.pb.h" +#include "source/extensions/common/workload_discovery/discovery.pb.validate.h" +#include "source/extensions/common/workload_discovery/extension.pb.h" +#include "source/extensions/common/workload_discovery/extension.pb.validate.h" + +namespace Envoy::Extensions::Common::WorkloadDiscovery { +namespace { +constexpr absl::string_view DefaultNamespace = "default"; +constexpr absl::string_view DefaultServiceAccount = "default"; +constexpr absl::string_view DefaultTrustDomain = "cluster.local"; +Istio::Common::WorkloadMetadataObject convert(const istio::workload::Workload& workload) { + auto workload_type = Istio::Common::WorkloadType::Deployment; + switch (workload.workload_type()) { + case istio::workload::WorkloadType::CRONJOB: + workload_type = Istio::Common::WorkloadType::CronJob; + break; + case istio::workload::WorkloadType::JOB: + workload_type = Istio::Common::WorkloadType::Job; + break; + case istio::workload::WorkloadType::POD: + workload_type = Istio::Common::WorkloadType::Pod; + break; + default: + break; + } + + absl::string_view ns = workload.namespace_(); + absl::string_view trust_domain = workload.trust_domain(); + absl::string_view service_account = workload.service_account(); + // Trust domain may be elided if it's equal to "cluster.local" + if (trust_domain.empty()) { + trust_domain = DefaultTrustDomain; + } + // The namespace may be elided if it's equal to "default" + if (ns.empty()) { + ns = DefaultNamespace; + } + // The service account may be elided if it's equal to "default" + if (service_account.empty()) { + service_account = DefaultServiceAccount; + } + const auto identity = + absl::StrCat("spiffe://", trust_domain, "/ns/", ns, "/sa/", service_account); + return Istio::Common::WorkloadMetadataObject( + workload.name(), workload.cluster_id(), ns, workload.workload_name(), + workload.canonical_name(), workload.canonical_revision(), workload.canonical_name(), + workload.canonical_revision(), workload_type, identity, workload.locality().region(), + workload.locality().zone()); +} +} // namespace + +class WorkloadMetadataProviderImpl : public WorkloadMetadataProvider, public Singleton::Instance { +public: + WorkloadMetadataProviderImpl(const envoy::config::core::v3::ConfigSource& config_source, + Server::Configuration::ServerFactoryContext& factory_context) + : config_source_(config_source), factory_context_(factory_context), + tls_(factory_context.threadLocal()), + scope_(factory_context.scope().createScope("workload_discovery")), + stats_(generateStats(*scope_)), subscription_(*this) { + tls_.set([](Event::Dispatcher&) { return std::make_shared(); }); + // This is safe because the ADS mux is started in the cluster manager constructor prior to this + // call. + subscription_.start(); + } + + std::optional + GetMetadata(const Network::Address::InstanceConstSharedPtr& address) override { + if (address && address->ip()) { + if (const auto ipv4 = address->ip()->ipv4(); ipv4) { + uint32_t value = ipv4->address(); + std::array output; + absl::little_endian::Store32(&output, value); + return tls_->get(std::string(output.begin(), output.end())); + } else if (const auto ipv6 = address->ip()->ipv6(); ipv6) { + const uint64_t high = absl::Uint128High64(ipv6->address()); + const uint64_t low = absl::Uint128Low64(ipv6->address()); + std::array output; + absl::little_endian::Store64(&output, low); + absl::little_endian::Store64(&output[8], high); + return tls_->get(std::string(output.begin(), output.end())); + } + } + return {}; + } + +private: + using IdToAddress = absl::flat_hash_map>; + using IdToAddressSharedPtr = std::shared_ptr; + using AddressToWorkload = absl::flat_hash_map; + using AddressToWorkloadSharedPtr = std::shared_ptr; + + struct ThreadLocalProvider : public ThreadLocal::ThreadLocalObject { + void reset(const AddressToWorkloadSharedPtr& index) { address_to_workload_ = *index; } + void update(const AddressToWorkloadSharedPtr& added_addresses, + const IdToAddressSharedPtr& added_ids, + const std::shared_ptr> removed) { + for (const auto& id : *removed) { + for (const auto& address : id_to_address_[id]) { + address_to_workload_.erase(address); + } + id_to_address_.erase(id); + } + for (const auto& [address, workload] : *added_addresses) { + address_to_workload_.emplace(address, workload); + } + for (const auto& [id, address] : *added_ids) { + id_to_address_.emplace(id, address); + } + } + size_t total() const { return address_to_workload_.size(); } + // Returns by-value since the flat map does not provide pointer stability. + std::optional get(const std::string& address) { + const auto it = address_to_workload_.find(address); + if (it != address_to_workload_.end()) { + return it->second; + } + return {}; + } + IdToAddress id_to_address_; + AddressToWorkload address_to_workload_; + }; + class WorkloadSubscription : Config::SubscriptionCallbacks { + public: + WorkloadSubscription(WorkloadMetadataProviderImpl& parent) + : parent_(parent), + resource_type_helper_(parent.factory_context_.messageValidationVisitor(), "uid") { + subscription_ = THROW_OR_RETURN_VALUE( + parent.factory_context_.clusterManager() + .subscriptionFactory() + .subscriptionFromConfigSource( + parent.config_source_, + Grpc::Common::typeUrl(resource_type_helper_.getResourceName()), *parent.scope_, + *this, resource_type_helper_.resourceDecoder(), {}), + Config::SubscriptionPtr); + } + void start() { subscription_->start({}); } + + private: + // Config::SubscriptionCallbacks + absl::Status onConfigUpdate(const std::vector& resources, + const std::string&) override { + AddressToWorkloadSharedPtr index = std::make_shared(); + for (const auto& resource : resources) { + const auto& workload = + dynamic_cast(resource.get().resource()); + const auto& metadata = convert(workload); + for (const auto& addr : workload.addresses()) { + index->emplace(addr, metadata); + } + } + parent_.reset(index); + return absl::OkStatus(); + } + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) override { + IdToAddressSharedPtr added_ids = std::make_shared(); + AddressToWorkloadSharedPtr added_addresses = std::make_shared(); + for (const auto& resource : added_resources) { + const auto& workload = + dynamic_cast(resource.get().resource()); + const auto& metadata = convert(workload); + for (const auto& addr : workload.addresses()) { + added_addresses->emplace(addr, metadata); + } + added_ids->emplace(workload.uid(), std::vector(workload.addresses().begin(), + workload.addresses().end())); + } + auto removed = std::make_shared>(); + removed->reserve(removed_resources.size()); + for (const auto& resource : removed_resources) { + removed->push_back(resource); + } + parent_.update(added_addresses, added_ids, removed); + return absl::OkStatus(); + } + void onConfigUpdateFailed(Config::ConfigUpdateFailureReason, const EnvoyException*) override { + // Do nothing - feature is automatically disabled. + // TODO: Potential issue with the expiration of the metadata. + } + WorkloadMetadataProviderImpl& parent_; + const Config::ResourceTypeHelper resource_type_helper_; + Config::SubscriptionPtr subscription_; + }; + + void reset(AddressToWorkloadSharedPtr index) { + tls_.runOnAllThreads([index](OptRef tls) { tls->reset(index); }); + stats_.total_.set(tls_->total()); + } + + void update(const AddressToWorkloadSharedPtr& added_addresses, + const IdToAddressSharedPtr& added_ids, + const std::shared_ptr> removed) { + tls_.runOnAllThreads([added_addresses, added_ids, removed](OptRef tls) { + tls->update(added_addresses, added_ids, removed); + }); + stats_.total_.set(tls_->total()); + } + + WorkloadDiscoveryStats generateStats(Stats::Scope& scope) { + return WorkloadDiscoveryStats{WORKLOAD_DISCOVERY_STATS(POOL_GAUGE(scope))}; + } + + const envoy::config::core::v3::ConfigSource config_source_; + Server::Configuration::ServerFactoryContext& factory_context_; + ThreadLocal::TypedSlot tls_; + Stats::ScopeSharedPtr scope_; + WorkloadDiscoveryStats stats_; + WorkloadSubscription subscription_; +}; + +SINGLETON_MANAGER_REGISTRATION(workload_metadata_provider) + +class WorkloadDiscoveryExtension : public Server::BootstrapExtension { +public: + WorkloadDiscoveryExtension(Server::Configuration::ServerFactoryContext& factory_context, + const istio::workload::BootstrapExtension& config) + : factory_context_(factory_context), config_(config) {} + + // Server::Configuration::BootstrapExtension + void onServerInitialized(Server::Instance&) override { + provider_ = factory_context_.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(workload_metadata_provider), [&] { + return std::make_shared(config_.config_source(), + factory_context_); + }); + } + + void onWorkerThreadInitialized() override{}; + +private: + Server::Configuration::ServerFactoryContext& factory_context_; + const istio::workload::BootstrapExtension config_; + WorkloadMetadataProviderSharedPtr provider_; +}; + +class WorkloadDiscoveryFactory : public Server::Configuration::BootstrapExtensionFactory { +public: + // Server::Configuration::BootstrapExtensionFactory + Server::BootstrapExtensionPtr + createBootstrapExtension(const Protobuf::Message& config, + Server::Configuration::ServerFactoryContext& context) override { + const auto& message = + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); + return std::make_unique(context, message); + } + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + std::string name() const override { return "envoy.bootstrap.workload_discovery"; }; +}; + +REGISTER_FACTORY(WorkloadDiscoveryFactory, Server::Configuration::BootstrapExtensionFactory); + +WorkloadMetadataProviderSharedPtr +GetProvider(Server::Configuration::ServerFactoryContext& context) { + return context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(workload_metadata_provider)); +} + +} // namespace Envoy::Extensions::Common::WorkloadDiscovery diff --git a/source/extensions/common/workload_discovery/api.h b/source/extensions/common/workload_discovery/api.h new file mode 100644 index 00000000000..38ad400c658 --- /dev/null +++ b/source/extensions/common/workload_discovery/api.h @@ -0,0 +1,41 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "envoy/network/address.h" +#include "envoy/stats/stats_macros.h" +#include "envoy/server/factory_context.h" +#include "extensions/common/metadata_object.h" + +namespace Envoy::Extensions::Common::WorkloadDiscovery { + +#define WORKLOAD_DISCOVERY_STATS(GAUGE) GAUGE(total, NeverImport) + +struct WorkloadDiscoveryStats { + WORKLOAD_DISCOVERY_STATS(GENERATE_GAUGE_STRUCT) +}; + +class WorkloadMetadataProvider { +public: + virtual ~WorkloadMetadataProvider() = default; + virtual std::optional + GetMetadata(const Network::Address::InstanceConstSharedPtr& address) PURE; +}; + +using WorkloadMetadataProviderSharedPtr = std::shared_ptr; + +WorkloadMetadataProviderSharedPtr GetProvider(Server::Configuration::ServerFactoryContext& context); + +} // namespace Envoy::Extensions::Common::WorkloadDiscovery diff --git a/source/extensions/common/workload_discovery/discovery.proto b/source/extensions/common/workload_discovery/discovery.proto new file mode 100644 index 00000000000..128f14ab1f2 --- /dev/null +++ b/source/extensions/common/workload_discovery/discovery.proto @@ -0,0 +1,239 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package istio.workload; +option go_package = "test/envoye2e/workloadapi"; + +/** + * Warning: Derived from + * https://github.com/istio/ztunnel/blob/e36680f1534fae3d158964500ae9185495ec5d7b/proto/workload.proto + * with the following changes: + * + * 1) change go_package; + * 2) append bootstrap extension stub; + */ + +// NetworkMode indicates how the addresses of the workload should be treated. +enum NetworkMode { + // STANDARD means that the workload is uniquely identified by its address (within its network). + STANDARD = 0; + // HOST_NETWORK means the workload has an IP address that is shared by many workloads. The data plane should avoid + // attempting to lookup these workloads by IP address (which could return the wrong result). + HOST_NETWORK = 1; +} + +// Workload represents a workload - an endpoint (or collection behind a hostname). +// The xds primary key is "uid" as defined on the workload below. +// Secondary (alias) keys are the unique `network/IP` pairs that the workload can be reached at. +message Workload { + // UID represents a globally unique opaque identifier for this workload. + // For k8s resources, it is recommended to use the more readable format: + // + // cluster/group/kind/namespace/name/section-name + // + // As an example, a ServiceEntry with two WorkloadEntries inlined could become + // two Workloads with the following UIDs: + // - cluster1/networking.istio.io/v1alpha3/ServiceEntry/default/external-svc/endpoint1 + // - cluster1/networking.istio.io/v1alpha3/ServiceEntry/default/external-svc/endpoint2 + // + // For VMs and other workloads other formats are also supported; for example, + // a single UID string: "0ae5c03d-5fb3-4eb9-9de8-2bd4b51606ba" + string uid = 20; + // Name represents the name for the workload. + // For Kubernetes, this is the pod name. + // This is just for debugging and may be elided as an optimization. + string name = 1; + // Namespace represents the namespace for the workload. + // This is just for debugging and may be elided as an optimization. + string namespace = 2; + + // Address represents the IPv4/IPv6 address for the workload. + // This should be globally unique. + // This should not have a port number. + // Each workload must have at least either an address or hostname; not both. + repeated bytes addresses = 3; + + // The hostname for the workload to be resolved by the ztunnel. + // DNS queries are sent on-demand by default. + // If the resolved DNS query has several endpoints, the request will be forwarded + // to the first response. + // + // At a minimum, each workload must have either an address or hostname. For example, + // a workload that backs a Kubernetes service will typically have only endpoints. A + // workload that backs a headless Kubernetes service, however, will have both + // addresses as well as a hostname used for direct access to the headless endpoint. + string hostname = 21; + + // Network represents the network this workload is on. This may be elided for the default network. + // A (network,address) pair makeup a unique key for a workload *at a point in time*. + string network = 4; + + // Protocol that should be used to connect to this workload. + TunnelProtocol tunnel_protocol = 5; + + // The SPIFFE identity of the workload. The identity is joined to form spiffe:///ns//sa/. + // TrustDomain of the workload. May be elided if this is the mesh wide default (typically cluster.local) + string trust_domain = 6; + // ServiceAccount of the workload. May be elided if this is "default" + string service_account = 7; + + // If present, the waypoint proxy for this workload. + // All incoming requests must go through the waypoint. + GatewayAddress waypoint = 8; + + // If present, East West network gateway this workload can be reached through. + // Requests from remote networks should traverse this gateway. + GatewayAddress network_gateway = 19; + + // Name of the node the workload runs on + string node = 9; + + // CanonicalName for the workload. Used for telemetry. + string canonical_name = 10; + // CanonicalRevision for the workload. Used for telemetry. + string canonical_revision = 11; + // WorkloadType represents the type of the workload. Used for telemetry. + WorkloadType workload_type = 12; + // WorkloadName represents the name for the workload (of type WorkloadType). Used for telemetry. + string workload_name = 13; + + // If set, this indicates a workload expects to directly receive tunnel traffic. + // In ztunnel, this means: + // * Requests *from* this workload do not need to be tunneled if they already are tunneled by the tunnel_protocol. + // * Requests *to* this workload, via the tunnel_protocol, do not need to be de-tunneled. + bool native_tunnel = 14; + + // If an application, such as a sandwiched waypoint proxy, supports directly + // receiving information from zTunnel they can set application_protocol. + ApplicationTunnel application_tunnel = 23; + + // The services for which this workload is an endpoint. + // The key is the NamespacedHostname string of the format namespace/hostname. + map services = 22; + + // A list of authorization policies applicable to this workload. + // NOTE: this *only* includes Selector based policies. Namespace and global polices + // are returned out of band. + // Authorization policies are only valid for workloads with `addresses` rather than `hostname`. + repeated string authorization_policies = 16; + + WorkloadStatus status = 17; + + // The cluster ID that the workload instance belongs to + string cluster_id = 18; + + // The Locality defines information about where a workload is geographically deployed + Locality locality = 24; + + NetworkMode network_mode = 25; + + // Reservations for deleted fields. + reserved 15; +} + +message Locality { + string region = 1; + string zone = 2; + string subzone = 3; +} + +enum WorkloadStatus { + // Workload is healthy and ready to serve traffic. + HEALTHY = 0; + // Workload is unhealthy and NOT ready to serve traffic. + UNHEALTHY = 1; +} + +enum WorkloadType { + DEPLOYMENT = 0; + CRONJOB = 1; + POD = 2; + JOB = 3; +} + +// PorList represents the ports for a service +message PortList { + repeated Port ports = 1; +} + +message Port { + // Port the service is reached at (frontend). + uint32 service_port = 1; + // Port the service forwards to (backend). + uint32 target_port = 2; +} + +// TunnelProtocol indicates the tunneling protocol for requests. +enum TunnelProtocol { + // NONE means requests should be forwarded as-is, without tunneling. + NONE = 0; + // HBONE means requests should be tunneled over HTTP. + // This does not dictate HTTP/1.1 vs HTTP/2; ALPN should be used for that purpose. + HBONE = 1; + // Future options may include things like QUIC/HTTP3, etc. +} + +// ApplicationProtocol specifies a workload (application or gateway) can +// consume tunnel information. +message ApplicationTunnel { + enum Protocol { + // Bytes are copied from the inner stream without modification. + NONE = 0; + + // Prepend PROXY protocol headers before copying bytes + // Standard PROXY source and destination information + // is included, along with potential extra TLV headers: + // 0xD0 - The SPIFFE identity of the source workload + // 0xD1 - The FQDN or Hostname of the targeted Service + PROXY = 1; + } + + // A target natively handles this type of traffic. + Protocol protocol = 1; + + // optional: if set, traffic should be sent to this port after the last zTunnel hop + uint32 port = 2; +} + +// GatewayAddress represents the address of a gateway +message GatewayAddress { + // address can either be a hostname (ex: gateway.example.com) or an IP (ex: 1.2.3.4). + oneof destination { + // TODO: add support for hostname lookup + NamespacedHostname hostname = 1; + NetworkAddress address = 2; + } + // port to reach the gateway at for mTLS HBONE connections + uint32 hbone_mtls_port = 3; + reserved "hbone_single_tls_port"; + reserved 4; +} + +// NetworkAddress represents an address bound to a specific network. +message NetworkAddress { + // Network represents the network this address is on. + string network = 1; + // Address presents the IP (v4 or v6). + bytes address = 2; +} + +// NamespacedHostname represents a service bound to a specific namespace. +message NamespacedHostname { + // The namespace the service is in. + string namespace = 1; + // hostname (ex: gateway.example.com) + string hostname = 2; +} diff --git a/source/extensions/common/workload_discovery/extension.proto b/source/extensions/common/workload_discovery/extension.proto new file mode 100644 index 00000000000..412f77f851e --- /dev/null +++ b/source/extensions/common/workload_discovery/extension.proto @@ -0,0 +1,24 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "envoy/config/core/v3/config_source.proto"; + +package istio.workload; +option go_package = "test/envoye2e/workloadapi"; + +message BootstrapExtension { + envoy.config.core.v3.ConfigSource config_source = 1; +} diff --git a/source/extensions/filters/http/alpn/BUILD b/source/extensions/filters/http/alpn/BUILD new file mode 100644 index 00000000000..e20c69b83af --- /dev/null +++ b/source/extensions/filters/http/alpn/BUILD @@ -0,0 +1,92 @@ +# Copyright 2019 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "alpn_filter", + srcs = ["alpn_filter.cc"], + hdrs = ["alpn_filter.h"], + repository = "@envoy", + deps = [ + ":config_cc_proto", + "@envoy//envoy/http:filter_interface", + "@envoy//source/common/network:application_protocol_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_library( + name = "config_lib", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + deps = [ + ":alpn_filter", + "@envoy//envoy/registry", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + ], +) + +envoy_cc_test( + name = "alpn_test", + srcs = [ + "alpn_test.cc", + ], + repository = "@envoy", + deps = [ + ":alpn_filter", + ":config_lib", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/mocks/local_info:local_info_mocks", + "@envoy//test/mocks/network:network_mocks", + "@envoy//test/mocks/protobuf:protobuf_mocks", + "@envoy//test/mocks/upstream:upstream_mocks", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = [ + "config_test.cc", + ], + repository = "@envoy", + deps = [ + ":alpn_filter", + ":config_lib", + "@envoy//test/mocks/server:server_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +cc_proto_library( + name = "config_cc_proto", + deps = ["config"], +) + +proto_library( + name = "config", + srcs = ["config.proto"], +) diff --git a/source/extensions/filters/http/alpn/alpn_filter.cc b/source/extensions/filters/http/alpn/alpn_filter.cc new file mode 100644 index 00000000000..b89805e049b --- /dev/null +++ b/source/extensions/filters/http/alpn/alpn_filter.cc @@ -0,0 +1,105 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/http/alpn/alpn_filter.h" + +#include "envoy/upstream/cluster_manager.h" +#include "source/common/network/application_protocol.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +AlpnFilterConfig::AlpnFilterConfig( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig& proto_config, + Upstream::ClusterManager& cluster_manager) + : cluster_manager_(cluster_manager) { + for (const auto& pair : proto_config.alpn_override()) { + std::vector application_protocols; + for (const auto& protocol : pair.alpn_override()) { + application_protocols.push_back(protocol); + } + + alpn_overrides_.insert( + {getHttpProtocol(pair.upstream_protocol()), std::move(application_protocols)}); + } +} + +Http::Protocol AlpnFilterConfig::getHttpProtocol( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig::Protocol& protocol) { + switch (protocol) { + case istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP10: + return Http::Protocol::Http10; + case istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP11: + return Http::Protocol::Http11; + case istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP2: + return Http::Protocol::Http2; + default: + PANIC("not implemented"); + } +} + +Http::FilterHeadersStatus AlpnFilter::decodeHeaders(Http::RequestHeaderMap&, bool) { + Router::RouteConstSharedPtr route = decoder_callbacks_->routeSharedPtr(); + const Router::RouteEntry* route_entry; + if (!route || !(route_entry = route->routeEntry())) { + ENVOY_LOG(debug, "cannot find route entry"); + return Http::FilterHeadersStatus::Continue; + } + + Upstream::ThreadLocalCluster* cluster = + config_->clusterManager().getThreadLocalCluster(route_entry->clusterName()); + if (!cluster || !cluster->info()) { + ENVOY_LOG(debug, "cannot find cluster {}", route_entry->clusterName()); + return Http::FilterHeadersStatus::Continue; + } + + const auto& filter_metadata = cluster->info()->metadata().filter_metadata(); + const auto& istio = filter_metadata.find("istio"); + if (istio != filter_metadata.end()) { + const auto& alpn_override = istio->second.fields().find("alpn_override"); + if (alpn_override != istio->second.fields().end()) { + const auto alpn_override_value = alpn_override->second.string_value(); + if (alpn_override_value == "false") { + // Skip ALPN header rewrite + ENVOY_LOG(debug, + "Skipping ALPN header rewrite because istio.alpn_override metadata is false"); + return Http::FilterHeadersStatus::Continue; + } + } + } + + auto protocols = + cluster->info()->upstreamHttpProtocol(decoder_callbacks_->streamInfo().protocol()); + const auto& alpn_override = config_->alpnOverrides(protocols[0]); + + if (!alpn_override.empty()) { + ENVOY_LOG(debug, "override with {} ALPNs", alpn_override.size()); + decoder_callbacks_->streamInfo().filterState()->setData( + Network::ApplicationProtocols::key(), + std::make_unique(alpn_override), + Envoy::StreamInfo::FilterState::StateType::ReadOnly); + } else { + ENVOY_LOG(debug, "ALPN override is empty"); + } + return Http::FilterHeadersStatus::Continue; +} + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/alpn_filter.h b/source/extensions/filters/http/alpn/alpn_filter.h new file mode 100644 index 00000000000..9b1144240b2 --- /dev/null +++ b/source/extensions/filters/http/alpn/alpn_filter.h @@ -0,0 +1,66 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "source/extensions/filters/http/alpn/config.pb.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +using AlpnOverrides = absl::flat_hash_map>; + +class AlpnFilterConfig { +public: + AlpnFilterConfig( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig& proto_config, + Upstream::ClusterManager& cluster_manager); + + Upstream::ClusterManager& clusterManager() { return cluster_manager_; } + + const std::vector alpnOverrides(const Http::Protocol& protocol) const { + if (alpn_overrides_.count(protocol)) { + return alpn_overrides_.at(protocol); + } + return {}; + } + +private: + Http::Protocol getHttpProtocol( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig::Protocol& protocol); + + AlpnOverrides alpn_overrides_; + Upstream::ClusterManager& cluster_manager_; +}; + +using AlpnFilterConfigSharedPtr = std::shared_ptr; + +class AlpnFilter : public Http::PassThroughDecoderFilter, Logger::Loggable { +public: + explicit AlpnFilter(const AlpnFilterConfigSharedPtr& config) : config_(config) {} + + // Http::PassThroughDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override; + +private: + const AlpnFilterConfigSharedPtr config_; +}; + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/alpn_test.cc b/source/extensions/filters/http/alpn/alpn_test.cc new file mode 100644 index 00000000000..8cc5d2767a5 --- /dev/null +++ b/source/extensions/filters/http/alpn/alpn_test.cc @@ -0,0 +1,194 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "source/common/network/application_protocol.h" +#include "source/extensions/filters/http/alpn/alpn_filter.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/upstream/mocks.h" + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig_AlpnOverride; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Http { +namespace Alpn { +namespace { + +class AlpnFilterTest : public testing::Test { +public: + std::unique_ptr makeAlpnOverrideFilter(const AlpnOverrides& alpn) { + FilterConfig proto_config; + + for (const auto& p : alpn) { + FilterConfig_AlpnOverride entry; + entry.set_upstream_protocol(getProtocol(p.first)); + for (const auto& v : p.second) { + entry.add_alpn_override(v); + } + proto_config.mutable_alpn_override()->Add(std::move(entry)); + } + + auto config = std::make_shared(proto_config, cluster_manager_); + auto filter = std::make_unique(config); + filter->setDecoderFilterCallbacks(callbacks_); + return filter; + } + +protected: + FilterConfig::Protocol getProtocol(Http::Protocol protocol) { + switch (protocol) { + case Http::Protocol::Http10: + return FilterConfig::Protocol::FilterConfig_Protocol_HTTP10; + case Http::Protocol::Http11: + return FilterConfig::Protocol::FilterConfig_Protocol_HTTP11; + case Http::Protocol::Http2: + return FilterConfig::Protocol::FilterConfig_Protocol_HTTP2; + default: + PANIC("not implemented"); + } + } + + NiceMock callbacks_; + NiceMock cluster_manager_; + std::shared_ptr fake_cluster_{ + std::make_shared>()}; + std::shared_ptr cluster_info_{ + std::make_shared>()}; + Http::TestRequestHeaderMapImpl headers_; +}; + +TEST_F(AlpnFilterTest, OverrideAlpnUseDownstreamProtocol) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + const AlpnOverrides alpn = {{Http::Protocol::Http10, {"foo", "bar"}}, + {Http::Protocol::Http11, {"baz"}}, + {Http::Protocol::Http2, {"qux"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + + ON_CALL(cluster_manager_, getThreadLocalCluster(_)).WillByDefault(Return(fake_cluster_.get())); + ON_CALL(*fake_cluster_, info()).WillByDefault(Return(cluster_info_)); + ON_CALL(*cluster_info_, upstreamHttpProtocol(_)) + .WillByDefault([](absl::optional protocol) -> std::vector { + return {protocol.value()}; + }); + + auto protocols = {Http::Protocol::Http10, Http::Protocol::Http11, Http::Protocol::Http2}; + for (const auto p : protocols) { + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(p)); + Envoy::StreamInfo::FilterStateSharedPtr filter_state( + std::make_shared( + Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); + EXPECT_CALL(stream_info, filterState()).WillOnce(ReturnRef(filter_state)); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_TRUE( + filter_state->hasData(Network::ApplicationProtocols::key())); + auto alpn_override = + filter_state + ->getDataReadOnly(Network::ApplicationProtocols::key()) + ->value(); + + EXPECT_EQ(alpn_override, alpn.at(p)); + } +} + +TEST_F(AlpnFilterTest, OverrideAlpn) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + const AlpnOverrides alpn = {{Http::Protocol::Http10, {"foo", "bar"}}, + {Http::Protocol::Http11, {"baz"}}, + {Http::Protocol::Http2, {"qux"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + + ON_CALL(cluster_manager_, getThreadLocalCluster(_)).WillByDefault(Return(fake_cluster_.get())); + ON_CALL(*fake_cluster_, info()).WillByDefault(Return(cluster_info_)); + ON_CALL(*cluster_info_, upstreamHttpProtocol(_)) + .WillByDefault([](absl::optional) -> std::vector { + return {Http::Protocol::Http2}; + }); + + auto protocols = {Http::Protocol::Http10, Http::Protocol::Http11, Http::Protocol::Http2}; + for (const auto p : protocols) { + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(p)); + Envoy::StreamInfo::FilterStateSharedPtr filter_state( + std::make_shared( + Envoy::StreamInfo::FilterState::LifeSpan::FilterChain)); + EXPECT_CALL(stream_info, filterState()).WillOnce(ReturnRef(filter_state)); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_TRUE( + filter_state->hasData(Network::ApplicationProtocols::key())); + auto alpn_override = + filter_state + ->getDataReadOnly(Network::ApplicationProtocols::key()) + ->value(); + + EXPECT_EQ(alpn_override, alpn.at(Http::Protocol::Http2)); + } +} + +TEST_F(AlpnFilterTest, EmptyOverrideAlpn) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + const AlpnOverrides alpn = {{Http::Protocol::Http10, {"foo", "bar"}}, + {Http::Protocol::Http11, {"baz"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + + ON_CALL(cluster_manager_, getThreadLocalCluster(_)).WillByDefault(Return(fake_cluster_.get())); + ON_CALL(*fake_cluster_, info()).WillByDefault(Return(cluster_info_)); + ON_CALL(*cluster_info_, upstreamHttpProtocol(_)) + .WillByDefault([](absl::optional) -> std::vector { + return {Http::Protocol::Http2}; + }); + + auto protocols = {Http::Protocol::Http10, Http::Protocol::Http11, Http::Protocol::Http2}; + for (const auto p : protocols) { + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(p)); + Envoy::StreamInfo::FilterStateImpl filter_state{Envoy::StreamInfo::FilterState::FilterChain}; + EXPECT_CALL(stream_info, filterState()).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_FALSE( + filter_state.hasData(Network::ApplicationProtocols::key())); + } +} + +TEST_F(AlpnFilterTest, AlpnOverrideFalse) { + NiceMock stream_info; + auto metadata = TestUtility::parseYaml(R"EOF( + filter_metadata: + istio: + alpn_override: "false" + )EOF"); + + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + ON_CALL(cluster_manager_, getThreadLocalCluster(_)).WillByDefault(Return(fake_cluster_.get())); + ON_CALL(*fake_cluster_, info()).WillByDefault(Return(cluster_info_)); + ON_CALL(*cluster_info_, metadata()).WillByDefault(ReturnRef(metadata)); + + const AlpnOverrides alpn = {{Http::Protocol::Http10, {"foo", "bar"}}, + {Http::Protocol::Http11, {"baz"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + + EXPECT_CALL(*cluster_info_, upstreamHttpProtocol(_)).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); +} + +} // namespace +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/config.cc b/source/extensions/filters/http/alpn/config.cc new file mode 100644 index 00000000000..51f2a6182b1 --- /dev/null +++ b/source/extensions/filters/http/alpn/config.cc @@ -0,0 +1,56 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/http/alpn/config.h" + +#include "source/common/protobuf/message_validator_impl.h" +#include "source/extensions/filters/http/alpn/alpn_filter.h" + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; + +namespace Envoy { +namespace Http { +namespace Alpn { +absl::StatusOr +AlpnConfigFactory::createFilterFactoryFromProto(const Protobuf::Message& config, const std::string&, + Server::Configuration::FactoryContext& context) { + return createFilterFactory(dynamic_cast(config), + context.serverFactoryContext().clusterManager()); +} + +ProtobufTypes::MessagePtr AlpnConfigFactory::createEmptyConfigProto() { + return ProtobufTypes::MessagePtr{new FilterConfig}; +} + +std::string AlpnConfigFactory::name() const { return "istio.alpn"; } + +Http::FilterFactoryCb +AlpnConfigFactory::createFilterFactory(const FilterConfig& proto_config, + Upstream::ClusterManager& cluster_manager) { + AlpnFilterConfigSharedPtr filter_config{ + std::make_shared(proto_config, cluster_manager)}; + return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_unique(filter_config)); + }; +} + +/** + * Static registration for the alpn override filter. @see RegisterFactory. + */ +REGISTER_FACTORY(AlpnConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/config.h b/source/extensions/filters/http/alpn/config.h new file mode 100644 index 00000000000..a4d991ada77 --- /dev/null +++ b/source/extensions/filters/http/alpn/config.h @@ -0,0 +1,45 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "source/extensions/filters/http/alpn/config.pb.h" +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Http { +namespace Alpn { + +/** + * Config registration for the alpn filter. + */ +class AlpnConfigFactory : public Server::Configuration::NamedHttpFilterConfigFactory { +public: + // Server::Configuration::NamedHttpFilterConfigFactory + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message& config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + std::string name() const override; + +private: + Http::FilterFactoryCb createFilterFactory( + const istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig& config_pb, + Upstream::ClusterManager& cluster_manager); +}; + +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/config.proto b/source/extensions/filters/http/alpn/config.proto new file mode 100644 index 00000000000..64d3e17cc6e --- /dev/null +++ b/source/extensions/filters/http/alpn/config.proto @@ -0,0 +1,41 @@ +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// $title: ALPN filter for overriding ALPN for upstream TLS connections. + +package istio.envoy.config.filter.http.alpn.v2alpha1; + +option go_package = "istio.io/api/envoy/config/filter/http/alpn/v2alpha1"; + +// FilterConfig is the config for Istio-specific filter. +message FilterConfig { + // Upstream protocols + enum Protocol { + HTTP10 = 0; + HTTP11 = 1; + HTTP2 = 2; + } + + message AlpnOverride { + // Upstream protocol + Protocol upstream_protocol = 1; + // A list of ALPN that will override the ALPN for upstream TLS connections. + repeated string alpn_override = 2; + } + + // Map from upstream protocol to list of ALPN + repeated AlpnOverride alpn_override = 1; +} diff --git a/source/extensions/filters/http/alpn/config_test.cc b/source/extensions/filters/http/alpn/config_test.cc new file mode 100644 index 00000000000..e66790d175c --- /dev/null +++ b/source/extensions/filters/http/alpn/config_test.cc @@ -0,0 +1,69 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/http/alpn/config.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "source/extensions/filters/http/alpn/alpn_filter.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +using istio::envoy::config::filter::http::alpn::v2alpha1::FilterConfig; + +namespace Envoy { +namespace Http { +namespace Alpn { +namespace { + +TEST(AlpnFilterConfigTest, OverrideAlpn) { + const std::string yaml = R"EOF( + alpn_override: + - upstream_protocol: HTTP10 + alpn_override: ["foo", "bar"] + - upstream_protocol: HTTP11 + alpn_override: ["baz"] + - upstream_protocol: HTTP2 + alpn_override: ["qux"] + )EOF"; + + FilterConfig proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + AlpnConfigFactory factory; + NiceMock context; + Http::FilterFactoryCb cb = + factory.createFilterFactoryFromProto(proto_config, "stats", context).value(); + Http::MockFilterChainFactoryCallbacks filter_callback; + Http::StreamDecoderFilterSharedPtr added_filter; + EXPECT_CALL(filter_callback, addStreamDecoderFilter(_)) + .WillOnce(Invoke([&added_filter](Http::StreamDecoderFilterSharedPtr filter) { + added_filter = std::move(filter); + })); + + cb(filter_callback); + EXPECT_NE(dynamic_cast(added_filter.get()), nullptr); +} + +} // namespace +} // namespace Alpn +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/http/istio_stats/BUILD b/source/extensions/filters/http/istio_stats/BUILD new file mode 100644 index 00000000000..ae6e9152ffe --- /dev/null +++ b/source/extensions/filters/http/istio_stats/BUILD @@ -0,0 +1,67 @@ +# Copyright 2018 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "istio_stats", + srcs = ["istio_stats.cc"], + hdrs = ["istio_stats.h"], + repository = "@envoy", + deps = [ + ":config_cc_proto", + "//extensions/common:metadata_object_lib", + "@cel-cpp//eval/public:builtin_func_registrar", + "@cel-cpp//eval/public:cel_expr_builder_factory", + "@cel-cpp//parser", + "@envoy//envoy/registry", + "@envoy//envoy/router:string_accessor_interface", + "@envoy//envoy/server:factory_context_interface", + "@envoy//envoy/server:filter_config_interface", + "@envoy//envoy/singleton:manager_interface", + "@envoy//envoy/stream_info:filter_state_interface", + "@envoy//source/common/grpc:common_lib", + "@envoy//source/common/http:header_map_lib", + "@envoy//source/common/http:header_utility_lib", + "@envoy//source/common/network:utility_lib", + "@envoy//source/common/stream_info:utility_lib", + "@envoy//source/extensions/filters/common/expr:context_lib", + "@envoy//source/extensions/filters/common/expr:evaluator_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy//source/extensions/filters/http/grpc_stats:config", + ], +) + +cc_proto_library( + name = "config_cc_proto", + deps = ["config"], +) + +proto_library( + name = "config", + srcs = ["config.proto"], + deps = [ + "@com_google_protobuf//:duration_proto", + ], +) diff --git a/source/extensions/filters/http/istio_stats/config.proto b/source/extensions/filters/http/istio_stats/config.proto new file mode 100644 index 00000000000..5f50e33e698 --- /dev/null +++ b/source/extensions/filters/http/istio_stats/config.proto @@ -0,0 +1,124 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +// $title: Stats Config +// $description: Configuration for Stats Filter. +// $location: https://istio.io/docs/reference/config/proxy_extensions/stats.html +// $weight: 20 + +package stats; + +import "google/protobuf/duration.proto"; + +// Metric instance configuration overrides. +// The metric value and the metric type are optional and permit changing the +// reported value for an existing metric. +// The standard metrics are optimized and reported through a "fast-path". +// The customizations allow full configurability, at the cost of a "slower" +// path. +message MetricConfig { + // (Optional) Collection of tag names and tag expressions to include in the + // metric. Conflicts are resolved by the tag name by overriding previously + // supplied values. + map dimensions = 1; + + // (Optional) Metric name to restrict the override to a metric. If not + // specified, applies to all. + string name = 2; + + // (Optional) A list of tags to remove. + repeated string tags_to_remove = 3; + + // NOT IMPLEMENTED. (Optional) Conditional enabling the override. + string match = 4; + + // (Optional) If this is set to true, the metric(s) selected by this + // configuration will not be generated or reported. + bool drop = 5; +} + +enum MetricType { + COUNTER = 0; + GAUGE = 1; + HISTOGRAM = 2; +} + +message MetricDefinition { + // Metric name. + string name = 1; + + // Metric value expression. + string value = 2; + + // Metric type. + MetricType type = 3; +} + +// Specifies the proxy deployment type. +enum Reporter { + // Default value is inferred from the listener direction, as either client or + // server sidecar. + UNSPECIFIED = 0; + + // Shared server gateway, e.g. "waypoint". + SERVER_GATEWAY = 1; +} + +message PluginConfig { + reserved 1; + reserved "debug"; + + reserved 2; + reserved "max_peer_cache_size"; + + reserved 3; + reserved "stat_prefix"; + + reserved 4; + reserved "field_separator"; + + reserved 5; + reserved "value_separator"; + + // Optional: Disable using host header as a fallback if destination service is + // not available from the controlplane. Disable the fallback if the host + // header originates outsides the mesh, like at ingress. + bool disable_host_header_fallback = 6; + + // Optional. Allows configuration of the time between calls out to for TCP + // metrics reporting. The default duration is `5s`. + google.protobuf.Duration tcp_reporting_duration = 7; + + // Metric overrides. + repeated MetricConfig metrics = 8; + + // Metric definitions. + repeated MetricDefinition definitions = 9; + + // Proxy deployment type. + Reporter reporter = 10; + + // Metric scope rotation interval. Set to 0 to disable the metric scope rotation. + // Defaults to 0. + // DEPRECATED. + google.protobuf.Duration rotation_interval = 11; + + // Metric expiry graceful deletion interval. No-op if the metric rotation is disabled. + // Defaults to 5m. Must be >=1s. + // DEPRECATED. + google.protobuf.Duration graceful_deletion_interval = 12; +} diff --git a/source/extensions/filters/http/istio_stats/istio_stats.cc b/source/extensions/filters/http/istio_stats/istio_stats.cc new file mode 100644 index 00000000000..bd3e2d619fd --- /dev/null +++ b/source/extensions/filters/http/istio_stats/istio_stats.cc @@ -0,0 +1,1306 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/extensions/filters/http/istio_stats/istio_stats.h" + +#include + +#include "envoy/router/string_accessor.h" +#include "envoy/registry/registry.h" +#include "envoy/server/factory_context.h" +#include "envoy/singleton/manager.h" +#include "extensions/common/metadata_object.h" +#include "parser/parser.h" +#include "source/common/grpc/common.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/http/header_utility.h" +#include "source/common/network/utility.h" +#include "source/common/stream_info/utility.h" +#include "source/extensions/filters/common/expr/context.h" +#include "source/extensions/filters/common/expr/cel_state.h" +#include "source/extensions/filters/common/expr/evaluator.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include "source/extensions/filters/http/grpc_stats/grpc_stats_filter.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include "eval/public/builtin_func_registrar.h" +#include "eval/public/cel_expr_builder_factory.h" + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace IstioStats { + +namespace { + +constexpr absl::string_view NamespaceKey = "/ns/"; + +absl::optional getNamespace(absl::string_view principal) { + // The namespace is a substring in principal with format: + // "/ns//sa/". '/' is not allowed to + // appear in actual content except as delimiter between tokens. + size_t begin = principal.find(NamespaceKey); + if (begin == absl::string_view::npos) { + return {}; + } + begin += NamespaceKey.length(); + size_t end = principal.find('/', begin); + size_t len = (end == std::string::npos ? end : end - begin); + return {principal.substr(begin, len)}; +} + +constexpr absl::string_view CustomStatNamespace = "istiocustom"; + +absl::string_view extractString(const Protobuf::Struct& metadata, absl::string_view key) { + const auto& it = metadata.fields().find(key); + if (it == metadata.fields().end()) { + return {}; + } + return it->second.string_value(); +} + +absl::string_view extractMapString(const Protobuf::Struct& metadata, const std::string& map_key, + absl::string_view key) { + const auto& it = metadata.fields().find(map_key); + if (it == metadata.fields().end()) { + return {}; + } + return extractString(it->second.struct_value(), key); +} + +absl::optional +extractEndpointMetadata(const StreamInfo::StreamInfo& info) { + auto upstream_info = info.upstreamInfo(); + auto upstream_host = upstream_info ? upstream_info->upstreamHost() : nullptr; + if (upstream_host && upstream_host->metadata()) { + const auto& filter_metadata = upstream_host->metadata()->filter_metadata(); + const auto& it = filter_metadata.find("istio"); + if (it != filter_metadata.end()) { + const auto& workload_it = it->second.fields().find("workload"); + if (workload_it != it->second.fields().end()) { + return Istio::Common::convertEndpointMetadata(workload_it->second.string_value()); + } + } + } + return {}; +} + +enum class Reporter { + // Regular outbound listener on a sidecar. + ClientSidecar, + // Regular inbound listener on a sidecar. + ServerSidecar, + // Gateway listener for a set of destination workloads. + ServerGateway, +}; + +// Detect if peer info read is completed by TCP metadata exchange. +// Checks for WorkloadMetadataObject key (set atomically with CelState by peer_metadata filter). +bool peerInfoRead(Reporter reporter, const StreamInfo::FilterState& filter_state) { + const auto& filter_state_key = + reporter == Reporter::ServerSidecar || reporter == Reporter::ServerGateway + ? Istio::Common::DownstreamPeerObj + : Istio::Common::UpstreamPeerObj; + return filter_state.hasDataWithName(filter_state_key) || + filter_state.hasDataWithName(Istio::Common::NoPeer); +} + +std::optional +peerInfo(Reporter reporter, const StreamInfo::FilterState& filter_state) { + const auto& cel_state_key = + reporter == Reporter::ServerSidecar || reporter == Reporter::ServerGateway + ? Istio::Common::DownstreamPeer + : Istio::Common::UpstreamPeer; + const auto& obj_key = reporter == Reporter::ServerSidecar || reporter == Reporter::ServerGateway + ? Istio::Common::DownstreamPeerObj + : Istio::Common::UpstreamPeerObj; + + // Try reading as WorkloadMetadataObject first (new format, stored under *_obj key) + const auto* peer_info = + filter_state.getDataReadOnly(obj_key); + if (peer_info) { + return *peer_info; + } + + // Fall back to CelState for backward compatibility with older deployments + const auto* cel_state = + filter_state.getDataReadOnly( + cel_state_key); + if (!cel_state) { + return {}; + } + + Protobuf::Struct obj; + if (!obj.ParseFromString(absl::string_view(cel_state->value()))) { + return {}; + } + + Istio::Common::WorkloadMetadataObject result( + extractString(obj, Istio::Common::InstanceNameToken), + extractString(obj, Istio::Common::ClusterNameToken), + extractString(obj, Istio::Common::NamespaceNameToken), + extractString(obj, Istio::Common::WorkloadNameToken), + extractString(obj, Istio::Common::ServiceNameToken), + extractString(obj, Istio::Common::ServiceVersionToken), + extractString(obj, Istio::Common::AppNameToken), + extractString(obj, Istio::Common::AppVersionToken), + Istio::Common::fromSuffix(extractString(obj, Istio::Common::WorkloadTypeToken)), + extractString(obj, Istio::Common::IdentityToken), + extractString(obj, Istio::Common::RegionToken), extractString(obj, Istio::Common::ZoneToken)); + + // Extract labels from the "labels" field + const auto& labels_it = obj.fields().find(Istio::Common::LabelsToken); + if (labels_it != obj.fields().end() && labels_it->second.has_struct_value()) { + std::vector> labels; + for (const auto& label : labels_it->second.struct_value().fields()) { + labels.push_back({std::string(label.first), std::string(label.second.string_value())}); + } + result.setLabels(labels); + } + + return result; +} + +// Process-wide context shared with all filter instances. +struct Context : public Singleton::Instance { + explicit Context(Stats::Scope& scope, const LocalInfo::LocalInfo& local_info) + : pool_(scope.symbolTable()), local_info_(local_info), + stat_namespace_(pool_.add(CustomStatNamespace)), + requests_total_(pool_.add("istio_requests_total")), + request_duration_milliseconds_(pool_.add("istio_request_duration_milliseconds")), + request_bytes_(pool_.add("istio_request_bytes")), + response_bytes_(pool_.add("istio_response_bytes")), + request_messages_total_(pool_.add("istio_request_messages_total")), + response_messages_total_(pool_.add("istio_response_messages_total")), + tcp_connections_opened_total_(pool_.add("istio_tcp_connections_opened_total")), + tcp_connections_closed_total_(pool_.add("istio_tcp_connections_closed_total")), + tcp_sent_bytes_total_(pool_.add("istio_tcp_sent_bytes_total")), + tcp_received_bytes_total_(pool_.add("istio_tcp_received_bytes_total")), + empty_(pool_.add("")), unknown_(pool_.add("unknown")), source_(pool_.add("source")), + destination_(pool_.add("destination")), latest_(pool_.add("latest")), + http_(pool_.add("http")), grpc_(pool_.add("grpc")), tcp_(pool_.add("tcp")), + mutual_tls_(pool_.add("mutual_tls")), none_(pool_.add("none")), + reporter_(pool_.add("reporter")), source_workload_(pool_.add("source_workload")), + source_workload_namespace_(pool_.add("source_workload_namespace")), + source_principal_(pool_.add("source_principal")), source_app_(pool_.add("source_app")), + source_version_(pool_.add("source_version")), + source_canonical_service_(pool_.add("source_canonical_service")), + source_canonical_revision_(pool_.add("source_canonical_revision")), + source_cluster_(pool_.add("source_cluster")), + destination_workload_(pool_.add("destination_workload")), + destination_workload_namespace_(pool_.add("destination_workload_namespace")), + destination_principal_(pool_.add("destination_principal")), + destination_app_(pool_.add("destination_app")), + destination_version_(pool_.add("destination_version")), + destination_service_(pool_.add("destination_service")), + destination_service_name_(pool_.add("destination_service_name")), + destination_service_namespace_(pool_.add("destination_service_namespace")), + destination_canonical_service_(pool_.add("destination_canonical_service")), + destination_canonical_revision_(pool_.add("destination_canonical_revision")), + destination_cluster_(pool_.add("destination_cluster")), + request_protocol_(pool_.add("request_protocol")), + response_flags_(pool_.add("response_flags")), + connection_security_policy_(pool_.add("connection_security_policy")), + response_code_(pool_.add("response_code")), + grpc_response_status_(pool_.add("grpc_response_status")), + workload_name_(pool_.add(extractString(local_info.node().metadata(), "WORKLOAD_NAME"))), + namespace_(pool_.add(extractString(local_info.node().metadata(), "NAMESPACE"))), + canonical_name_(pool_.add(extractMapString(local_info.node().metadata(), "LABELS", + Istio::Common::CanonicalNameLabel))), + canonical_revision_(pool_.add(extractMapString(local_info.node().metadata(), "LABELS", + Istio::Common::CanonicalRevisionLabel))), + app_name_(pool_.add( + extractMapString(local_info.node().metadata(), "LABELS", Istio::Common::AppNameLabel))), + app_version_(pool_.add(extractMapString(local_info.node().metadata(), "LABELS", + Istio::Common::AppVersionLabel))), + cluster_name_(pool_.add(extractString(local_info.node().metadata(), "CLUSTER_ID"))), + waypoint_(pool_.add("waypoint")), istio_build_(pool_.add("istio_build")), + component_(pool_.add("component")), proxy_(pool_.add("proxy")), tag_(pool_.add("tag")), + istio_version_(pool_.add(extractString(local_info.node().metadata(), "ISTIO_VERSION"))), + scope_(scope.createScope("", true)) { + all_metrics_ = { + {"requests_total", requests_total_}, + {"request_duration_milliseconds", request_duration_milliseconds_}, + {"request_bytes", request_bytes_}, + {"response_bytes", response_bytes_}, + {"request_messages_total", request_messages_total_}, + {"response_messages_total", response_messages_total_}, + {"tcp_connections_opened_total", tcp_connections_opened_total_}, + {"tcp_connections_closed_total", tcp_connections_closed_total_}, + {"tcp_sent_bytes_total", tcp_sent_bytes_total_}, + {"tcp_received_bytes_total", tcp_received_bytes_total_}, + }; + all_tags_ = { + {"reporter", reporter_}, + {"source_workload", source_workload_}, + {"source_workload_namespace", source_workload_namespace_}, + {"source_principal", source_principal_}, + {"source_app", source_app_}, + {"source_version", source_version_}, + {"source_canonical_service", source_canonical_service_}, + {"source_canonical_revision", source_canonical_revision_}, + {"source_cluster", source_cluster_}, + {"destination_workload", destination_workload_}, + {"destination_workload_namespace", destination_workload_namespace_}, + {"destination_principal", destination_principal_}, + {"destination_app", destination_app_}, + {"destination_version", destination_version_}, + {"destination_service", destination_service_}, + {"destination_service_name", destination_service_name_}, + {"destination_service_namespace", destination_service_namespace_}, + {"destination_canonical_service", destination_canonical_service_}, + {"destination_canonical_revision", destination_canonical_revision_}, + {"destination_cluster", destination_cluster_}, + {"request_protocol", request_protocol_}, + {"response_flags", response_flags_}, + {"connection_security_policy", connection_security_policy_}, + {"response_code", response_code_}, + {"grpc_response_status", grpc_response_status_}, + }; + } + + Stats::StatNamePool pool_; + const LocalInfo::LocalInfo& local_info_; + absl::flat_hash_map all_metrics_; + absl::flat_hash_map all_tags_; + + // Metric names. + const Stats::StatName stat_namespace_; + const Stats::StatName requests_total_; + const Stats::StatName request_duration_milliseconds_; + const Stats::StatName request_bytes_; + const Stats::StatName response_bytes_; + const Stats::StatName request_messages_total_; + const Stats::StatName response_messages_total_; + const Stats::StatName tcp_connections_opened_total_; + const Stats::StatName tcp_connections_closed_total_; + const Stats::StatName tcp_sent_bytes_total_; + const Stats::StatName tcp_received_bytes_total_; + + // Constant names. + const Stats::StatName empty_; + const Stats::StatName unknown_; + const Stats::StatName source_; + const Stats::StatName destination_; + const Stats::StatName latest_; + const Stats::StatName http_; + const Stats::StatName grpc_; + const Stats::StatName tcp_; + const Stats::StatName mutual_tls_; + const Stats::StatName none_; + + // Tag names. + const Stats::StatName reporter_; + + const Stats::StatName source_workload_; + const Stats::StatName source_workload_namespace_; + const Stats::StatName source_principal_; + const Stats::StatName source_app_; + const Stats::StatName source_version_; + const Stats::StatName source_canonical_service_; + const Stats::StatName source_canonical_revision_; + const Stats::StatName source_cluster_; + + const Stats::StatName destination_workload_; + const Stats::StatName destination_workload_namespace_; + const Stats::StatName destination_principal_; + const Stats::StatName destination_app_; + const Stats::StatName destination_version_; + const Stats::StatName destination_service_; + const Stats::StatName destination_service_name_; + const Stats::StatName destination_service_namespace_; + const Stats::StatName destination_canonical_service_; + const Stats::StatName destination_canonical_revision_; + const Stats::StatName destination_cluster_; + + const Stats::StatName request_protocol_; + const Stats::StatName response_flags_; + const Stats::StatName connection_security_policy_; + const Stats::StatName response_code_; + const Stats::StatName grpc_response_status_; + + // Per-process constants. + const Stats::StatName workload_name_; + const Stats::StatName namespace_; + const Stats::StatName canonical_name_; + const Stats::StatName canonical_revision_; + const Stats::StatName app_name_; + const Stats::StatName app_version_; + const Stats::StatName cluster_name_; + const Stats::StatName waypoint_; + + // istio_build metric: + // Publishes Istio version for the proxy as a gauge, sample data: + // testdata/metric/istio_build.yaml + // Sample value for istio_version: "1.17.0" + const Stats::StatName istio_build_; + const Stats::StatName component_; + const Stats::StatName proxy_; + const Stats::StatName tag_; + const Stats::StatName istio_version_; + + // Shared evictable stats scope + Stats::ScopeSharedPtr scope_; +}; // namespace + +using ContextSharedPtr = std::shared_ptr; + +SINGLETON_MANAGER_REGISTRATION(Context) + +// Instructions on dropping, creating, and overriding labels. +// This is not the "hot path" of the metrics system and thus, fairly +// unoptimized. +struct MetricOverrides : public Logger::Loggable { + MetricOverrides(ContextSharedPtr& context, Stats::SymbolTable& symbol_table) + : context_(context), pool_(symbol_table) {} + ContextSharedPtr context_; + Stats::StatNameDynamicPool pool_; + + enum class MetricType { + Counter, + Gauge, + Histogram, + }; + struct CustomMetric { + Stats::StatName name_; + uint32_t expr_; + MetricType type_; + explicit CustomMetric(Stats::StatName name, uint32_t expr, MetricType type) + : name_(name), expr_(expr), type_(type) {} + }; + absl::flat_hash_map custom_metrics_; + // Initial transformation: metrics dropped. + absl::flat_hash_set drop_; + // Second transformation: tags changed. + using TagOverrides = absl::flat_hash_map>; + absl::flat_hash_map tag_overrides_; + // Third transformation: tags added. + using TagAdditions = std::vector>; + absl::flat_hash_map tag_additions_; + + Stats::StatNameTagVector + overrideTags(Stats::StatName metric, const Stats::StatNameTagVector& tags, + const std::vector>& expr_values) { + Stats::StatNameTagVector out; + out.reserve(tags.size()); + const auto& tag_overrides_it = tag_overrides_.find(metric); + if (tag_overrides_it == tag_overrides_.end()) { + out = tags; + } else + for (const auto& [key, val] : tags) { + const auto& it = tag_overrides_it->second.find(key); + if (it != tag_overrides_it->second.end()) { + if (it->second.has_value()) { + out.push_back({key, expr_values[it->second.value()].first}); + } else { + // Skip dropped tags. + } + } else { + out.push_back({key, val}); + } + } + const auto& tag_additions_it = tag_additions_.find(metric); + if (tag_additions_it != tag_additions_.end()) { + for (const auto& [tag, id] : tag_additions_it->second) { + out.push_back({tag, expr_values[id].first}); + } + } + return out; + } + absl::optional getOrCreateExpression(const std::string& expr, bool int_expr) { + const auto& it = expression_ids_.find(expr); + if (it != expression_ids_.end()) { + return {it->second}; + } + auto parse_status = google::api::expr::parser::Parse(expr); + if (!parse_status.ok()) { + return {}; + } + if (expr_builder_ == nullptr) { + google::api::expr::runtime::InterpreterOptions options; + auto builder = google::api::expr::runtime::CreateCelExpressionBuilder(options); + auto register_status = + google::api::expr::runtime::RegisterBuiltinFunctions(builder->GetRegistry(), options); + if (!register_status.ok()) { + throw Extensions::Filters::Common::Expr::CelException( + absl::StrCat("failed to register built-in functions: ", register_status.message())); + } + expr_builder_ = + std::make_shared(std::move(builder)); + } + const auto& parsed_expr = parse_status.value(); + const cel::expr::Expr& cel_expr = parsed_expr.expr(); + + parsed_exprs_.push_back(cel_expr); + auto compiled_expr = Extensions::Filters::Common::Expr::CompiledExpression::Create( + expr_builder_, parsed_exprs_.back()); + if (!compiled_expr.ok()) { + throw Extensions::Filters::Common::Expr::CelException( + absl::StrCat("failed to create compiled expression: ", compiled_expr.status().message())); + } + compiled_exprs_.push_back(std::make_pair(std::move(compiled_expr.value()), int_expr)); + uint32_t id = compiled_exprs_.size() - 1; + expression_ids_.emplace(expr, id); + return {id}; + } + Extensions::Filters::Common::Expr::BuilderInstanceSharedConstPtr expr_builder_; + std::vector parsed_exprs_; + std::vector> compiled_exprs_; + absl::flat_hash_map expression_ids_; +}; + +struct Config : public Logger::Loggable { + Config(const stats::PluginConfig& proto_config, + Server::Configuration::FactoryContext& factory_context) + : context_(factory_context.serverFactoryContext().singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(Context), + [&factory_context] { + return std::make_shared(factory_context.serverFactoryContext().scope(), + factory_context.serverFactoryContext().localInfo()); + })), + disable_host_header_fallback_(proto_config.disable_host_header_fallback()), + report_duration_( + PROTOBUF_GET_MS_OR_DEFAULT(proto_config, tcp_reporting_duration, /* 5s */ 5000)) { + recordVersion(factory_context); + reporter_ = Reporter::ClientSidecar; + switch (proto_config.reporter()) { + case stats::Reporter::UNSPECIFIED: + switch (factory_context.listenerInfo().direction()) { + case envoy::config::core::v3::TrafficDirection::INBOUND: + reporter_ = Reporter::ServerSidecar; + break; + case envoy::config::core::v3::TrafficDirection::OUTBOUND: + reporter_ = Reporter::ClientSidecar; + break; + default: + break; + } + break; + case stats::Reporter::SERVER_GATEWAY: + reporter_ = Reporter::ServerGateway; + break; + default: + break; + } + if (proto_config.metrics_size() > 0 || proto_config.definitions_size() > 0) { + metric_overrides_ = std::make_unique(context_, scope().symbolTable()); + for (const auto& definition : proto_config.definitions()) { + const auto& it = context_->all_metrics_.find(definition.name()); + if (it != context_->all_metrics_.end()) { + ENVOY_LOG(info, "Re-defining standard metric not allowed:: {}", definition.name()); + continue; + } + auto id = metric_overrides_->getOrCreateExpression(definition.value(), true); + if (!id.has_value()) { + ENVOY_LOG(info, "Failed to parse metric value expression: {}", definition.value()); + continue; + } + auto metric_type = MetricOverrides::MetricType::Counter; + switch (definition.type()) { + case stats::MetricType::GAUGE: + metric_type = MetricOverrides::MetricType::Gauge; + break; + case stats::MetricType::HISTOGRAM: + metric_type = MetricOverrides::MetricType::Histogram; + break; + default: + break; + } + metric_overrides_->custom_metrics_.try_emplace( + definition.name(), + metric_overrides_->pool_.add(absl::StrCat("istio_", definition.name())), id.value(), + metric_type); + } + for (const auto& metric : proto_config.metrics()) { + if (metric.drop()) { + const auto& it = context_->all_metrics_.find(metric.name()); + if (it != context_->all_metrics_.end()) { + metric_overrides_->drop_.insert(it->second); + } + continue; + } + for (const auto& tag : metric.tags_to_remove()) { + const auto& tag_it = context_->all_tags_.find(tag); + if (tag_it == context_->all_tags_.end()) { + ENVOY_LOG(info, "Tag is not standard: {}", tag); + continue; + } + if (!metric.name().empty()) { + const auto& it = context_->all_metrics_.find(metric.name()); + if (it != context_->all_metrics_.end()) { + metric_overrides_->tag_overrides_[it->second][tag_it->second] = {}; + } + } else + for (const auto& [_, metric] : context_->all_metrics_) { + metric_overrides_->tag_overrides_[metric][tag_it->second] = {}; + } + } + // Make order of tags deterministic. + std::vector tags; + tags.reserve(metric.dimensions().size()); + for (const auto& [tag, _] : metric.dimensions()) { + tags.push_back(tag); + } + std::sort(tags.begin(), tags.end()); + for (const auto& tag : tags) { + const std::string& expr = metric.dimensions().at(tag); + auto id = metric_overrides_->getOrCreateExpression(expr, false); + if (!id.has_value()) { + ENVOY_LOG(info, "Failed to parse expression: {}", expr); + } + const auto& tag_it = context_->all_tags_.find(tag); + if (tag_it == context_->all_tags_.end()) { + if (!id.has_value()) { + continue; + } + const auto tag_name = metric_overrides_->pool_.add(tag); + if (!metric.name().empty()) { + const auto& it = context_->all_metrics_.find(metric.name()); + if (it != context_->all_metrics_.end()) { + metric_overrides_->tag_additions_[it->second].push_back({tag_name, id.value()}); + } + const auto& custom_it = metric_overrides_->custom_metrics_.find(metric.name()); + if (custom_it != metric_overrides_->custom_metrics_.end()) { + metric_overrides_->tag_additions_[custom_it->second.name_].push_back( + {tag_name, id.value()}); + } + } else { + for (const auto& [_, metric] : context_->all_metrics_) { + metric_overrides_->tag_additions_[metric].push_back({tag_name, id.value()}); + } + for (const auto& [_, metric] : metric_overrides_->custom_metrics_) { + metric_overrides_->tag_additions_[metric.name_].push_back({tag_name, id.value()}); + } + } + } else { + const auto tag_name = tag_it->second; + if (!metric.name().empty()) { + const auto& it = context_->all_metrics_.find(metric.name()); + if (it != context_->all_metrics_.end()) { + metric_overrides_->tag_overrides_[it->second][tag_name] = id; + } + const auto& custom_it = metric_overrides_->custom_metrics_.find(metric.name()); + if (custom_it != metric_overrides_->custom_metrics_.end()) { + metric_overrides_->tag_additions_[custom_it->second.name_].push_back( + {tag_name, id.value()}); + } + } else { + for (const auto& [_, metric] : context_->all_metrics_) { + metric_overrides_->tag_overrides_[metric][tag_name] = id; + } + for (const auto& [_, metric] : metric_overrides_->custom_metrics_) { + metric_overrides_->tag_additions_[metric.name_].push_back({tag_name, id.value()}); + } + } + } + } + } + } + } + + // RAII for stream context propagation. + struct StreamOverrides : public Filters::Common::Expr::StreamActivation { + StreamOverrides(Config& parent, Stats::StatNameDynamicPool& pool) + : parent_(parent), pool_(pool) {} + + void evaluate(const StreamInfo::StreamInfo& info, + const Http::RequestHeaderMap* request_headers = nullptr, + const Http::ResponseHeaderMap* response_headers = nullptr, + const Http::ResponseTrailerMap* response_trailers = nullptr) { + evaluated_ = true; + if (parent_.metric_overrides_) { + local_info_ = &parent_.context_->local_info_; + activation_info_ = &info; + activation_request_headers_ = request_headers; + activation_response_headers_ = response_headers; + activation_response_trailers_ = response_trailers; + const auto& compiled_exprs = parent_.metric_overrides_->compiled_exprs_; + expr_values_.clear(); + expr_values_.reserve(compiled_exprs.size()); + for (size_t id = 0; id < compiled_exprs.size(); id++) { + Protobuf::Arena arena; + auto eval_status = compiled_exprs[id].first.evaluate(*this, &arena); + if (!eval_status.ok() || eval_status.value().IsError()) { + if (!eval_status.ok()) { + ENVOY_LOG(debug, "Failed to evaluate metric expression: {}", eval_status.status()); + } + if (eval_status.value().IsError()) { + ENVOY_LOG(debug, "Failed to evaluate metric expression: {}", + eval_status.value().ErrorOrDie()->message()); + } + expr_values_.push_back(std::make_pair(parent_.context_->unknown_, 0)); + } else { + const auto string_value = Filters::Common::Expr::print(eval_status.value()); + if (compiled_exprs[id].second) { + uint64_t amount = 0; + if (!absl::SimpleAtoi(string_value, &amount)) { + ENVOY_LOG(trace, "Failed to get metric value: {}", string_value); + } + expr_values_.push_back(std::make_pair(Stats::StatName(), amount)); + } else { + expr_values_.push_back(std::make_pair(pool_.add(string_value), 0)); + } + } + } + resetActivation(); + } + } + + void addCounter(Stats::StatName metric, const Stats::StatNameTagVector& tags, + uint64_t amount = 1) { + ASSERT(evaluated_); + if (parent_.metric_overrides_) { + if (parent_.metric_overrides_->drop_.contains(metric)) { + return; + } + auto new_tags = parent_.metric_overrides_->overrideTags(metric, tags, expr_values_); + Stats::Utility::counterFromStatNames(parent_.scope(), + {parent_.context_->stat_namespace_, metric}, new_tags) + .add(amount); + return; + } + Stats::Utility::counterFromStatNames(parent_.scope(), + {parent_.context_->stat_namespace_, metric}, tags) + .add(amount); + } + + void recordHistogram(Stats::StatName metric, Stats::Histogram::Unit unit, + const Stats::StatNameTagVector& tags, uint64_t value) { + ASSERT(evaluated_); + if (parent_.metric_overrides_) { + if (parent_.metric_overrides_->drop_.contains(metric)) { + return; + } + auto new_tags = parent_.metric_overrides_->overrideTags(metric, tags, expr_values_); + Stats::Utility::histogramFromStatNames( + parent_.scope(), {parent_.context_->stat_namespace_, metric}, unit, new_tags) + .recordValue(value); + return; + } + Stats::Utility::histogramFromStatNames( + parent_.scope(), {parent_.context_->stat_namespace_, metric}, unit, tags) + .recordValue(value); + } + + void recordCustomMetrics() { + ASSERT(evaluated_); + if (parent_.metric_overrides_) { + for (const auto& [_, metric] : parent_.metric_overrides_->custom_metrics_) { + const auto tags = parent_.metric_overrides_->overrideTags(metric.name_, {}, expr_values_); + uint64_t amount = expr_values_[metric.expr_].second; + switch (metric.type_) { + case MetricOverrides::MetricType::Counter: + Stats::Utility::counterFromStatNames( + parent_.scope(), {parent_.context_->stat_namespace_, metric.name_}, tags) + .add(amount); + break; + case MetricOverrides::MetricType::Histogram: + Stats::Utility::histogramFromStatNames( + parent_.scope(), {parent_.context_->stat_namespace_, metric.name_}, + Stats::Histogram::Unit::Bytes, tags) + .recordValue(amount); + break; + case MetricOverrides::MetricType::Gauge: + Stats::Utility::gaugeFromStatNames(parent_.scope(), + {parent_.context_->stat_namespace_, metric.name_}, + Stats::Gauge::ImportMode::Accumulate, tags) + .set(amount); + break; + default: + break; + } + } + } + } + + Config& parent_; + Stats::StatNameDynamicPool& pool_; + std::vector> expr_values_; + bool evaluated_{false}; + }; + + void recordVersion(Server::Configuration::FactoryContext& factory_context) { + Stats::StatNameTagVector tags; + tags.push_back({context_->component_, context_->proxy_}); + tags.push_back({context_->tag_, context_->istio_version_.empty() ? context_->unknown_ + : context_->istio_version_}); + + Stats::Utility::gaugeFromStatNames(factory_context.scope(), + {context_->stat_namespace_, context_->istio_build_}, + Stats::Gauge::ImportMode::Accumulate, tags) + .set(1); + } + + Reporter reporter() const { return reporter_; } + Stats::Scope& scope() { return *context_->scope_; } + + ContextSharedPtr context_; + Reporter reporter_; + + const bool disable_host_header_fallback_; + const std::chrono::milliseconds report_duration_; + std::unique_ptr metric_overrides_; +}; + +using ConfigSharedPtr = std::shared_ptr; + +class IstioStatsFilter : public Http::PassThroughFilter, + public Logger::Loggable, + public AccessLog::Instance, + public Network::ReadFilter, + public Network::ConnectionCallbacks { +public: + IstioStatsFilter(ConfigSharedPtr config) + : config_(config), context_(*config->context_), pool_(config->scope().symbolTable()), + stream_(*config_, pool_) { + tags_.reserve(25); + switch (config_->reporter()) { + case Reporter::ServerSidecar: + tags_.push_back({context_.reporter_, context_.destination_}); + break; + case Reporter::ServerGateway: + tags_.push_back({context_.reporter_, context_.waypoint_}); + break; + case Reporter::ClientSidecar: + tags_.push_back({context_.reporter_, context_.source_}); + break; + } + } + ~IstioStatsFilter() { ASSERT(report_timer_ == nullptr); } + + // Http::StreamDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& request_headers, bool) override { + is_grpc_ = Grpc::Common::isGrpcRequestHeaders(request_headers); + if (is_grpc_) { + report_timer_ = decoder_callbacks_->dispatcher().createTimer([this] { onReportTimer(); }); + report_timer_->enableTimer(config_->report_duration_); + } + return Http::FilterHeadersStatus::Continue; + } + + // AccessLog::Instance + void log(const Formatter::Context& log_context, const StreamInfo::StreamInfo& info) override { + const Http::RequestHeaderMap* request_headers = log_context.requestHeaders().ptr(); + const Http::ResponseHeaderMap* response_headers = log_context.responseHeaders().ptr(); + const Http::ResponseTrailerMap* response_trailers = log_context.responseTrailers().ptr(); + + reportHelper(true); + if (is_grpc_) { + tags_.push_back({context_.request_protocol_, context_.grpc_}); + } else { + tags_.push_back({context_.request_protocol_, context_.http_}); + } + + // TODO: copy Http::CodeStatsImpl version for status codes and flags. + tags_.push_back( + {context_.response_code_, pool_.add(absl::StrCat(info.responseCode().value_or(0)))}); + if (is_grpc_) { + auto const& optional_status = Grpc::Common::getGrpcStatus( + response_trailers ? *response_trailers + : *Http::StaticEmptyHeaders::get().response_trailers, + response_headers ? *response_headers : *Http::StaticEmptyHeaders::get().response_headers, + info); + tags_.push_back( + {context_.grpc_response_status_, + optional_status ? pool_.add(absl::StrCat(optional_status.value())) : context_.empty_}); + } else { + tags_.push_back({context_.grpc_response_status_, context_.empty_}); + } + populateFlagsAndConnectionSecurity(info); + + // Evaluate the end stream override expressions for HTTP. This may change values for periodic + // metrics. + stream_.evaluate(info, request_headers, response_headers, response_trailers); + stream_.addCounter(context_.requests_total_, tags_); + auto duration = info.requestComplete(); + if (duration.has_value()) { + stream_.recordHistogram(context_.request_duration_milliseconds_, + Stats::Histogram::Unit::Milliseconds, tags_, + absl::FromChrono(duration.value()) / absl::Milliseconds(1)); + } + auto meter = info.getDownstreamBytesMeter(); + if (meter) { + stream_.recordHistogram(context_.request_bytes_, Stats::Histogram::Unit::Bytes, tags_, + meter->wireBytesReceived()); + stream_.recordHistogram(context_.response_bytes_, Stats::Histogram::Unit::Bytes, tags_, + meter->wireBytesSent()); + } + stream_.recordCustomMetrics(); + } + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance&, bool) override { + return Network::FilterStatus::Continue; + } + Network::FilterStatus onNewConnection() override { + if (config_->report_duration_ > std::chrono::milliseconds(0)) { + report_timer_ = network_read_callbacks_->connection().dispatcher().createTimer( + [this] { onReportTimer(); }); + report_timer_->enableTimer(config_->report_duration_); + } + return Network::FilterStatus::Continue; + } + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + network_read_callbacks_ = &callbacks; + network_read_callbacks_->connection().addConnectionCallbacks(*this); + } + // Network::ConnectionCallbacks + void onEvent(Network::ConnectionEvent event) override { + switch (event) { + case Network::ConnectionEvent::LocalClose: + case Network::ConnectionEvent::RemoteClose: + reportHelper(true); + break; + default: + break; + } + } + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + +private: + // Invoked periodically for streams. + void reportHelper(bool end_stream) { + if (end_stream && report_timer_) { + report_timer_->disableTimer(); + report_timer_.reset(); + } + // HTTP handled first. + if (decoder_callbacks_) { + if (!peer_read_) { + const auto& info = decoder_callbacks_->streamInfo(); + peer_read_ = peerInfoRead(config_->reporter(), info.filterState()); + if (peer_read_ || end_stream) { + ENVOY_LOG(trace, "Populating peer metadata from HTTP MX."); + populatePeerInfo(info, info.filterState()); + } + if (is_grpc_ && (peer_read_ || end_stream)) { + // For periodic HTTP metric, evaluate once when the peer info is read. + stream_.evaluate(decoder_callbacks_->streamInfo()); + } + } + if (is_grpc_ && (peer_read_ || end_stream)) { + const auto* counters = + decoder_callbacks_->streamInfo() + .filterState() + ->getDataReadOnly("envoy.filters.http.grpc_stats"); + if (counters) { + stream_.addCounter(context_.request_messages_total_, tags_, + counters->request_message_count - request_message_count_); + stream_.addCounter(context_.response_messages_total_, tags_, + counters->response_message_count - response_message_count_); + request_message_count_ = counters->request_message_count; + response_message_count_ = counters->response_message_count; + } + } + return; + } + const auto& info = network_read_callbacks_->connection().streamInfo(); + // TCP MX writes to upstream stream info instead. + OptRef upstream_info; + if (config_->reporter() == Reporter::ClientSidecar) { + upstream_info = info.upstreamInfo(); + } + const StreamInfo::FilterState& filter_state = + upstream_info && upstream_info->upstreamFilterState() + ? *upstream_info->upstreamFilterState() + : info.filterState(); + + if (!peer_read_) { + peer_read_ = peerInfoRead(config_->reporter(), filter_state); + // Report connection open once peer info is read or connection is closed. + if (peer_read_ || end_stream) { + ENVOY_LOG(trace, "Populating peer metadata from TCP MX."); + populatePeerInfo(info, filter_state); + tags_.push_back({context_.request_protocol_, context_.tcp_}); + populateFlagsAndConnectionSecurity(info); + // For TCP, evaluate only once immediately before emitting the first metric. + stream_.evaluate(info); + stream_.addCounter(context_.tcp_connections_opened_total_, tags_); + } + } + if (peer_read_ || end_stream) { + auto meter = info.getDownstreamBytesMeter(); + if (meter) { + stream_.addCounter(context_.tcp_sent_bytes_total_, tags_, + meter->wireBytesSent() - bytes_sent_); + bytes_sent_ = meter->wireBytesSent(); + stream_.addCounter(context_.tcp_received_bytes_total_, tags_, + meter->wireBytesReceived() - bytes_received_); + bytes_received_ = meter->wireBytesReceived(); + } + } + if (end_stream) { + stream_.addCounter(context_.tcp_connections_closed_total_, tags_); + stream_.recordCustomMetrics(); + } + } + void onReportTimer() { + reportHelper(false); + report_timer_->enableTimer(config_->report_duration_); + } + + void populateFlagsAndConnectionSecurity(const StreamInfo::StreamInfo& info) { + tags_.push_back( + {context_.response_flags_, pool_.add(StreamInfo::ResponseFlagUtils::toShortString(info))}); + tags_.push_back({context_.connection_security_policy_, + mutual_tls_.has_value() + ? (*mutual_tls_ ? context_.mutual_tls_ : context_.none_) + : context_.unknown_}); + } + + // Peer metadata is populated after encode/decodeHeaders by MX HTTP filter, + // and after initial bytes read/written by MX TCP filter. + void populatePeerInfo(const StreamInfo::StreamInfo& info, + const StreamInfo::FilterState& filter_state) { + // Compute peer info with client-side fallbacks. + absl::optional peer; + auto object = peerInfo(config_->reporter(), filter_state); + if (object) { + peer.emplace(object.value()); + } else if (config_->reporter() == Reporter::ClientSidecar) { + if (auto label_obj = extractEndpointMetadata(info); label_obj) { + peer.emplace(label_obj.value()); + } + } + + // Compute destination service with client-side fallbacks. + absl::string_view service_host; + absl::string_view service_host_name; + absl::string_view service_namespace; + if (!config_->disable_host_header_fallback_) { + const auto* headers = info.getRequestHeaders(); + if (headers && headers->Host()) { + service_host = headers->Host()->value().getStringView(); + service_host_name = service_host; + } + } + if (info.getRouteName() == "block_all") { + service_host_name = "BlackHoleCluster"; + } else if (info.getRouteName() == "allow_any") { + service_host_name = "PassthroughCluster"; + } else { + const auto cluster_info = info.upstreamClusterInfo(); + if (cluster_info) { + const auto& cluster_name = cluster_info->name(); + if (cluster_name == "BlackHoleCluster" || cluster_name == "PassthroughCluster" || + cluster_name == "InboundPassthroughCluster" || + cluster_name == "InboundPassthroughClusterIpv4" || + cluster_name == "InboundPassthroughClusterIpv6") { + service_host_name = cluster_name; + } else { + const auto& filter_metadata = cluster_info->metadata().filter_metadata(); + const auto& it = filter_metadata.find("istio"); + if (it != filter_metadata.end()) { + const auto& services_it = it->second.fields().find("services"); + if (services_it != it->second.fields().end()) { + const auto& services = services_it->second.list_value(); + if (services.values_size() > 0) { + const auto& service = services.values(0).struct_value().fields(); + const auto& host_it = service.find("host"); + if (host_it != service.end()) { + service_host = host_it->second.string_value(); + } + const auto& name_it = service.find("name"); + const auto& namespace_it = service.find("namespace"); + if (namespace_it != service.end()) { + service_namespace = namespace_it->second.string_value(); + } + if (name_it != service.end()) { + service_host_name = name_it->second.string_value(); + } else { + service_host_name = service_host.substr(0, service_host.find_first_of('.')); + } + } + } + } + } + } + } + + std::string peer_san; + absl::string_view local_san; + switch (config_->reporter()) { + case Reporter::ServerSidecar: + case Reporter::ServerGateway: { + auto peer_principal = + info.filterState().getDataReadOnly("io.istio.peer_principal"); + auto local_principal = + info.filterState().getDataReadOnly("io.istio.local_principal"); + peer_san = peer_principal ? peer_principal->asString() : ""; + local_san = local_principal ? local_principal->asString() : ""; + + // This fallback should be deleted once istio_authn is globally enabled. + if (peer_san.empty() && local_san.empty()) { + const Ssl::ConnectionInfoConstSharedPtr ssl_info = + info.downstreamAddressProvider().sslConnection(); + if (ssl_info && !ssl_info->uriSanPeerCertificate().empty()) { + peer_san = ssl_info->uriSanPeerCertificate()[0]; + } + if (ssl_info && !ssl_info->uriSanLocalCertificate().empty()) { + local_san = ssl_info->uriSanLocalCertificate()[0]; + } + } + + // Save the connection security policy for a tag added later. + mutual_tls_ = !peer_san.empty() && !local_san.empty(); + break; + } + case Reporter::ClientSidecar: { + const Ssl::ConnectionInfoConstSharedPtr ssl_info = + info.upstreamInfo() ? info.upstreamInfo()->upstreamSslConnection() : nullptr; + std::optional endpoint_peer; + if (ssl_info && !ssl_info->uriSanPeerCertificate().empty()) { + peer_san = ssl_info->uriSanPeerCertificate()[0]; + } + if (peer_san.empty()) { + auto endpoint_object = peerInfo(config_->reporter(), filter_state); + if (endpoint_object) { + endpoint_peer.emplace(endpoint_object.value()); + peer_san = endpoint_peer->identity_; + } + } + // This won't work for sidecar/ingress -> ambient becuase of the CONNECT + // tunnel. + if (ssl_info && !ssl_info->uriSanLocalCertificate().empty()) { + local_san = ssl_info->uriSanLocalCertificate()[0]; + } + break; + } + } + // Implements fallback from using the namespace from SAN if available to + // using peer metadata, otherwise. + absl::string_view peer_namespace; + if (!peer_san.empty()) { + const auto san_namespace = getNamespace(peer_san); + if (san_namespace) { + peer_namespace = san_namespace.value(); + } + } + if (peer_namespace.empty() && peer) { + peer_namespace = peer->namespace_name_; + } + switch (config_->reporter()) { + case Reporter::ServerSidecar: + case Reporter::ServerGateway: { + tags_.push_back({context_.source_workload_, peer && !peer->workload_name_.empty() + ? pool_.add(peer->workload_name_) + : context_.unknown_}); + tags_.push_back({context_.source_canonical_service_, peer && !peer->canonical_name_.empty() + ? pool_.add(peer->canonical_name_) + : context_.unknown_}); + tags_.push_back( + {context_.source_canonical_revision_, peer && !peer->canonical_revision_.empty() + ? pool_.add(peer->canonical_revision_) + : context_.latest_}); + tags_.push_back({context_.source_workload_namespace_, + !peer_namespace.empty() ? pool_.add(peer_namespace) : context_.unknown_}); + tags_.push_back({context_.source_principal_, + !peer_san.empty() ? pool_.add(peer_san) : context_.unknown_}); + tags_.push_back({context_.source_app_, peer && !peer->app_name_.empty() + ? pool_.add(peer->app_name_) + : context_.unknown_}); + tags_.push_back({context_.source_version_, peer && !peer->app_version_.empty() + ? pool_.add(peer->app_version_) + : context_.unknown_}); + tags_.push_back({context_.source_cluster_, peer && !peer->cluster_name_.empty() + ? pool_.add(peer->cluster_name_) + : context_.unknown_}); + switch (config_->reporter()) { + case Reporter::ServerGateway: { + std::optional endpoint_peer; + auto endpoint_object = peerInfo(Reporter::ClientSidecar, filter_state); + if (endpoint_object) { + endpoint_peer.emplace(endpoint_object.value()); + } + tags_.push_back( + {context_.destination_workload_, endpoint_peer && !endpoint_peer->workload_name_.empty() + ? pool_.add(endpoint_peer->workload_name_) + : context_.unknown_}); + tags_.push_back({context_.destination_workload_namespace_, + endpoint_peer && !endpoint_peer->namespace_name_.empty() + ? pool_.add(endpoint_peer->namespace_name_) + : context_.unknown_}); + tags_.push_back( + {context_.destination_principal_, endpoint_peer && !endpoint_peer->identity_.empty() + ? pool_.add(endpoint_peer->identity_) + : context_.unknown_}); + // Endpoint encoding does not have app and version. + tags_.push_back( + {context_.destination_app_, endpoint_peer && !endpoint_peer->app_name_.empty() + ? pool_.add(endpoint_peer->app_name_) + : context_.unknown_}); + tags_.push_back( + {context_.destination_version_, endpoint_peer && !endpoint_peer->app_version_.empty() + ? pool_.add(endpoint_peer->app_version_) + : context_.unknown_}); + tags_.push_back({context_.destination_service_, + service_host.empty() ? context_.unknown_ : pool_.add(service_host)}); + tags_.push_back({context_.destination_canonical_service_, + endpoint_peer && !endpoint_peer->canonical_name_.empty() + ? pool_.add(endpoint_peer->canonical_name_) + : context_.unknown_}); + tags_.push_back({context_.destination_canonical_revision_, + endpoint_peer && !endpoint_peer->canonical_revision_.empty() + ? pool_.add(endpoint_peer->canonical_revision_) + : context_.unknown_}); + tags_.push_back({context_.destination_service_name_, service_host_name.empty() + ? context_.unknown_ + : pool_.add(service_host_name)}); + tags_.push_back({context_.destination_service_namespace_, !service_namespace.empty() + ? pool_.add(service_namespace) + : context_.unknown_}); + tags_.push_back( + {context_.destination_cluster_, endpoint_peer && !endpoint_peer->cluster_name_.empty() + ? pool_.add(endpoint_peer->cluster_name_) + : context_.unknown_}); + break; + } + default: + tags_.push_back({context_.destination_workload_, context_.workload_name_}); + tags_.push_back({context_.destination_workload_namespace_, context_.namespace_}); + tags_.push_back({context_.destination_principal_, + !local_san.empty() ? pool_.add(local_san) : context_.unknown_}); + tags_.push_back({context_.destination_app_, context_.app_name_}); + tags_.push_back({context_.destination_version_, context_.app_version_}); + tags_.push_back({context_.destination_service_, service_host.empty() + ? context_.canonical_name_ + : pool_.add(service_host)}); + tags_.push_back({context_.destination_canonical_service_, context_.canonical_name_}); + tags_.push_back({context_.destination_canonical_revision_, context_.canonical_revision_}); + tags_.push_back({context_.destination_service_name_, service_host_name.empty() + ? context_.canonical_name_ + : pool_.add(service_host_name)}); + tags_.push_back({context_.destination_service_namespace_, context_.namespace_}); + tags_.push_back({context_.destination_cluster_, context_.cluster_name_}); + break; + } + + break; + } + case Reporter::ClientSidecar: { + tags_.push_back({context_.source_workload_, context_.workload_name_}); + tags_.push_back({context_.source_canonical_service_, context_.canonical_name_}); + tags_.push_back({context_.source_canonical_revision_, context_.canonical_revision_}); + tags_.push_back({context_.source_workload_namespace_, context_.namespace_}); + tags_.push_back({context_.source_principal_, + !local_san.empty() ? pool_.add(local_san) : context_.unknown_}); + tags_.push_back({context_.source_app_, context_.app_name_}); + tags_.push_back({context_.source_version_, context_.app_version_}); + tags_.push_back({context_.source_cluster_, context_.cluster_name_}); + tags_.push_back({context_.destination_workload_, peer && !peer->workload_name_.empty() + ? pool_.add(peer->workload_name_) + : context_.unknown_}); + tags_.push_back({context_.destination_workload_namespace_, + !peer_namespace.empty() ? pool_.add(peer_namespace) : context_.unknown_}); + tags_.push_back({context_.destination_principal_, + !peer_san.empty() ? pool_.add(peer_san) : context_.unknown_}); + tags_.push_back({context_.destination_app_, peer && !peer->app_name_.empty() + ? pool_.add(peer->app_name_) + : context_.unknown_}); + tags_.push_back({context_.destination_version_, peer && !peer->app_version_.empty() + ? pool_.add(peer->app_version_) + : context_.unknown_}); + tags_.push_back({context_.destination_service_, + service_host.empty() ? context_.unknown_ : pool_.add(service_host)}); + tags_.push_back({context_.destination_canonical_service_, + peer && !peer->canonical_name_.empty() ? pool_.add(peer->canonical_name_) + : context_.unknown_}); + tags_.push_back( + {context_.destination_canonical_revision_, peer && !peer->canonical_revision_.empty() + ? pool_.add(peer->canonical_revision_) + : context_.latest_}); + tags_.push_back({context_.destination_service_name_, service_host_name.empty() + ? context_.unknown_ + : pool_.add(service_host_name)}); + tags_.push_back( + {context_.destination_service_namespace_, + !service_namespace.empty() + ? pool_.add(service_namespace) + : (!peer_namespace.empty() ? pool_.add(peer_namespace) : context_.unknown_)}); + tags_.push_back({context_.destination_cluster_, peer && !peer->cluster_name_.empty() + ? pool_.add(peer->cluster_name_) + : context_.unknown_}); + break; + } + default: + break; + } + } + + ConfigSharedPtr config_; + Context& context_; + Stats::StatNameDynamicPool pool_; + Stats::StatNameTagVector tags_; + Event::TimerPtr report_timer_{nullptr}; + Network::ReadFilterCallbacks* network_read_callbacks_; + bool peer_read_{false}; + uint64_t bytes_sent_{0}; + uint64_t bytes_received_{0}; + absl::optional mutual_tls_; + bool is_grpc_{false}; + uint64_t request_message_count_{0}; + uint64_t response_message_count_{0}; + // Custom expression values are evaluated at most twice: at the start and the end of the stream. + Config::StreamOverrides stream_; +}; + +} // namespace + +absl::StatusOr IstioStatsFilterConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& proto_config, const std::string&, + Server::Configuration::FactoryContext& factory_context) { + factory_context.serverFactoryContext().api().customStatNamespaces().registerStatNamespace( + CustomStatNamespace); + ConfigSharedPtr config = std::make_shared( + dynamic_cast(proto_config), factory_context); + return [config](Http::FilterChainFactoryCallbacks& callbacks) { + auto filter = std::make_shared(config); + callbacks.addStreamFilter(filter); + // Wasm filters inject filter state in access log handlers, which are called + // after onStreamComplete. + callbacks.addAccessLogHandler(filter); + }; +} + +REGISTER_FACTORY(IstioStatsFilterConfigFactory, + Server::Configuration::NamedHttpFilterConfigFactory); + +absl::StatusOr +IstioStatsNetworkFilterConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& proto_config, Server::Configuration::FactoryContext& factory_context) { + factory_context.serverFactoryContext().api().customStatNamespaces().registerStatNamespace( + CustomStatNamespace); + ConfigSharedPtr config = std::make_shared( + dynamic_cast(proto_config), factory_context); + return [config](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(std::make_shared(config)); + }; +} + +REGISTER_FACTORY(IstioStatsNetworkFilterConfigFactory, + Server::Configuration::NamedNetworkFilterConfigFactory); + +} // namespace IstioStats +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/istio_stats/istio_stats.h b/source/extensions/filters/http/istio_stats/istio_stats.h new file mode 100644 index 00000000000..fe0af8ce831 --- /dev/null +++ b/source/extensions/filters/http/istio_stats/istio_stats.h @@ -0,0 +1,56 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "envoy/server/filter_config.h" +#include "envoy/stream_info/filter_state.h" +#include "source/extensions/filters/http/istio_stats/config.pb.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace IstioStats { + +class IstioStatsFilterConfigFactory : public Server::Configuration::NamedHttpFilterConfigFactory { +public: + std::string name() const override { return "envoy.filters.http.istio_stats"; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string&, + Server::Configuration::FactoryContext&) override; +}; + +class IstioStatsNetworkFilterConfigFactory + : public Server::Configuration::NamedNetworkFilterConfigFactory { +public: + std::string name() const override { return "envoy.filters.network.istio_stats"; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + Server::Configuration::FactoryContext& factory_context) override; +}; + +} // namespace IstioStats +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/peer_metadata/BUILD b/source/extensions/filters/http/peer_metadata/BUILD new file mode 100644 index 00000000000..3168fa62d53 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/BUILD @@ -0,0 +1,87 @@ +# Copyright 2018 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_benchmark_binary", + "envoy_cc_library", + "envoy_cc_test", + "envoy_proto_library", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "filter_lib", + srcs = ["filter.cc"], + hdrs = ["filter.h"], + repository = "@envoy", + deps = [ + ":config_cc_proto", + "//extensions/common:metadata_object_lib", + "//source/extensions/common/workload_discovery:api_lib", + "@envoy//envoy/registry", + "@envoy//source/common/common:base64_lib", + "@envoy//source/common/common:hash_lib", + "@envoy//source/common/http:header_utility_lib", + "@envoy//source/common/http:utility_lib", + "@envoy//source/common/network:utility_lib", + "@envoy//source/extensions/filters/common/expr:cel_state_lib", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_proto_library( + name = "config", + srcs = ["config.proto"], +) + +envoy_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + repository = "@envoy", + deps = [ + ":filter_lib", + "@envoy//source/common/network:address_lib", + "@envoy//test/common/stream_info:test_util", + "@envoy//test/mocks/server:factory_context_mocks", + "@envoy//test/mocks/stream_info:stream_info_mocks", + "@envoy//test/test_common:logging_lib", + ], +) + +envoy_cc_benchmark_binary( + name = "filter_state_benchmark", + srcs = ["filter_state_benchmark.cc"], + repository = "@envoy", + deps = [ + ":filter_lib", + "//extensions/common:metadata_object_lib", + "@envoy//source/common/formatter:formatter_extension_lib", + "@envoy//source/common/formatter:substitution_formatter_lib", + "@envoy//source/common/stream_info:stream_info_lib", + "@envoy//source/extensions/filters/common/expr:cel_state_lib", + "@envoy//source/extensions/formatter/cel:config", + "@envoy//test/common/stream_info:test_util", + "@envoy//test/mocks:common_lib", + "@envoy//test/mocks/server:factory_context_mocks", + "@envoy//test/test_common:utility_lib", + ], +) diff --git a/source/extensions/filters/http/peer_metadata/config.proto b/source/extensions/filters/http/peer_metadata/config.proto new file mode 100644 index 00000000000..22f7b036830 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/config.proto @@ -0,0 +1,95 @@ +/* Copyright Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package io.istio.http.peer_metadata; + +// Peer metadata provider filter. This filter encapsulates the discovery of the +// peer telemetry attributes for consumption by the telemetry filters. +message Config { + // This method uses `baggage` header encoding. Only used for HTTP CONNECT tunnels. + message Baggage { + } + + // This method uses the workload metadata xDS. Requires that the bootstrap extension is enabled. + // For downstream discovery, the remote address is the lookup key in xDS. + // For upstream discovery: + // + // * If the upstream host address is an IP, this IP is used as the lookup key; + // + // * If the upstream host address is internal, uses the + // "filter_metadata.tunnel.destination" dynamic metadata value as the lookup key. + message WorkloadDiscovery { + } + + // This method uses Istio HTTP metadata exchange headers, e.g. `x-envoy-peer-metadata`. Removes these headers if found. + message IstioHeaders { + // Strip x-envoy-peer-metadata and x-envoy-peer-metadata-id headers on HTTP requests to services outside the mesh. + // Detects upstream clusters with `istio` and `external` filter metadata fields + bool skip_external_clusters = 1; + } + + // This method extracts peer metadata from the upstream filter state if it's available. + // + // Upstream filter state could be populated by multiple means in general, but in practice the intention here is that + // upstream PeerMetadata filter will populate the filter state with peer details extracted from the baggage header + // sent in response. + // + // Naturally this metadata discovery method only makes sense for upstream peer metadata discovery. + message UpstreamFilterState { + // Upstream filter state key that will be used to store peer metadata. + string peer_metadata_key = 1; + } + + // An exhaustive list of the derivation methods. + message DiscoveryMethod { + oneof method_specifier { + Baggage baggage = 1; + WorkloadDiscovery workload_discovery = 2; + IstioHeaders istio_headers = 3; + UpstreamFilterState upstream_filter_state = 4; + } + } + + // The order of the derivation of the downstream peer metadata, in the precedence order. + // First successful lookup wins. + repeated DiscoveryMethod downstream_discovery = 1; + + // The order of the derivation of the upstream peer metadata, in the precedence order. + // First successful lookup wins. + repeated DiscoveryMethod upstream_discovery = 2; + + // An exhaustive list of the metadata propagation methods. + message PropagationMethod { + oneof method_specifier { + IstioHeaders istio_headers = 1; + Baggage baggage = 2; + } + } + + // Downstream injection of the metadata via a response header. + repeated PropagationMethod downstream_propagation = 3; + + // Upstream injection of the metadata via a request header. + repeated PropagationMethod upstream_propagation = 4; + + // True to enable sharing with the upstream. + bool shared_with_upstream = 5; + + // Additional labels to be added to the peer metadata to help your understand the traffic. + // e.g. `role`, `location` etc. + repeated string additional_labels = 6; +} diff --git a/source/extensions/filters/http/peer_metadata/filter.cc b/source/extensions/filters/http/peer_metadata/filter.cc new file mode 100644 index 00000000000..e952d542db1 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/filter.cc @@ -0,0 +1,511 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/extensions/filters/http/peer_metadata/filter.h" + +#include "envoy/registry/registry.h" +#include "envoy/server/factory_context.h" +#include "source/common/common/hash.h" +#include "source/common/common/base64.h" +#include "source/common/http/header_utility.h" +#include "source/common/http/utility.h" +#include "source/common/network/utility.h" + +#include "extensions/common/metadata_object.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace PeerMetadata { + +using ::Envoy::Extensions::Filters::Common::Expr::CelState; + +class XDSMethod : public DiscoveryMethod { +public: + XDSMethod(bool downstream, Server::Configuration::ServerFactoryContext& factory_context) + : downstream_(downstream), + metadata_provider_(Extensions::Common::WorkloadDiscovery::GetProvider(factory_context)), + local_info_(factory_context.localInfo()) {} + absl::optional derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap&, + Context&) const override; + +private: + const bool downstream_; + Extensions::Common::WorkloadDiscovery::WorkloadMetadataProviderSharedPtr metadata_provider_; + const LocalInfo::LocalInfo& local_info_; +}; + +absl::optional XDSMethod::derivePeerInfo(const StreamInfo::StreamInfo& info, + Http::HeaderMap& headers, Context&) const { + if (!metadata_provider_) { + return {}; + } + Network::Address::InstanceConstSharedPtr peer_address; + if (downstream_) { + const auto origin_network_header = headers.get(Headers::get().ExchangeMetadataOriginNetwork); + const auto& local_metadata = local_info_.node().metadata(); + const auto& it = local_metadata.fields().find("NETWORK"); + // We might not have a local network configured in the single cluster case, so default to empty. + auto local_network = it != local_metadata.fields().end() ? it->second.string_value() : ""; + if (!origin_network_header.empty() && + origin_network_header[0]->value().getStringView() != local_network) { + ENVOY_LOG_MISC(debug, + "Origin network header present: {}; skipping downstream workload discovery", + origin_network_header[0]->value().getStringView()); + peer_address = {}; + } else { + peer_address = info.downstreamAddressProvider().remoteAddress(); + } + } else { + if (info.upstreamInfo().has_value()) { + auto upstream_host = info.upstreamInfo().value().get().upstreamHost(); + if (upstream_host) { + const auto address = upstream_host->address(); + switch (address->type()) { + case Network::Address::Type::Ip: + peer_address = upstream_host->address(); + break; + case Network::Address::Type::EnvoyInternal: + if (upstream_host->metadata()) { + const auto& filter_metadata = upstream_host->metadata()->filter_metadata(); + const auto& istio_it = filter_metadata.find("istio"); + if (istio_it != filter_metadata.end()) { + const auto& double_hbone_it = istio_it->second.fields().find("double_hbone"); + // This is an E/W gateway endpoint, so we should explicitly not use workload discovery + if (double_hbone_it != istio_it->second.fields().end()) { + ENVOY_LOG_MISC( + debug, + "Skipping upstream workload discovery for an endpoint on a remote network"); + peer_address = nullptr; + break; + } + } else { + ENVOY_LOG_MISC(debug, "No istio metadata found on upstream host."); + } + const auto& it = filter_metadata.find("envoy.filters.listener.original_dst"); + if (it != filter_metadata.end()) { + const auto& destination_it = it->second.fields().find("local"); + if (destination_it != it->second.fields().end()) { + peer_address = Network::Utility::parseInternetAddressAndPortNoThrow( + destination_it->second.string_value(), /*v6only=*/false); + } + } + } + break; + default: + break; + } + } + } + } + if (!peer_address) { + return {}; + } + ENVOY_LOG_MISC(debug, "Peer address: {}", peer_address->asString()); + return metadata_provider_->GetMetadata(peer_address); +} + +MXMethod::MXMethod(bool downstream, const absl::flat_hash_set additional_labels, + Server::Configuration::ServerFactoryContext& factory_context) + : downstream_(downstream), tls_(factory_context.threadLocal()), + additional_labels_(additional_labels) { + tls_.set([](Event::Dispatcher&) { return std::make_shared(); }); +} + +absl::optional MXMethod::derivePeerInfo(const StreamInfo::StreamInfo&, + Http::HeaderMap& headers, Context& ctx) const { + const auto peer_id_header = headers.get(Headers::get().ExchangeMetadataHeaderId); + if (downstream_) { + ctx.request_peer_id_received_ = !peer_id_header.empty(); + } + absl::string_view peer_id = + peer_id_header.empty() ? "" : peer_id_header[0]->value().getStringView(); + const auto peer_info_header = headers.get(Headers::get().ExchangeMetadataHeader); + if (downstream_) { + ctx.request_peer_received_ = !peer_info_header.empty(); + } + absl::string_view peer_info = + peer_info_header.empty() ? "" : peer_info_header[0]->value().getStringView(); + if (!peer_info.empty()) { + return lookup(peer_id, peer_info); + } + return {}; +} + +void MXMethod::remove(Http::HeaderMap& headers) const { + headers.remove(Headers::get().ExchangeMetadataHeaderId); + headers.remove(Headers::get().ExchangeMetadataHeader); +} + +absl::optional MXMethod::lookup(absl::string_view id, absl::string_view value) const { + // This code is copied from: + // https://github.com/istio/proxy/blob/release-1.18/extensions/metadata_exchange/plugin.cc#L116 + auto& cache = tls_->cache_; + if (max_peer_cache_size_ > 0 && !id.empty()) { + auto it = cache.find(id); + if (it != cache.end()) { + return it->second; + } + } + const auto bytes = Base64::decodeWithoutPadding(value); + google::protobuf::Struct metadata; + if (!metadata.ParseFromString(bytes)) { + return {}; + } + auto out = Istio::Common::convertStructToWorkloadMetadata(metadata, additional_labels_); + if (max_peer_cache_size_ > 0 && !id.empty()) { + // do not let the cache grow beyond max cache size. + if (static_cast(cache.size()) > max_peer_cache_size_) { + cache.erase(cache.begin(), std::next(cache.begin(), max_peer_cache_size_ / 4)); + } + cache.emplace(id, *out); + } + return *out; +} + +class UpstreamFilterStateMethod : public DiscoveryMethod { +public: + UpstreamFilterStateMethod( + const io::istio::http::peer_metadata::Config_UpstreamFilterState& config) + : peer_metadata_key_(config.peer_metadata_key()) {} + absl::optional derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap&, + Context&) const override; + +private: + std::string peer_metadata_key_; +}; + +absl::optional +UpstreamFilterStateMethod::derivePeerInfo(const StreamInfo::StreamInfo& info, Http::HeaderMap&, + Context&) const { + const auto upstream = info.upstreamInfo(); + if (!upstream) { + return {}; + } + + const auto filter_state = upstream->upstreamFilterState(); + if (!filter_state) { + return {}; + } + + const auto* cel_state = + filter_state->getDataReadOnly( + peer_metadata_key_); + if (!cel_state) { + return {}; + } + + google::protobuf::Struct obj; + if (!obj.ParseFromString(absl::string_view(cel_state->value()))) { + return {}; + } + + std::unique_ptr peer_info = ::Istio::Common::convertStructToWorkloadMetadata(obj); + if (!peer_info) { + return {}; + } + + return *peer_info; +} + +MXPropagationMethod::MXPropagationMethod( + bool downstream, Server::Configuration::ServerFactoryContext& factory_context, + const absl::flat_hash_set& additional_labels, + const io::istio::http::peer_metadata::Config_IstioHeaders& istio_headers) + : downstream_(downstream), id_(factory_context.localInfo().node().id()), + value_(computeValue(additional_labels, factory_context)), + skip_external_clusters_(istio_headers.skip_external_clusters()) {} + +std::string MXPropagationMethod::computeValue( + const absl::flat_hash_set& additional_labels, + Server::Configuration::ServerFactoryContext& factory_context) const { + const auto obj = Istio::Common::convertStructToWorkloadMetadata( + factory_context.localInfo().node().metadata(), additional_labels, + factory_context.localInfo().node().locality()); + const google::protobuf::Struct metadata = Istio::Common::convertWorkloadMetadataToStruct(*obj); + const std::string metadata_bytes = Istio::Common::serializeToStringDeterministic(metadata); + return Base64::encode(metadata_bytes.data(), metadata_bytes.size()); +} + +void MXPropagationMethod::inject(const StreamInfo::StreamInfo& info, Http::HeaderMap& headers, + Context& ctx) const { + if (skipMXHeaders(skip_external_clusters_, info)) { + return; + } + if (!downstream_ || ctx.request_peer_id_received_) { + headers.setReference(Headers::get().ExchangeMetadataHeaderId, id_); + } + if (!downstream_ || ctx.request_peer_received_) { + headers.setReference(Headers::get().ExchangeMetadataHeader, value_); + } +} + +BaggagePropagationMethod::BaggagePropagationMethod( + Server::Configuration::ServerFactoryContext& factory_context, + const io::istio::http::peer_metadata::Config_Baggage&) + : value_(computeBaggageValue(factory_context)) {} + +std::string BaggagePropagationMethod::computeBaggageValue( + Server::Configuration::ServerFactoryContext& factory_context) const { + const auto obj = Istio::Common::convertStructToWorkloadMetadata( + factory_context.localInfo().node().metadata(), {}, + factory_context.localInfo().node().locality()); + return obj->baggage(); +} + +void BaggagePropagationMethod::inject(const StreamInfo::StreamInfo&, Http::HeaderMap& headers, + Context&) const { + headers.setReference(Headers::get().Baggage, value_); +} + +BaggageDiscoveryMethod::BaggageDiscoveryMethod() {} + +absl::optional BaggageDiscoveryMethod::derivePeerInfo(const StreamInfo::StreamInfo&, + Http::HeaderMap& headers, + Context&) const { + const auto baggage_header = headers.get(Headers::get().Baggage); + if (baggage_header.empty()) { + return {}; + } + const auto baggage_value = baggage_header[0]->value().getStringView(); + const auto workload = Istio::Common::convertBaggageToWorkloadMetadata(baggage_value); + if (workload) { + return *workload; + } + return {}; +} + +FilterConfig::FilterConfig(const io::istio::http::peer_metadata::Config& config, + Server::Configuration::FactoryContext& factory_context) + : shared_with_upstream_(config.shared_with_upstream()), + downstream_discovery_(buildDiscoveryMethods(config.downstream_discovery(), + buildAdditionalLabels(config.additional_labels()), + true, factory_context)), + upstream_discovery_(buildDiscoveryMethods(config.upstream_discovery(), + buildAdditionalLabels(config.additional_labels()), + false, factory_context)), + downstream_propagation_(buildPropagationMethods( + config.downstream_propagation(), buildAdditionalLabels(config.additional_labels()), true, + factory_context)), + upstream_propagation_(buildPropagationMethods( + config.upstream_propagation(), buildAdditionalLabels(config.additional_labels()), false, + factory_context)) {} + +std::vector FilterConfig::buildDiscoveryMethods( + const Protobuf::RepeatedPtrField& + config, + const absl::flat_hash_set& additional_labels, bool downstream, + Server::Configuration::FactoryContext& factory_context) const { + std::vector methods; + methods.reserve(config.size()); + for (const auto& method : config) { + switch (method.method_specifier_case()) { + case io::istio::http::peer_metadata::Config::DiscoveryMethod::MethodSpecifierCase:: + kWorkloadDiscovery: + methods.push_back( + std::make_unique(downstream, factory_context.serverFactoryContext())); + break; + case io::istio::http::peer_metadata::Config::DiscoveryMethod::MethodSpecifierCase:: + kIstioHeaders: + methods.push_back(std::make_unique(downstream, additional_labels, + factory_context.serverFactoryContext())); + break; + case io::istio::http::peer_metadata::Config::DiscoveryMethod::MethodSpecifierCase::kBaggage: + if (downstream) { + methods.push_back(std::make_unique()); + } else { + ENVOY_LOG(warn, "BaggageDiscovery peer metadata discovery option is only available for " + "downstream peer discovery"); + } + break; + case io::istio::http::peer_metadata::Config::DiscoveryMethod::MethodSpecifierCase:: + kUpstreamFilterState: + if (!downstream) { + methods.push_back( + std::make_unique(method.upstream_filter_state())); + } else { + ENVOY_LOG(warn, "UpstreamFilterState peer metadata discovery option is only available for " + "upstream peer discovery"); + } + break; + default: + break; + } + } + return methods; +} + +std::vector FilterConfig::buildPropagationMethods( + const Protobuf::RepeatedPtrField& + config, + const absl::flat_hash_set& additional_labels, bool downstream, + Server::Configuration::FactoryContext& factory_context) const { + std::vector methods; + methods.reserve(config.size()); + for (const auto& method : config) { + switch (method.method_specifier_case()) { + case io::istio::http::peer_metadata::Config::PropagationMethod::MethodSpecifierCase:: + kIstioHeaders: + methods.push_back( + std::make_unique(downstream, factory_context.serverFactoryContext(), + additional_labels, method.istio_headers())); + break; + case io::istio::http::peer_metadata::Config::PropagationMethod::MethodSpecifierCase::kBaggage: + methods.push_back(std::make_unique( + factory_context.serverFactoryContext(), method.baggage())); + break; + default: + break; + } + } + return methods; +} + +absl::flat_hash_set +FilterConfig::buildAdditionalLabels(const Protobuf::RepeatedPtrField& labels) const { + absl::flat_hash_set result; + for (const auto& label : labels) { + result.emplace(label); + } + return result; +} + +void FilterConfig::discoverDownstream(StreamInfo::StreamInfo& info, Http::RequestHeaderMap& headers, + Context& ctx) const { + discover(info, true, headers, ctx); +} + +void FilterConfig::discoverUpstream(StreamInfo::StreamInfo& info, Http::ResponseHeaderMap& headers, + Context& ctx) const { + discover(info, false, headers, ctx); +} + +void FilterConfig::discover(StreamInfo::StreamInfo& info, bool downstream, Http::HeaderMap& headers, + Context& ctx) const { + for (const auto& method : downstream ? downstream_discovery_ : upstream_discovery_) { + const auto result = method->derivePeerInfo(info, headers, ctx); + if (result) { + setFilterState(info, downstream, *result); + break; + } + } + for (const auto& method : downstream ? downstream_discovery_ : upstream_discovery_) { + method->remove(headers); + } +} + +void FilterConfig::injectDownstream(const StreamInfo::StreamInfo& info, + Http::ResponseHeaderMap& headers, Context& ctx) const { + for (const auto& method : downstream_propagation_) { + method->inject(info, headers, ctx); + } +} + +void FilterConfig::injectUpstream(const StreamInfo::StreamInfo& info, + Http::RequestHeaderMap& headers, Context& ctx) const { + for (const auto& method : upstream_propagation_) { + method->inject(info, headers, ctx); + } +} + +void FilterConfig::setFilterState(StreamInfo::StreamInfo& info, bool downstream, + const PeerInfo& value) const { + const absl::string_view key = + downstream ? Istio::Common::DownstreamPeer : Istio::Common::UpstreamPeer; + const absl::string_view obj_key = + downstream ? Istio::Common::DownstreamPeerObj : Istio::Common::UpstreamPeerObj; + if (!info.filterState()->hasDataWithName(key)) { + // Store CelState for CEL expressions like filter_state.downstream_peer.labels['role'] + auto pb = value.serializeAsProto(); + auto cel_state = std::make_unique(FilterConfig::peerInfoPrototype()); + cel_state->setValue(absl::string_view(pb->SerializeAsString())); + info.filterState()->setData( + key, std::move(cel_state), StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::FilterChain, sharedWithUpstream()); + + // Also store WorkloadMetadataObject under a separate key for FIELD accessor support. + // WorkloadMetadataObject implements hasFieldSupport() + getField() for + // formatters using %FILTER_STATE(downstream_peer_obj:FIELD:fieldname)% syntax. + auto workload_metadata = std::make_unique(value); + info.filterState()->setData( + obj_key, std::move(workload_metadata), StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::FilterChain, sharedWithUpstream()); + } else { + ENVOY_LOG(debug, "Duplicate peer metadata, skipping"); + } +} + +Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { + config_->discoverDownstream(decoder_callbacks_->streamInfo(), headers, ctx_); + config_->injectUpstream(decoder_callbacks_->streamInfo(), headers, ctx_); + return Http::FilterHeadersStatus::Continue; +} + +bool MXPropagationMethod::skipMXHeaders(const bool skip_external_clusters, + const StreamInfo::StreamInfo& info) const { + // We skip metadata in two cases. + // 1. skip_external_clusters is enabled, and we detect the upstream as external. + const auto cluster_info = info.upstreamClusterInfo(); + if (cluster_info) { + const auto& cluster_name = cluster_info->name(); + // PassthroughCluster is always considered external + if (skip_external_clusters && cluster_name == "PassthroughCluster") { + return true; + } + const auto& filter_metadata = cluster_info->metadata().filter_metadata(); + const auto& it = filter_metadata.find("istio"); + // Otherwise, cluster must be tagged as external + if (it != filter_metadata.end()) { + if (skip_external_clusters) { + const auto& skip_mx = it->second.fields().find("external"); + if (skip_mx != it->second.fields().end()) { + if (skip_mx->second.bool_value()) { + return true; + } + } + } + const auto& skip_mx = it->second.fields().find("disable_mx"); + if (skip_mx != it->second.fields().end()) { + if (skip_mx->second.bool_value()) { + return true; + } + } + } + } + return false; +} + +Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + config_->discoverUpstream(decoder_callbacks_->streamInfo(), headers, ctx_); + config_->injectDownstream(decoder_callbacks_->streamInfo(), headers, ctx_); + return Http::FilterHeadersStatus::Continue; +} + +absl::StatusOr FilterConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& config, const std::string&, + Server::Configuration::FactoryContext& factory_context) { + auto filter_config = std::make_shared( + dynamic_cast(config), factory_context); + return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) { + auto filter = std::make_shared(filter_config); + callbacks.addStreamFilter(filter); + }; +} + +REGISTER_FACTORY(FilterConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace PeerMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/peer_metadata/filter.h b/source/extensions/filters/http/peer_metadata/filter.h new file mode 100644 index 00000000000..4a04f76a8d1 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/filter.h @@ -0,0 +1,193 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "source/extensions/filters/common/expr/cel_state.h" +#include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include "source/extensions/filters/http/peer_metadata/config.pb.h" +#include "source/extensions/common/workload_discovery/api.h" +#include "source/common/singleton/const_singleton.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace PeerMetadata { + +using ::Envoy::Extensions::Filters::Common::Expr::CelStatePrototype; +using ::Envoy::Extensions::Filters::Common::Expr::CelStateType; + +struct HeaderValues { + const Http::LowerCaseString Baggage{"baggage"}; + const Http::LowerCaseString ExchangeMetadataHeader{"x-envoy-peer-metadata"}; + const Http::LowerCaseString ExchangeMetadataHeaderId{"x-envoy-peer-metadata-id"}; + const Http::LowerCaseString ExchangeMetadataOriginNetwork{"x-forwarded-network"}; +}; + +using Headers = ConstSingleton; + +using PeerInfo = Istio::Common::WorkloadMetadataObject; + +struct Context { + bool request_peer_id_received_{false}; + bool request_peer_received_{false}; +}; + +// Base class for the discovery methods. First derivation wins but all methods perform removal. +class DiscoveryMethod { +public: + virtual ~DiscoveryMethod() = default; + virtual absl::optional derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap&, + Context&) const PURE; + virtual void remove(Http::HeaderMap&) const {} +}; + +using DiscoveryMethodPtr = std::unique_ptr; + +class MXMethod : public DiscoveryMethod { +public: + MXMethod(bool downstream, const absl::flat_hash_set additional_labels, + Server::Configuration::ServerFactoryContext& factory_context); + absl::optional derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap&, + Context&) const override; + void remove(Http::HeaderMap&) const override; + +private: + absl::optional lookup(absl::string_view id, absl::string_view value) const; + const bool downstream_; + struct MXCache : public ThreadLocal::ThreadLocalObject { + absl::flat_hash_map cache_; + }; + mutable ThreadLocal::TypedSlot tls_; + const absl::flat_hash_set additional_labels_; + const int64_t max_peer_cache_size_{500}; +}; + +// Base class for the propagation methods. +class PropagationMethod { +public: + virtual ~PropagationMethod() = default; + virtual void inject(const StreamInfo::StreamInfo&, Http::HeaderMap&, Context&) const PURE; +}; + +using PropagationMethodPtr = std::unique_ptr; + +class MXPropagationMethod : public PropagationMethod { +public: + MXPropagationMethod(bool downstream, Server::Configuration::ServerFactoryContext& factory_context, + const absl::flat_hash_set& additional_labels, + const io::istio::http::peer_metadata::Config_IstioHeaders&); + void inject(const StreamInfo::StreamInfo&, Http::HeaderMap&, Context&) const override; + +private: + const bool downstream_; + std::string computeValue(const absl::flat_hash_set&, + Server::Configuration::ServerFactoryContext&) const; + const std::string id_; + const std::string value_; + const bool skip_external_clusters_; + bool skipMXHeaders(const bool, const StreamInfo::StreamInfo&) const; +}; + +class BaggagePropagationMethod : public PropagationMethod { +public: + BaggagePropagationMethod(Server::Configuration::ServerFactoryContext& factory_context, + const io::istio::http::peer_metadata::Config_Baggage&); + void inject(const StreamInfo::StreamInfo&, Http::HeaderMap&, Context&) const override; + +private: + std::string computeBaggageValue(Server::Configuration::ServerFactoryContext&) const; + const std::string value_; +}; + +class BaggageDiscoveryMethod : public DiscoveryMethod, public Logger::Loggable { +public: + BaggageDiscoveryMethod(); + absl::optional derivePeerInfo(const StreamInfo::StreamInfo&, Http::HeaderMap&, + Context&) const override; +}; + +class FilterConfig : public Logger::Loggable { +public: + FilterConfig(const io::istio::http::peer_metadata::Config&, + Server::Configuration::FactoryContext&); + void discoverDownstream(StreamInfo::StreamInfo&, Http::RequestHeaderMap&, Context&) const; + void discoverUpstream(StreamInfo::StreamInfo&, Http::ResponseHeaderMap&, Context&) const; + void injectDownstream(const StreamInfo::StreamInfo&, Http::ResponseHeaderMap&, Context&) const; + void injectUpstream(const StreamInfo::StreamInfo&, Http::RequestHeaderMap&, Context&) const; + + static const CelStatePrototype& peerInfoPrototype() { + static const CelStatePrototype* const prototype = new CelStatePrototype( + true, CelStateType::Protobuf, "type.googleapis.com/google.protobuf.Struct", + StreamInfo::FilterState::LifeSpan::FilterChain); + return *prototype; + } + +private: + std::vector buildDiscoveryMethods( + const Protobuf::RepeatedPtrField&, + const absl::flat_hash_set& additional_labels, bool downstream, + Server::Configuration::FactoryContext&) const; + std::vector buildPropagationMethods( + const Protobuf::RepeatedPtrField&, + const absl::flat_hash_set& additional_labels, bool downstream, + Server::Configuration::FactoryContext&) const; + absl::flat_hash_set + buildAdditionalLabels(const Protobuf::RepeatedPtrField&) const; + StreamInfo::StreamSharingMayImpactPooling sharedWithUpstream() const { + return shared_with_upstream_ + ? StreamInfo::StreamSharingMayImpactPooling::SharedWithUpstreamConnectionOnce + : StreamInfo::StreamSharingMayImpactPooling::None; + } + void discover(StreamInfo::StreamInfo&, bool downstream, Http::HeaderMap&, Context&) const; + void setFilterState(StreamInfo::StreamInfo&, bool downstream, const PeerInfo& value) const; + const bool shared_with_upstream_; + const std::vector downstream_discovery_; + const std::vector upstream_discovery_; + const std::vector downstream_propagation_; + const std::vector upstream_propagation_; +}; + +using FilterConfigSharedPtr = std::shared_ptr; + +class Filter : public Http::PassThroughFilter, public Logger::Loggable { +public: + Filter(const FilterConfigSharedPtr& config) : config_(config) {} + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override; + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap&, bool) override; + +private: + FilterConfigSharedPtr config_; + Context ctx_; +}; + +class FilterConfigFactory : public Server::Configuration::NamedHttpFilterConfigFactory { +public: + std::string name() const override { return "envoy.filters.http.peer_metadata"; } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string&, + Server::Configuration::FactoryContext&) override; +}; + +} // namespace PeerMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/peer_metadata/filter_state_benchmark.cc b/source/extensions/filters/http/peer_metadata/filter_state_benchmark.cc new file mode 100644 index 00000000000..f006f8e20c7 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/filter_state_benchmark.cc @@ -0,0 +1,175 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/extensions/filters/http/peer_metadata/filter.h" + +#include "source/common/formatter/substitution_formatter.h" +#include "source/extensions/filters/common/expr/cel_state.h" +#include "extensions/common/metadata_object.h" + +#include "test/common/stream_info/test_util.h" +#include "test/mocks/common.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "benchmark/benchmark.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace PeerMetadata { + +namespace { + +// Helper to create a WorkloadMetadataObject with realistic test data +std::unique_ptr makeWorkloadMetadata() { + return std::make_unique( + "sleep-v1-12345-abcde", // instance_name + "cluster1", // cluster_name + "default", // namespace_name + "sleep-v1", // workload_name + "sleep", // canonical_name + "v1", // canonical_revision + "sleep", // app_name + "v1", // app_version + Istio::Common::WorkloadType::Pod, // workload_type + "spiffe://cluster.local/ns/default/sa/sleep", // identity + "us-west1", // region + "us-west1-a" // zone + ); +} + +// Setup stream info with filter state for CEL access +void setupCelFilterState(Envoy::StreamInfo::StreamInfo& stream_info) { + auto metadata = makeWorkloadMetadata(); + auto proto = metadata->serializeAsProto(); + + // CEL access requires CelState wrapper under "downstream_peer" key + auto cel_state = + std::make_unique(FilterConfig::peerInfoPrototype()); + cel_state->setValue(absl::string_view(proto->SerializeAsString())); + + stream_info.filterState()->setData( + std::string(Istio::Common::DownstreamPeer), std::move(cel_state), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); +} + +// Setup stream info with filter state for FIELD access +void setupFieldFilterState(Envoy::StreamInfo::StreamInfo& stream_info) { + auto metadata = makeWorkloadMetadata(); + + // FIELD access uses WorkloadMetadataObject under "downstream_peer_obj" key + stream_info.filterState()->setData( + std::string(Istio::Common::DownstreamPeerObj), std::move(metadata), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::FilterChain); +} + +} // namespace + +// Benchmark CEL accessor for filter_state.downstream_peer.workload +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_FilterState_CEL(benchmark::State& state) { + testing::NiceMock time_system; + NiceMock context; + ScopedThreadLocalServerContextSetter server_context_setter(context.server_factory_context_); + + Envoy::TestStreamInfo stream_info(time_system); + + setupCelFilterState(stream_info); + + // CEL format: %CEL(filter_state.downstream_peer.workload)% + const std::string format = "%CEL(filter_state.downstream_peer.workload)%"; + auto formatter = *Formatter::FormatterImpl::create(format, false); + + Formatter::Context formatter_context; + size_t total_bytes_allocated = 0; + + for (auto _ : state) { // NOLINT + std::string result = formatter->format(formatter_context, stream_info); + // Count string allocation: capacity is usually result.size() rounded up to power of 2 + // For small strings like "sleep-v1", this is typically 16-32 bytes + total_bytes_allocated += result.capacity(); + benchmark::DoNotOptimize(result); + } + + // Report memory allocated per iteration + state.SetBytesProcessed(total_bytes_allocated); + state.SetLabel("alloc_per_iter=" + std::to_string(total_bytes_allocated / state.iterations()) + + "B"); +} +BENCHMARK(BM_FilterState_CEL); + +// Benchmark FIELD accessor for filter_state downstream_peer workload +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_FilterState_FIELD(benchmark::State& state) { + testing::NiceMock time_system; + NiceMock context; + ScopedThreadLocalServerContextSetter server_context_setter(context.server_factory_context_); + + Envoy::TestStreamInfo stream_info(time_system); + + setupFieldFilterState(stream_info); + + // FIELD format: %FILTER_STATE(downstream_peer_obj:FIELD:workload)% + const std::string format = "%FILTER_STATE(downstream_peer_obj:FIELD:workload)%"; + auto formatter = *Formatter::FormatterImpl::create(format, false); + + Formatter::Context formatter_context; + size_t total_bytes_allocated = 0; + + for (auto _ : state) { // NOLINT + std::string result = formatter->format(formatter_context, stream_info); + total_bytes_allocated += result.capacity(); + benchmark::DoNotOptimize(result); + } + + state.SetBytesProcessed(total_bytes_allocated); + state.SetLabel("alloc_per_iter=" + std::to_string(total_bytes_allocated / state.iterations()) + + "B"); +} +BENCHMARK(BM_FilterState_FIELD); + +// Benchmark baseline - accessing filter state directly without formatter +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_FilterState_Direct(benchmark::State& state) { + testing::NiceMock time_system; + NiceMock context; + ScopedThreadLocalServerContextSetter server_context_setter(context.server_factory_context_); + + Envoy::TestStreamInfo stream_info(time_system); + + setupFieldFilterState(stream_info); + + size_t total_bytes_read = 0; + + for (auto _ : state) { // NOLINT + const auto* obj = + stream_info.filterState()->getDataReadOnly( + std::string(Istio::Common::DownstreamPeerObj)); + if (obj) { + // Direct access doesn't allocate - just reads the string_view + total_bytes_read += obj->workload_name_.length(); + } + } + + state.SetBytesProcessed(total_bytes_read); + state.SetLabel("alloc_per_iter=0B (no allocation, direct access)"); + benchmark::DoNotOptimize(total_bytes_read); +} +BENCHMARK(BM_FilterState_Direct); + +} // namespace PeerMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/peer_metadata/filter_test.cc b/source/extensions/filters/http/peer_metadata/filter_test.cc new file mode 100644 index 00000000000..fb0087c7a26 --- /dev/null +++ b/source/extensions/filters/http/peer_metadata/filter_test.cc @@ -0,0 +1,1398 @@ +// Copyright Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/extensions/filters/http/peer_metadata/filter.h" + +#include "source/common/network/address_impl.h" +#include "source/common/common/base64.h" +#include "test/common/stream_info/test_util.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::Envoy::Base64; +using Istio::Common::WorkloadMetadataObject; +using testing::HasSubstr; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace PeerMetadata { +namespace { + +class MockSingletonManager : public Singleton::Manager { +public: + MockSingletonManager() {} + ~MockSingletonManager() override {} + MOCK_METHOD(Singleton::InstanceSharedPtr, get, + (const std::string& name, Singleton::SingletonFactoryCb cb, bool pin)); +}; +class MockWorkloadMetadataProvider + : public Extensions::Common::WorkloadDiscovery::WorkloadMetadataProvider, + public Singleton::Instance { +public: + MockWorkloadMetadataProvider() {} + ~MockWorkloadMetadataProvider() override {} + MOCK_METHOD(std::optional, GetMetadata, + (const Network::Address::InstanceConstSharedPtr& address)); +}; + +class PeerMetadataTest : public testing::Test { +protected: + PeerMetadataTest() { + ON_CALL(context_.server_factory_context_, singletonManager()) + .WillByDefault(ReturnRef(singleton_manager_)); + metadata_provider_ = std::make_shared>(); + ON_CALL(singleton_manager_, get(HasSubstr("workload_metadata_provider"), _, _)) + .WillByDefault(Return(metadata_provider_)); + } + void initialize(const std::string& yaml_config) { + TestUtility::loadFromYaml(yaml_config, config_); + FilterConfigFactory factory; + Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config_, "", context_).value(); + Http::MockFilterChainFactoryCallbacks filter_callback; + ON_CALL(filter_callback, addStreamFilter(_)).WillByDefault(testing::SaveArg<0>(&filter_)); + EXPECT_CALL(filter_callback, addStreamFilter(_)); + cb(filter_callback); + ON_CALL(decoder_callbacks_, streamInfo()).WillByDefault(testing::ReturnRef(stream_info_)); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers_, true)); + } + void checkNoPeer(bool downstream) { + EXPECT_FALSE(stream_info_.filterState()->hasDataWithName( + downstream ? Istio::Common::DownstreamPeer : Istio::Common::UpstreamPeer)); + } + void checkPeerNamespace(bool downstream, const std::string& expected) { + const auto* peer_info = stream_info_.filterState()->getDataReadOnly( + downstream ? Istio::Common::DownstreamPeerObj : Istio::Common::UpstreamPeerObj); + ASSERT_NE(peer_info, nullptr); + EXPECT_EQ(expected, peer_info->namespace_name_); + } + void checkPeerLocality(bool downstream, const std::string& expected_region, + const std::string& expected_zone) { + const auto* peer_info = stream_info_.filterState()->getDataReadOnly( + downstream ? Istio::Common::DownstreamPeerObj : Istio::Common::UpstreamPeerObj); + ASSERT_NE(peer_info, nullptr); + EXPECT_EQ(expected_region, peer_info->locality_region_); + EXPECT_EQ(expected_zone, peer_info->locality_zone_); + } + + absl::string_view extractString(const Protobuf::Struct& metadata, absl::string_view key) { + const auto& it = metadata.fields().find(key); + if (it == metadata.fields().end()) { + return {}; + } + return it->second.string_value(); + } + + void checkShared(bool expected) { + EXPECT_EQ(expected, + stream_info_.filterState()->objectsSharedWithUpstreamConnection()->size() > 0); + } + NiceMock context_; + NiceMock singleton_manager_; + std::shared_ptr> metadata_provider_; + NiceMock stream_info_; + NiceMock decoder_callbacks_; + Http::TestRequestHeaderMapImpl request_headers_; + Http::TestResponseHeaderMapImpl response_headers_; + io::istio::http::peer_metadata::Config config_; + Http::StreamFilterSharedPtr filter_; +}; + +TEST_F(PeerMetadataTest, None) { + initialize("{}"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamXDSNone) { + EXPECT_CALL(*metadata_provider_, GetMetadata(_)).WillRepeatedly(Return(std::nullopt)); + initialize(R"EOF( + downstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamXDS) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "default"); + checkNoPeer(false); + checkShared(false); +} + +TEST_F(PeerMetadataTest, DownstreamXDSCrossNetwork) { + request_headers_.setReference(Headers::get().ExchangeMetadataOriginNetwork, "remote-network"); + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); // We don't remove the header because we terminate the + // tunnel that delivered it + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); // No downstream peer because it's a cross-network request + checkNoPeer(false); + checkShared(false); +} + +TEST_F(PeerMetadataTest, UpstreamXDS) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "10.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "foo"); +} + +TEST_F(PeerMetadataTest, UpstreamXDSInternal) { + Network::Address::InstanceConstSharedPtr upstream_address = + std::make_shared("internal_address", "endpoint_id"); + std::shared_ptr> upstream_host( + new NiceMock()); + EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); + stream_info_.upstreamInfo()->setUpstreamHost(upstream_host); + auto host_metadata = std::make_shared(); + ON_CALL(*upstream_host, metadata()).WillByDefault(testing::Return(host_metadata)); + TestUtility::loadFromYaml(R"EOF( + filter_metadata: + envoy.filters.listener.original_dst: + local: 127.0.0.100:80 + )EOF", + *host_metadata); + + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.100")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "foo"); +} + +TEST_F(PeerMetadataTest, UpstreamXDSInternalCrossNetwork) { + Network::Address::InstanceConstSharedPtr upstream_address = + std::make_shared("internal_address", "endpoint_id"); + std::shared_ptr> upstream_host( + new NiceMock()); + EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); + stream_info_.upstreamInfo()->setUpstreamHost(upstream_host); + auto host_metadata = std::make_shared(); + ON_CALL(*upstream_host, metadata()).WillByDefault(testing::Return(host_metadata)); + TestUtility::loadFromYaml(R"EOF( + filter_metadata: + envoy.filters.listener.original_dst: + local: 127.0.0.100:80 + istio: + double_hbone: + hbone_target_address: 10.0.0.1 + )EOF", + *host_metadata); + + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.100")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); // Shouldn't be any upstream filter state since it's a cross-network endpoint +} + +TEST_F(PeerMetadataTest, DownstreamMXEmpty) { + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +constexpr absl::string_view SampleIstioHeader = + "ChIKBWlzdGlvEgkaB3NpZGVjYXIKDgoIU1RTX1BPUlQSAhoAChEKB01FU0hfSUQSBhoEbWVzaAocChZTVEFDS0RSSVZFUl" + "9UT0tFTl9GSUxFEgIaAAowCihTVEFDS0RSSVZFUl9MT0dHSU5HX0VYUE9SVF9JTlRFUlZBTF9TRUNTEgQaAjIwCjYKDElO" + "U1RBTkNFX0lQUxImGiQxMC41Mi4wLjM0LGZlODA6OmEwNzU6MTFmZjpmZTVlOmYxY2QKFAoDYXBwEg0aC3Byb2R1Y3RwYW" + "dlCisKG1NFQ1VSRV9TVEFDS0RSSVZFUl9FTkRQT0lOVBIMGgpsb2NhbGhvc3Q6Cl0KGmt1YmVybmV0ZXMuaW8vbGltaXQt" + "cmFuZ2VyEj8aPUxpbWl0UmFuZ2VyIHBsdWdpbiBzZXQ6IGNwdSByZXF1ZXN0IGZvciBjb250YWluZXIgcHJvZHVjdHBhZ2" + "UKIQoNV09SS0xPQURfTkFNRRIQGg5wcm9kdWN0cGFnZS12MQofChFJTlRFUkNFUFRJT05fTU9ERRIKGghSRURJUkVDVAoe" + "CgpDTFVTVEVSX0lEEhAaDmNsaWVudC1jbHVzdGVyCkkKD0lTVElPX1BST1hZX1NIQRI2GjRpc3Rpby1wcm94eTo0N2U0NT" + "U5YjhlNGYwZDUxNmMwZDE3YjIzM2QxMjdhM2RlYjNkN2NlClIKBU9XTkVSEkkaR2t1YmVybmV0ZXM6Ly9hcGlzL2FwcHMv" + "djEvbmFtZXNwYWNlcy9kZWZhdWx0L2RlcGxveW1lbnRzL3Byb2R1Y3RwYWdlLXYxCsEBCgZMQUJFTFMStgEqswEKFAoDYX" + "BwEg0aC3Byb2R1Y3RwYWdlCiEKEXBvZC10ZW1wbGF0ZS1oYXNoEgwaCjg0OTc1YmM3NzgKMwofc2VydmljZS5pc3Rpby5p" + "by9jYW5vbmljYWwtbmFtZRIQGg5wcm9kdWN0cGFnZS12MQoyCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2" + "lvbhILGgl2ZXJzaW9uLTEKDwoHdmVyc2lvbhIEGgJ2MQopCgROQU1FEiEaH3Byb2R1Y3RwYWdlLXYxLTg0OTc1YmM3Nzgt" + "cHh6MncKLQoIUE9EX05BTUUSIRofcHJvZHVjdHBhZ2UtdjEtODQ5NzViYzc3OC1weHoydwoaCg1JU1RJT19WRVJTSU9OEg" + "kaBzEuNS1kZXYKHwoVSU5DTFVERV9JTkJPVU5EX1BPUlRTEgYaBDkwODAKmwEKEVBMQVRGT1JNX01FVEFEQVRBEoUBKoIB" + "CiYKFGdjcF9na2VfY2x1c3Rlcl9uYW1lEg4aDHRlc3QtY2x1c3RlcgocCgxnY3BfbG9jYXRpb24SDBoKdXMtZWFzdDQtYg" + "odCgtnY3BfcHJvamVjdBIOGgx0ZXN0LXByb2plY3QKGwoSZ2NwX3Byb2plY3RfbnVtYmVyEgUaAzEyMwopCg9TRVJWSUNF" + "X0FDQ09VTlQSFhoUYm9va2luZm8tcHJvZHVjdHBhZ2UKHQoQQ09ORklHX05BTUVTUEFDRRIJGgdkZWZhdWx0Cg8KB3Zlcn" + "Npb24SBBoCdjEKHgoYU1RBQ0tEUklWRVJfUk9PVF9DQV9GSUxFEgIaAAohChFwb2QtdGVtcGxhdGUtaGFzaBIMGgo4NDk3" + "NWJjNzc4Ch8KDkFQUF9DT05UQUlORVJTEg0aC3Rlc3QsYm9uemFpChYKCU5BTUVTUEFDRRIJGgdkZWZhdWx0CjMKK1NUQU" + "NLRFJJVkVSX01PTklUT1JJTkdfRVhQT1JUX0lOVEVSVkFMX1NFQ1MSBBoCMjA"; + +TEST_F(PeerMetadataTest, DownstreamFallbackFirst) { + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)).Times(0); + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "default"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamFallbackSecond) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { // remote address + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "default"); + checkNoPeer(false); +} + +TEST(MXMethod, Cache) { + NiceMock context; + absl::flat_hash_set additional_labels; + MXMethod method(true, additional_labels, context); + NiceMock stream_info; + Http::TestRequestHeaderMapImpl request_headers; + const int32_t max = 1000; + for (int32_t run = 0; run < 3; run++) { + for (int32_t i = 0; i < max; i++) { + std::string id = absl::StrCat("test-", i); + request_headers.setReference(Headers::get().ExchangeMetadataHeaderId, id); + request_headers.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + Context ctx; + const auto result = method.derivePeerInfo(stream_info, request_headers, ctx); + EXPECT_TRUE(result.has_value()); + } + } +} + +TEST_F(PeerMetadataTest, DownstreamMX) { + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "default"); + checkNoPeer(false); + checkShared(false); +} + +TEST_F(PeerMetadataTest, UpstreamMX) { + response_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + response_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + upstream_discovery: + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "default"); +} + +TEST_F(PeerMetadataTest, UpstreamFallbackFirst) { + EXPECT_CALL(*metadata_provider_, GetMetadata(_)).Times(0); + response_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + response_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + upstream_discovery: + - istio_headers: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "default"); +} + +TEST_F(PeerMetadataTest, UpstreamFallbackSecond) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "10.0.0.1")) { // upstream host address + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - istio_headers: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "foo"); +} + +TEST_F(PeerMetadataTest, UpstreamFallbackFirstXDS) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "foo", "foo", "foo-service", + "v1alpha3", "", "", Istio::Common::WorkloadType::Pod, "", "", + ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "10.0.0.1")) { // upstream host address + return {pod}; + } + return {}; + })); + response_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + response_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + upstream_discovery: + - workload_discovery: {} + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "foo"); +} + +TEST_F(PeerMetadataTest, DownstreamMXPropagation) { + initialize(R"EOF( + downstream_propagation: + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamMXPropagationWithAdditionalLabels) { + initialize(R"EOF( + downstream_propagation: + - istio_headers: {} + additional_labels: + - foo + - bar + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamMXDiscoveryPropagation) { + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + downstream_propagation: + - istio_headers: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(2, response_headers_.size()); + checkPeerNamespace(true, "default"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamMXPropagation) { + initialize(R"EOF( + upstream_propagation: + - istio_headers: + skip_external_clusters: false + )EOF"); + EXPECT_EQ(2, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamMXPropagationSkipNoMatch) { + initialize(R"EOF( + upstream_propagation: + - istio_headers: + skip_external_clusters: true + )EOF"); + EXPECT_EQ(2, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +// Test MX propagation includes locality from node configuration +TEST_F(PeerMetadataTest, UpstreamMXPropagationWithNodeLocality) { + // Setup node with locality + auto& node = context_.server_factory_context_.local_info_.node_; + TestUtility::loadFromYaml(R"EOF( + metadata: + NAMESPACE: mx-prop-ns + CLUSTER_ID: mx-prop-cluster + WORKLOAD_NAME: mx-prop-workload + locality: + region: asia-southeast1 + zone: asia-southeast1-a + )EOF", + node); + + initialize(R"EOF( + upstream_propagation: + - istio_headers: + skip_external_clusters: false + )EOF"); + + EXPECT_EQ(2, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + + // Verify the MX header contains locality information + const auto mx_header = request_headers_.get(Headers::get().ExchangeMetadataHeader); + ASSERT_FALSE(mx_header.empty()); + + // Decode and verify the metadata contains locality + const std::string encoded_metadata = std::string(mx_header[0]->value().getStringView()); + const std::string metadata_bytes = Base64::decode(encoded_metadata); + + google::protobuf::Struct metadata; + ASSERT_TRUE(metadata.ParseFromString(metadata_bytes)); + // Check that locality fields are present + ASSERT_TRUE(metadata.fields().contains("REGION")); + EXPECT_EQ("asia-southeast1", metadata.fields().at("REGION").string_value()); + ASSERT_TRUE(metadata.fields().contains("AVAILABILITY_ZONE")); + EXPECT_EQ("asia-southeast1-a", metadata.fields().at("AVAILABILITY_ZONE").string_value()); + + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamMXPropagationSkip) { + std::shared_ptr cluster_info_{ + std::make_shared>()}; + auto metadata = TestUtility::parseYaml(R"EOF( + filter_metadata: + istio: + external: true + )EOF"); + stream_info_.upstream_cluster_info_ = cluster_info_; + ON_CALL(*cluster_info_, metadata()).WillByDefault(ReturnRef(metadata)); + initialize(R"EOF( + upstream_propagation: + - istio_headers: + skip_external_clusters: true + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamMXPropagationSkipPassthrough) { + std::shared_ptr cluster_info_{ + std::make_shared>()}; + cluster_info_->name_ = "PassthroughCluster"; + stream_info_.upstream_cluster_info_ = cluster_info_; + initialize(R"EOF( + upstream_propagation: + - istio_headers: + skip_external_clusters: true + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, FieldAccessorSupport) { + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "default", "foo", "foo-service", + "v1alpha3", "myapp", "v1", Istio::Common::WorkloadType::Pod, "", + "test-region", "test-zone-a"); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - workload_discovery: {} + )EOF"); + + const auto* peer_info = + stream_info_.filterState()->getDataReadOnly(Istio::Common::DownstreamPeerObj); + ASSERT_NE(peer_info, nullptr); + + // Test hasFieldSupport + EXPECT_TRUE(peer_info->hasFieldSupport()); + + // Test getField() for all fields including locality + EXPECT_EQ("foo", std::get(peer_info->getField("workload"))); + EXPECT_EQ("default", std::get(peer_info->getField("namespace"))); + EXPECT_EQ("my-cluster", std::get(peer_info->getField("cluster"))); + EXPECT_EQ("foo-service", std::get(peer_info->getField("service"))); + EXPECT_EQ("v1alpha3", std::get(peer_info->getField("revision"))); + EXPECT_EQ("myapp", std::get(peer_info->getField("app"))); + EXPECT_EQ("v1", std::get(peer_info->getField("version"))); + EXPECT_EQ("pod", std::get(peer_info->getField("type"))); + EXPECT_EQ("pod-foo-1234", std::get(peer_info->getField("name"))); + // Test locality field access + EXPECT_EQ("test-region", std::get(peer_info->getField("region"))); + EXPECT_EQ("test-zone-a", std::get(peer_info->getField("availability_zone"))); +} + +TEST_F(PeerMetadataTest, CelExpressionCompatibility) { + const WorkloadMetadataObject pod("pod-bar-5678", "test-cluster", "production", "bar", + "bar-service", "v2", "barapp", "v2", + Istio::Common::WorkloadType::Pod, "", "", ""); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - workload_discovery: {} + )EOF"); + + // Verify CelState is stored under downstream_peer for CEL expressions + const auto* cel_state = stream_info_.filterState() + ->getDataReadOnly( + Istio::Common::DownstreamPeer); + ASSERT_NE(cel_state, nullptr); + + // Verify WorkloadMetadataObject is stored under downstream_peer_obj for FIELD accessor + const auto* peer_info = + stream_info_.filterState()->getDataReadOnly(Istio::Common::DownstreamPeerObj); + ASSERT_NE(peer_info, nullptr); + + // Test that serializeAsProto still works for CEL compatibility + auto proto = peer_info->serializeAsProto(); + ASSERT_NE(proto, nullptr); + + // Verify the protobuf contains expected data + const auto* struct_proto = dynamic_cast(proto.get()); + ASSERT_NE(struct_proto, nullptr); + EXPECT_EQ("production", extractString(*struct_proto, "namespace")); + EXPECT_EQ("bar", extractString(*struct_proto, "workload")); + EXPECT_EQ("test-cluster", extractString(*struct_proto, "cluster")); +} +TEST_F(PeerMetadataTest, DownstreamBaggagePropagation) { + initialize(R"EOF( + downstream_propagation: + - baggage: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + EXPECT_TRUE(response_headers_.has(Headers::get().Baggage)); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamBaggagePropagation) { + initialize(R"EOF( + upstream_propagation: + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + EXPECT_TRUE(request_headers_.has(Headers::get().Baggage)); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, BothDirectionsBaggagePropagation) { + initialize(R"EOF( + downstream_propagation: + - baggage: {} + upstream_propagation: + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + EXPECT_TRUE(request_headers_.has(Headers::get().Baggage)); + EXPECT_TRUE(response_headers_.has(Headers::get().Baggage)); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, BaggagePropagationWithNodeMetadata) { + // Setup node metadata that would be converted to baggage + auto& node = context_.server_factory_context_.local_info_.node_; + TestUtility::loadFromYaml(R"EOF( + metadata: + NAMESPACE: production + CLUSTER_ID: test-cluster + WORKLOAD_NAME: test-workload + NAME: test-instance + LABELS: + app: test-app + version: v1.0 + service.istio.io/canonical-name: test-service + service.istio.io/canonical-revision: main + )EOF", + node); + + initialize(R"EOF( + downstream_propagation: + - baggage: {} + )EOF"); + + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + + const auto baggage_header = response_headers_.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + // Verify baggage contains expected key-value pairs + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.namespace.name=production")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.cluster.name=test-cluster")); + EXPECT_TRUE(absl::StrContains(baggage_value, "service.name=test-service")); + EXPECT_TRUE(absl::StrContains(baggage_value, "service.version=main")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.pod.name=test-workload")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.instance.name=test-instance")); +} + +// Test class specifically for BaggagePropagationMethod unit tests +class BaggagePropagationMethodTest : public testing::Test { +protected: + BaggagePropagationMethodTest() = default; + + void SetUp() override { + TestUtility::loadFromYaml(R"EOF( + metadata: + NAMESPACE: test-namespace + CLUSTER_ID: sample-cluster + WORKLOAD_NAME: sample-workload + NAME: sample-instance + LABELS: + app: sample-app + version: v2.1 + service.istio.io/canonical-name: sample-service + service.istio.io/canonical-revision: stable + locality: + zone: us-east4-b + region: us-east4 + )EOF", + context_.server_factory_context_.local_info_.node_); + } + + NiceMock context_; + NiceMock stream_info_; +}; + +TEST_F(BaggagePropagationMethodTest, DownstreamBaggageInjection) { + io::istio::http::peer_metadata::Config_Baggage baggage_config; + BaggagePropagationMethod method(context_.server_factory_context_, baggage_config); + + Http::TestResponseHeaderMapImpl headers; + Context ctx; + + method.inject(stream_info_, headers, ctx); + + EXPECT_EQ(1, headers.size()); + const auto baggage_header = headers.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + + // Verify all expected tokens are present + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.namespace.name=test-namespace")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.cluster.name=sample-cluster")); + EXPECT_TRUE(absl::StrContains(baggage_value, "service.name=sample-service")); + EXPECT_TRUE(absl::StrContains(baggage_value, "service.version=stable")); + EXPECT_TRUE(absl::StrContains(baggage_value, "app.name=sample-app")); + EXPECT_TRUE(absl::StrContains(baggage_value, "app.version=v2.1")); + EXPECT_TRUE( + absl::StrContains(baggage_value, "k8s.pod.name=sample-workload")); // workload type is pod + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.instance.name=sample-instance")); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.region=us-east4")); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.availability_zone=us-east4-b")); +} + +TEST_F(BaggagePropagationMethodTest, UpstreamBaggageInjection) { + io::istio::http::peer_metadata::Config_Baggage baggage_config; + BaggagePropagationMethod method(context_.server_factory_context_, baggage_config); + + Http::TestRequestHeaderMapImpl headers; + Context ctx; + + method.inject(stream_info_, headers, ctx); + + EXPECT_EQ(1, headers.size()); + const auto baggage_header = headers.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + + // Verify tokens are properly formatted + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.namespace.name=test-namespace")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.cluster.name=sample-cluster")); + + // Check that values are comma-separated + std::vector parts = absl::StrSplit(baggage_value, ','); + EXPECT_GT(parts.size(), 1); +} + +TEST_F(BaggagePropagationMethodTest, EmptyMetadataBaggage) { + // Reset node metadata to empty + context_.server_factory_context_.local_info_.node_.Clear(); + + io::istio::http::peer_metadata::Config_Baggage baggage_config; + BaggagePropagationMethod method(context_.server_factory_context_, baggage_config); + + Http::TestResponseHeaderMapImpl headers; + Context ctx; + + method.inject(stream_info_, headers, ctx); + + EXPECT_EQ(1, headers.size()); + const auto baggage_header = headers.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + // With empty metadata, there should be no baggage + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + EXPECT_EQ("", baggage_value); +} + +TEST_F(BaggagePropagationMethodTest, PartialMetadataBaggage) { + // Setup node metadata with only some fields + TestUtility::loadFromYaml(R"EOF( + metadata: + NAMESPACE: partial-namespace + LABELS: + app: partial-app + # Missing other fields like version, cluster, etc. + )EOF", + context_.server_factory_context_.local_info_.node_); + + io::istio::http::peer_metadata::Config_Baggage baggage_config; + BaggagePropagationMethod method(context_.server_factory_context_, baggage_config); + + Http::TestRequestHeaderMapImpl headers; + Context ctx; + + method.inject(stream_info_, headers, ctx); + + EXPECT_EQ(1, headers.size()); + const auto baggage_header = headers.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + + // Should contain only the fields that were present + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.namespace.name=partial-namespace")); + EXPECT_TRUE(absl::StrContains(baggage_value, "app.name=partial-app")); + + // Should not contain fields that were not present + EXPECT_FALSE(absl::StrContains(baggage_value, "app.version=")); + EXPECT_FALSE(absl::StrContains(baggage_value, "k8s.cluster.name=")); +} + +TEST_F(PeerMetadataTest, BaggagePropagationWithMixedConfig) { + initialize(R"EOF( + downstream_propagation: + - baggage: {} + - istio_headers: {} + upstream_propagation: + - baggage: {} + - istio_headers: {} + )EOF"); + + // Baggage should always be propagated, Istio headers are also propagated for upstream only + EXPECT_EQ(3, request_headers_.size()); // baggage + istio headers (id + metadata) + EXPECT_EQ(1, response_headers_.size()); // baggage only (no discovery, so no MX downstream) + + EXPECT_TRUE(request_headers_.has(Headers::get().Baggage)); + EXPECT_TRUE(request_headers_.has(Headers::get().ExchangeMetadataHeaderId)); + EXPECT_TRUE(request_headers_.has(Headers::get().ExchangeMetadataHeader)); + + EXPECT_TRUE(response_headers_.has(Headers::get().Baggage)); +} + +// Baggage Discovery Tests + +TEST_F(PeerMetadataTest, DownstreamBaggageDiscoveryEmpty) { + initialize(R"EOF( + downstream_discovery: + - baggage: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamBaggageDiscoveryEmpty) { + // The baggage discovery filter should only be used for downstream + // peer metadata detection. + initialize(R"EOF( + upstream_discovery: + - baggage: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamBaggageDiscovery) { + request_headers_.setReference( + Headers::get().Baggage, + "k8s.namespace.name=test-namespace,k8s.cluster.name=test-cluster," + "service.name=test-service,service.version=v1,k8s.deployment.name=test-workload," + "k8s.workload.type=deployment,k8s.instance.name=test-instance-123," + "app.name=test-app,app.version=v2.0"); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "test-namespace"); + checkNoPeer(false); + checkShared(false); +} + +TEST_F(PeerMetadataTest, UpstreamBaggageDiscovery) { + response_headers_.setReference( + Headers::get().Baggage, + "k8s.namespace.name=upstream-namespace,k8s.cluster.name=upstream-cluster," + "service.name=upstream-service,service.version=v2,k8s.workload.name=upstream-workload," + "k8s.workload.type=pod,k8s.instance.name=upstream-instance-456," + "app.name=upstream-app,app.version=v3.0"); + initialize(R"EOF( + upstream_discovery: + - baggage: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + checkNoPeer(true); + // Baggage discovery should ignore upstream. + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, BothDirectionsBaggageDiscovery) { + request_headers_.setReference(Headers::get().Baggage, + "k8s.namespace.name=downstream-ns,service.name=downstream-svc"); + response_headers_.setReference(Headers::get().Baggage, + "k8s.namespace.name=upstream-ns,service.name=upstream-svc"); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + upstream_discovery: + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + checkPeerNamespace(true, "downstream-ns"); + // Baggage discovery should ignore upstream + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamBaggageFallbackFirst) { + // Baggage is present, so XDS should not be called + request_headers_.setReference( + Headers::get().Baggage, "k8s.namespace.name=baggage-namespace,service.name=baggage-service"); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)).Times(0); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "baggage-namespace"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamBaggageFallbackSecond) { + // No baggage header, so XDS should be called as fallback + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-namespace", "foo", + "foo-service", "v1alpha3", "", "", + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "xds-namespace"); + checkPeerLocality(true, "us-east4", "us-east4-b"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, UpstreamBaggageFallbackFirst) { + // Baggage is present, but ignored as it's coming from upstream. + response_headers_.setReference( + Headers::get().Baggage, + "k8s.namespace.name=baggage-upstream,service.name=baggage-upstream-service"); + // WDS information is also present, and this is the one tha tshould be used. + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-upstream", "foo", + "foo-service", "v1alpha3", "", "", + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "10.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - baggage: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(1, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "xds-upstream"); +} + +TEST_F(PeerMetadataTest, UpstreamBaggageFallbackSecond) { + // No baggage header, baggage is ignored as it's coming from upstream, + // but workload discovery should pick up the details. + const WorkloadMetadataObject pod("pod-foo-1234", "my-cluster", "xds-upstream", "foo", + "foo-service", "v1alpha3", "", "", + Istio::Common::WorkloadType::Pod, "", "us-east4", "us-east4-b"); + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "10.0.0.1")) { + return {pod}; + } + return {}; + })); + initialize(R"EOF( + upstream_discovery: + - baggage: {} + - workload_discovery: {} + )EOF"); + EXPECT_EQ(0, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkNoPeer(true); + checkPeerNamespace(false, "xds-upstream"); +} + +TEST_F(PeerMetadataTest, DownstreamBaggageWithMXFallback) { + // Baggage is present, so MX should not be used + request_headers_.setReference(Headers::get().Baggage, + "k8s.namespace.name=baggage-ns,service.name=baggage-svc"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + - istio_headers: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + checkPeerNamespace(true, "baggage-ns"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, DownstreamMXWithBaggageFallback) { + // MX is first, so it should be used even if baggage is present + request_headers_.setReference(Headers::get().Baggage, + "k8s.namespace.name=baggage-ns,service.name=baggage-svc"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-pod"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, SampleIstioHeader); + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); + EXPECT_EQ(0, response_headers_.size()); + // MX header has namespace "default" from SampleIstioHeader + checkPeerNamespace(true, "default"); + checkNoPeer(false); +} + +TEST_F(PeerMetadataTest, BaggageDiscoveryWithPropagation) { + request_headers_.setReference(Headers::get().Baggage, + "k8s.namespace.name=discovered-ns,service.name=discovered-svc"); + initialize(R"EOF( + downstream_discovery: + - baggage: {} + downstream_propagation: + - baggage: {} + upstream_propagation: + - baggage: {} + )EOF"); + EXPECT_EQ(1, request_headers_.size()); // upstream baggage propagation + EXPECT_EQ(1, response_headers_.size()); // downstream baggage propagation + EXPECT_TRUE(request_headers_.has(Headers::get().Baggage)); + EXPECT_TRUE(response_headers_.has(Headers::get().Baggage)); + checkPeerNamespace(true, "discovered-ns"); + checkNoPeer(false); +} + +// Test class specifically for BaggageDiscoveryMethod unit tests +class BaggageDiscoveryMethodTest : public testing::Test { +protected: + BaggageDiscoveryMethodTest() = default; + + NiceMock context_; + NiceMock stream_info_; +}; + +TEST_F(BaggageDiscoveryMethodTest, DerivePeerInfoFromBaggage) { + BaggageDiscoveryMethod method; + + Http::TestRequestHeaderMapImpl headers; + headers.setReference( + Headers::get().Baggage, + "k8s.namespace.name=unit-test-namespace,k8s.cluster.name=unit-test-cluster," + "service.name=unit-test-service,service.version=v1.0," + "k8s.deployment.name=unit-test-workload,k8s.workload.type=deployment," + "k8s.instance.name=unit-test-instance,app.name=unit-test-app,app.version=v2.0"); + Context ctx; + + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ("unit-test-namespace", result->namespace_name_); + EXPECT_EQ("unit-test-cluster", result->cluster_name_); + EXPECT_EQ("unit-test-service", result->canonical_name_); + EXPECT_EQ("v1.0", result->canonical_revision_); + EXPECT_EQ("unit-test-workload", result->workload_name_); + EXPECT_EQ("unit-test-instance", result->instance_name_); + EXPECT_EQ("unit-test-app", result->app_name_); + EXPECT_EQ("v2.0", result->app_version_); + EXPECT_EQ(Istio::Common::WorkloadType::Deployment, result->workload_type_); +} + +TEST_F(BaggageDiscoveryMethodTest, DerivePeerInfoEmptyBaggage) { + BaggageDiscoveryMethod method; + + Http::TestRequestHeaderMapImpl headers; + Context ctx; + + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + + EXPECT_FALSE(result.has_value()); +} + +TEST_F(BaggageDiscoveryMethodTest, DerivePeerInfoPartialBaggage) { + BaggageDiscoveryMethod method; + + Http::TestResponseHeaderMapImpl headers; + headers.setReference(Headers::get().Baggage, + "k8s.namespace.name=partial-ns,service.name=partial-svc"); + Context ctx; + + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ("partial-ns", result->namespace_name_); + EXPECT_EQ("partial-svc", result->canonical_name_); + // Other fields should be empty or default + EXPECT_TRUE(result->cluster_name_.empty()); + EXPECT_TRUE(result->workload_name_.empty()); +} + +TEST_F(BaggageDiscoveryMethodTest, DerivePeerInfoAllWorkloadTypes) { + BaggageDiscoveryMethod method; + Context ctx; + + // Test Pod workload type + { + Http::TestRequestHeaderMapImpl headers; + headers.setReference(Headers::get().Baggage, + "k8s.namespace.name=test-ns,k8s.pod.name=pod-name"); + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(Istio::Common::WorkloadType::Pod, result->workload_type_); + } + + // Test Deployment workload type + { + Http::TestRequestHeaderMapImpl headers; + headers.setReference(Headers::get().Baggage, + "k8s.namespace.name=test-ns,k8s.deployment.name=deployment-name"); + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(Istio::Common::WorkloadType::Deployment, result->workload_type_); + } + + // Test Job workload type + { + Http::TestRequestHeaderMapImpl headers; + headers.setReference(Headers::get().Baggage, + "k8s.namespace.name=test-ns,k8s.job.name=job-name"); + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(Istio::Common::WorkloadType::Job, result->workload_type_); + } + + // Test CronJob workload type + { + Http::TestRequestHeaderMapImpl headers; + headers.setReference(Headers::get().Baggage, + "k8s.namespace.name=test-ns,k8s.cronjob.name=cronjob-name"); + const auto result = method.derivePeerInfo(stream_info_, headers, ctx); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(Istio::Common::WorkloadType::CronJob, result->workload_type_); + } +} + +// Additional Locality Tests + +// Test baggage discovery with locality information +TEST_F(PeerMetadataTest, BaggageDiscoveryWithLocalityInformation) { + request_headers_.setReference( + Headers::get().Baggage, + "k8s.namespace.name=locality-test-ns,k8s.cluster.name=locality-test-cluster," + "service.name=locality-test-service,service.version=v1," + "cloud.region=europe-west1,cloud.availability_zone=europe-west1-c"); + + initialize(R"EOF( + downstream_discovery: + - baggage: {} + )EOF"); + + EXPECT_EQ(1, request_headers_.size()); + checkPeerNamespace(true, "locality-test-ns"); + checkPeerLocality(true, "europe-west1", "europe-west1-c"); + checkNoPeer(false); +} + +// Test baggage propagation includes locality from node configuration +TEST_F(PeerMetadataTest, BaggagePropagationWithNodeLocality) { + // Setup node with locality + auto& node = context_.server_factory_context_.local_info_.node_; + TestUtility::loadFromYaml(R"EOF( + metadata: + NAMESPACE: locality-prop-ns + CLUSTER_ID: locality-prop-cluster + WORKLOAD_NAME: locality-prop-workload + locality: + region: asia-southeast1 + zone: asia-southeast1-a + )EOF", + node); + + initialize(R"EOF( + upstream_propagation: + - baggage: {} + )EOF"); + + EXPECT_EQ(1, request_headers_.size()); + const auto baggage_header = request_headers_.get(Headers::get().Baggage); + ASSERT_FALSE(baggage_header.empty()); + + std::string baggage_value = std::string(baggage_header[0]->value().getStringView()); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.region=asia-southeast1")); + EXPECT_TRUE(absl::StrContains(baggage_value, "cloud.availability_zone=asia-southeast1-a")); + EXPECT_TRUE(absl::StrContains(baggage_value, "k8s.namespace.name=locality-prop-ns")); +} + +// Test MX discovery preserves locality from incoming headers +TEST_F(PeerMetadataTest, MXDiscoveryPreservesLocality) { + // Create test MX header with locality + google::protobuf::Struct test_metadata; + (*test_metadata.mutable_fields())[Istio::Common::NamespaceMetadataField].set_string_value( + "mx-locality-ns"); + (*test_metadata.mutable_fields())[Istio::Common::ClusterMetadataField].set_string_value( + "mx-locality-cluster"); + (*test_metadata.mutable_fields())[Istio::Common::RegionMetadataField].set_string_value( + "us-central1"); + (*test_metadata.mutable_fields())[Istio::Common::ZoneMetadataField].set_string_value( + "us-central1-f"); + + const std::string metadata_bytes = Istio::Common::serializeToStringDeterministic(test_metadata); + const std::string encoded_metadata = Base64::encode(metadata_bytes.data(), metadata_bytes.size()); + + request_headers_.setReference(Headers::get().ExchangeMetadataHeaderId, "test-mx-locality"); + request_headers_.setReference(Headers::get().ExchangeMetadataHeader, encoded_metadata); + + initialize(R"EOF( + downstream_discovery: + - istio_headers: {} + )EOF"); + + EXPECT_EQ(0, request_headers_.size()); // Headers removed after discovery + checkPeerNamespace(true, "mx-locality-ns"); + checkPeerLocality(true, "us-central1", "us-central1-f"); +} + +// Test locality absence doesn't break functionality +TEST_F(PeerMetadataTest, LocalityAbsenceDoesNotBreakFunctionality) { + // Setup node WITHOUT locality (the Envoy serverFactory mock sets zone to "zone_name" by default) + auto& node = context_.server_factory_context_.local_info_.node_; + node.mutable_locality()->Clear(); // Clear locality to simulate absence + // Test with WorkloadMetadataObject with empty locality + const WorkloadMetadataObject pod_no_locality( + "pod-no-locality", "cluster-no-locality", "ns-no-locality", "workload-no-locality", + "service-no-locality", "v1", "", "", Istio::Common::WorkloadType::Pod, "", "", ""); + + EXPECT_CALL(*metadata_provider_, GetMetadata(_)) + .WillRepeatedly(Invoke([&](const Network::Address::InstanceConstSharedPtr& address) + -> std::optional { + if (absl::StartsWith(address->asStringView(), "127.0.0.1")) { + return {pod_no_locality}; + } + return {}; + })); + + // Also test with baggage without locality + request_headers_.setReference( + Headers::get().Baggage, + "k8s.namespace.name=baggage-no-locality,service.name=baggage-no-locality-svc"); + + initialize(R"EOF( + downstream_discovery: + - baggage: {} + - workload_discovery: {} + upstream_propagation: + - baggage: {} + )EOF"); + + EXPECT_EQ(1, request_headers_.size()); // baggage propagation + + // Verify discovery still works without locality + checkPeerNamespace(true, "baggage-no-locality"); + + // Verify empty locality doesn't break anything + const auto* peer_info = + stream_info_.filterState()->getDataReadOnly(Istio::Common::DownstreamPeerObj); + ASSERT_NE(peer_info, nullptr); + EXPECT_EQ("", peer_info->locality_region_); + EXPECT_EQ("", peer_info->locality_zone_); + + // Verify propagated baggage doesn't contain locality tokens when not available + const auto propagated_baggage = request_headers_.get(Headers::get().Baggage); + ASSERT_FALSE(propagated_baggage.empty()); + std::string propagated_value = std::string(propagated_baggage[0]->value().getStringView()); + EXPECT_FALSE(absl::StrContains(propagated_value, "cloud.region=")); + EXPECT_FALSE(absl::StrContains(propagated_value, "cloud.availability_zone=")); +} + +} // namespace +} // namespace PeerMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/metadata_exchange/BUILD b/source/extensions/filters/network/metadata_exchange/BUILD new file mode 100644 index 00000000000..6712bfa5734 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/BUILD @@ -0,0 +1,91 @@ +# Copyright 2019 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +# + +# Metadata Exchange filter + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_cc_library( + name = "metadata_exchange", + srcs = [ + "metadata_exchange.cc", + "metadata_exchange_initial_header.cc", + ], + hdrs = [ + "metadata_exchange.h", + "metadata_exchange_initial_header.h", + ], + repository = "@envoy", + deps = [ + "//extensions/common:metadata_object_lib", + "//source/extensions/common/workload_discovery:api_lib", + "//source/extensions/filters/network/metadata_exchange/config:metadata_exchange_cc_proto", + "@abseil-cpp//absl/base:core_headers", + "@abseil-cpp//absl/strings", + "@envoy//envoy/local_info:local_info_interface", + "@envoy//envoy/network:connection_interface", + "@envoy//envoy/network:filter_interface", + "@envoy//envoy/runtime:runtime_interface", + "@envoy//envoy/stats:stats_macros", + "@envoy//envoy/stream_info:filter_state_interface", + "@envoy//source/common/http:utility_lib", + "@envoy//source/common/network:filter_state_dst_address_lib", + "@envoy//source/common/network:utility_lib", + "@envoy//source/common/protobuf", + "@envoy//source/common/protobuf:utility_lib", + "@envoy//source/common/stream_info:bool_accessor_lib", + "@envoy//source/extensions/filters/common/expr:cel_state_lib", + ], +) + +envoy_cc_library( + name = "config_lib", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + deps = [ + ":metadata_exchange", + "//source/extensions/filters/network/metadata_exchange/config:metadata_exchange_cc_proto", + "@envoy//envoy/registry", + "@envoy//envoy/server:filter_config_interface", + ], +) + +envoy_cc_test( + name = "metadataexchange_test", + srcs = [ + "metadata_exchange_test.cc", + ], + repository = "@envoy", + deps = [ + ":config_lib", + ":metadata_exchange", + "@envoy//source/common/protobuf", + "@envoy//test/mocks/local_info:local_info_mocks", + "@envoy//test/mocks/network:network_mocks", + "@envoy//test/mocks/protobuf:protobuf_mocks", + "@envoy//test/mocks/server:server_factory_context_mocks", + ], +) diff --git a/source/extensions/filters/network/metadata_exchange/config.cc b/source/extensions/filters/network/metadata_exchange/config.cc new file mode 100644 index 00000000000..215770c680d --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/config.cc @@ -0,0 +1,104 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/network/metadata_exchange/config.h" + +#include "envoy/network/connection.h" +#include "envoy/registry/registry.h" +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange.h" + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { +namespace { + +static constexpr char StatPrefix[] = "metadata_exchange."; + +Network::FilterFactoryCb createFilterFactoryHelper( + const envoy::tcp::metadataexchange::config::MetadataExchange& proto_config, + Server::Configuration::ServerFactoryContext& context, FilterDirection filter_direction) { + ASSERT(!proto_config.protocol().empty()); + + absl::flat_hash_set additional_labels; + if (!proto_config.additional_labels().empty()) { + for (const auto& label : proto_config.additional_labels()) { + additional_labels.emplace(label); + } + } + + MetadataExchangeConfigSharedPtr filter_config(std::make_shared( + StatPrefix, proto_config.protocol(), filter_direction, proto_config.enable_discovery(), + additional_labels, context, context.scope())); + return [filter_config, &context](Network::FilterManager& filter_manager) -> void { + filter_manager.addFilter( + std::make_shared(filter_config, context.localInfo())); + }; +} +} // namespace + +absl::StatusOr +MetadataExchangeConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& config, Server::Configuration::FactoryContext& context) { + return createFilterFactory( + dynamic_cast(config), context); +} + +ProtobufTypes::MessagePtr MetadataExchangeConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +Network::FilterFactoryCb MetadataExchangeConfigFactory::createFilterFactory( + const envoy::tcp::metadataexchange::config::MetadataExchange& proto_config, + Server::Configuration::FactoryContext& context) { + return createFilterFactoryHelper(proto_config, context.serverFactoryContext(), + FilterDirection::Downstream); +} + +Network::FilterFactoryCb MetadataExchangeUpstreamConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& config, Server::Configuration::UpstreamFactoryContext& context) { + return createFilterFactory( + dynamic_cast(config), context); +} + +ProtobufTypes::MessagePtr MetadataExchangeUpstreamConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +Network::FilterFactoryCb MetadataExchangeUpstreamConfigFactory::createFilterFactory( + const envoy::tcp::metadataexchange::config::MetadataExchange& proto_config, + Server::Configuration::UpstreamFactoryContext& context) { + return createFilterFactoryHelper(proto_config, context.serverFactoryContext(), + FilterDirection::Upstream); +} + +/** + * Static registration for the MetadataExchange Downstream filter. @see + * RegisterFactory. + */ +static Registry::RegisterFactory + registered_; + +/** + * Static registration for the MetadataExchange Upstream filter. @see + * RegisterFactory. + */ +static Registry::RegisterFactory + registered_upstream_; + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/source/extensions/filters/network/metadata_exchange/config.h b/source/extensions/filters/network/metadata_exchange/config.h new file mode 100644 index 00000000000..d4a215569f2 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/config.h @@ -0,0 +1,69 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "envoy/server/filter_config.h" +#include "source/extensions/filters/network/metadata_exchange/config/metadata_exchange.pb.h" + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { + +/** + * Config registration for the MetadataExchange filter. @see + * NamedNetworkFilterConfigFactory. + */ +class MetadataExchangeConfigFactory + : public Server::Configuration::NamedNetworkFilterConfigFactory { +public: + absl::StatusOr + createFilterFactoryFromProto(const Protobuf::Message&, + Server::Configuration::FactoryContext&) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + std::string name() const override { return "envoy.filters.network.metadata_exchange"; } + +private: + Network::FilterFactoryCb + createFilterFactory(const envoy::tcp::metadataexchange::config::MetadataExchange& proto_config, + Server::Configuration::FactoryContext& context); +}; + +/** + * Config registration for the MetadataExchange Upstream filter. @see + * NamedUpstreamNetworkFilterConfigFactory. + */ +class MetadataExchangeUpstreamConfigFactory + : public Server::Configuration::NamedUpstreamNetworkFilterConfigFactory { +public: + Network::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, + Server::Configuration::UpstreamFactoryContext&) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + std::string name() const override { return "envoy.filters.network.upstream.metadata_exchange"; } + +private: + Network::FilterFactoryCb + createFilterFactory(const envoy::tcp::metadataexchange::config::MetadataExchange& proto_config, + Server::Configuration::UpstreamFactoryContext& context); +}; + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/src/istio/authn/BUILD b/source/extensions/filters/network/metadata_exchange/config/BUILD similarity index 72% rename from src/istio/authn/BUILD rename to source/extensions/filters/network/metadata_exchange/config/BUILD index b87c9b7bde4..160ee90e7fe 100644 --- a/src/istio/authn/BUILD +++ b/source/extensions/filters/network/metadata_exchange/config/BUILD @@ -1,4 +1,4 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. +# Copyright 2019 Istio Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,15 +14,16 @@ # ################################################################################ # - package(default_visibility = ["//visibility:public"]) -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_proto_library", +licenses(["notice"]) + +proto_library( + name = "metadata_exchange_proto", + srcs = ["metadata_exchange.proto"], ) -envoy_proto_library( - name = "context_proto", - srcs = ["context.proto"], +cc_proto_library( + name = "metadata_exchange_cc_proto", + deps = ["metadata_exchange_proto"], ) diff --git a/source/extensions/filters/network/metadata_exchange/config/metadata_exchange.proto b/source/extensions/filters/network/metadata_exchange/config/metadata_exchange.proto new file mode 100644 index 00000000000..332b0c507b6 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/config/metadata_exchange.proto @@ -0,0 +1,38 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package envoy.tcp.metadataexchange.config; + +option java_outer_classname = "MetadataExchangeProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.tcp.metadataexchange.config"; +option go_package = "MetadataExchange"; + +// [#protodoc-title: MetadataExchange protocol match and data transfer] +// MetadataExchange protocol match and data transfer +message MetadataExchange { + // Protocol that Alpn should support on the server. + // [#comment:TODO(GargNupur): Make it a list.] + string protocol = 1; + + // If true, will attempt to use WDS in case the prefix peer metadata is not available. + bool enable_discovery = 2; + + // Additional labels to be added to the peer metadata to help your understand the traffic. + // e.g. `role`, `location` etc. + repeated string additional_labels = 3; +} diff --git a/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc b/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc new file mode 100644 index 00000000000..16d9eae8921 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange.cc @@ -0,0 +1,377 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange.h" + +#include +#include + +#include "absl/base/internal/endian.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "envoy/network/connection.h" +#include "envoy/stats/scope.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/protobuf/utility.h" +#include "source/common/network/utility.h" +#include "source/common/network/filter_state_dst_address.h" +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h" +#include "source/common/stream_info/bool_accessor_impl.h" + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { + +using ::Envoy::Extensions::Filters::Common::Expr::CelState; + +namespace { + +// Sentinel key in the filter state, indicating that the peer metadata is +// decidedly absent. This is different from a missing peer metadata ID key +// which could indicate that the metadata is not received yet. +const std::string kMetadataNotFoundValue = "envoy.wasm.metadata_exchange.peer_unknown"; + +std::unique_ptr constructProxyHeaderData(const Protobuf::Any& proxy_data) { + MetadataExchangeInitialHeader initial_header; + std::string proxy_data_str = proxy_data.SerializeAsString(); + // Converting from host to network byte order so that most significant byte is + // placed first. + initial_header.magic = absl::ghtonl(MetadataExchangeInitialHeader::magic_number); + initial_header.data_size = absl::ghtonl(proxy_data_str.length()); + + Buffer::OwnedImpl initial_header_buffer{absl::string_view( + reinterpret_cast(&initial_header), sizeof(MetadataExchangeInitialHeader))}; + auto proxy_data_buffer = std::make_unique(proxy_data_str); + proxy_data_buffer->prepend(initial_header_buffer); + return proxy_data_buffer; +} + +} // namespace + +MetadataExchangeConfig::MetadataExchangeConfig( + const std::string& stat_prefix, const std::string& protocol, + const FilterDirection filter_direction, bool enable_discovery, + const absl::flat_hash_set additional_labels, + Server::Configuration::ServerFactoryContext& factory_context, Stats::Scope& scope) + : scope_(scope), stat_prefix_(stat_prefix), protocol_(protocol), + filter_direction_(filter_direction), stats_(generateStats(stat_prefix, scope)), + additional_labels_(additional_labels) { + if (enable_discovery) { + metadata_provider_ = Extensions::Common::WorkloadDiscovery::GetProvider(factory_context); + } +} + +Network::FilterStatus MetadataExchangeFilter::onData(Buffer::Instance& data, bool end_stream) { + switch (conn_state_) { + case Invalid: + FALLTHRU; + case Done: + // No work needed if connection state is Done or Invalid. + return Network::FilterStatus::Continue; + case ConnProtocolNotRead: { + // If Alpn protocol is not the expected one, then return. + // Else find and write node metadata. + if (read_callbacks_->connection().nextProtocol() != config_->protocol_) { + ENVOY_LOG(trace, "Alpn Protocol Not Found. Expected {}, Got {}", config_->protocol_, + read_callbacks_->connection().nextProtocol()); + setMetadataNotFoundFilterState(); + conn_state_ = Invalid; + config_->stats().alpn_protocol_not_found_.inc(); + return Network::FilterStatus::Continue; + } + conn_state_ = WriteMetadata; + config_->stats().alpn_protocol_found_.inc(); + FALLTHRU; + } + case WriteMetadata: { + // TODO(gargnupur): Try to move this just after alpn protocol is + // determined and first onData is called in Downstream filter. + // If downstream filter, write metadata. + // Otherwise, go ahead and try to read initial header and proxy data. + writeNodeMetadata(); + FALLTHRU; + } + case ReadingInitialHeader: + case NeedMoreDataInitialHeader: { + tryReadInitialProxyHeader(data); + if (conn_state_ == NeedMoreDataInitialHeader) { + if (end_stream) { + // Upstream has entered a half-closed state, and will be sending no more data. + // Since this plugin would expect additional headers, but none is forthcoming, + // do not block the tcp_proxy downstream of us from draining the buffer. + ENVOY_LOG(debug, "Upstream closed early, aborting istio-peer-exchange"); + conn_state_ = Invalid; + return Network::FilterStatus::Continue; + } + return Network::FilterStatus::StopIteration; + } + if (conn_state_ == Invalid) { + return Network::FilterStatus::Continue; + } + FALLTHRU; + } + case ReadingProxyHeader: + case NeedMoreDataProxyHeader: { + tryReadProxyData(data); + if (conn_state_ == NeedMoreDataProxyHeader) { + return Network::FilterStatus::StopIteration; + } + if (conn_state_ == Invalid) { + return Network::FilterStatus::Continue; + } + FALLTHRU; + } + default: + conn_state_ = Done; + return Network::FilterStatus::Continue; + } + + return Network::FilterStatus::Continue; +} + +Network::FilterStatus MetadataExchangeFilter::onNewConnection() { + return Network::FilterStatus::Continue; +} + +Network::FilterStatus MetadataExchangeFilter::onWrite(Buffer::Instance&, bool) { + switch (conn_state_) { + case Invalid: + case Done: + // No work needed if connection state is Done or Invalid. + return Network::FilterStatus::Continue; + case ConnProtocolNotRead: { + if (read_callbacks_->connection().nextProtocol() != config_->protocol_) { + ENVOY_LOG(trace, "Alpn Protocol Not Found. Expected {}, Got {}", config_->protocol_, + read_callbacks_->connection().nextProtocol()); + setMetadataNotFoundFilterState(); + conn_state_ = Invalid; + config_->stats().alpn_protocol_not_found_.inc(); + return Network::FilterStatus::Continue; + } else { + conn_state_ = WriteMetadata; + config_->stats().alpn_protocol_found_.inc(); + } + FALLTHRU; + } + case WriteMetadata: { + // TODO(gargnupur): Try to move this just after alpn protocol is + // determined and first onWrite is called in Upstream filter. + writeNodeMetadata(); + FALLTHRU; + } + case ReadingInitialHeader: + case ReadingProxyHeader: + case NeedMoreDataInitialHeader: + case NeedMoreDataProxyHeader: + // These are to be handled in Reading Pipeline. + return Network::FilterStatus::Continue; + } + + return Network::FilterStatus::Continue; +} + +void MetadataExchangeFilter::writeNodeMetadata() { + if (conn_state_ != WriteMetadata) { + return; + } + ENVOY_LOG(trace, "Writing metadata to the connection."); + Protobuf::Struct data; + const auto obj = Istio::Common::convertStructToWorkloadMetadata( + local_info_.node().metadata(), config_->additional_labels_, local_info_.node().locality()); + + *(*data.mutable_fields())[ExchangeMetadataHeader].mutable_struct_value() = + Istio::Common::convertWorkloadMetadataToStruct(*obj); + std::string metadata_id = getMetadataId(); + if (!metadata_id.empty()) { + (*data.mutable_fields())[ExchangeMetadataHeaderId].set_string_value(metadata_id); + } + if (data.fields_size() > 0) { + Protobuf::Any metadata_any_value; + metadata_any_value.set_type_url(StructTypeUrl); + *metadata_any_value.mutable_value() = Istio::Common::serializeToStringDeterministic(data); + ; + std::unique_ptr buf = constructProxyHeaderData(metadata_any_value); + write_callbacks_->injectWriteDataToFilterChain(*buf, false); + config_->stats().metadata_added_.inc(); + } + + conn_state_ = ReadingInitialHeader; +} + +void MetadataExchangeFilter::tryReadInitialProxyHeader(Buffer::Instance& data) { + if (conn_state_ != ReadingInitialHeader && conn_state_ != NeedMoreDataInitialHeader) { + return; + } + const uint32_t initial_header_length = sizeof(MetadataExchangeInitialHeader); + if (data.length() < initial_header_length) { + config_->stats().initial_header_not_found_.inc(); + // Not enough data to read. Wait for it to come. + ENVOY_LOG(debug, "Alpn Protocol matched. Waiting to read more initial header."); + conn_state_ = NeedMoreDataInitialHeader; + return; + } + MetadataExchangeInitialHeader initial_header; + data.copyOut(0, initial_header_length, &initial_header); + if (absl::gntohl(initial_header.magic) != MetadataExchangeInitialHeader::magic_number) { + config_->stats().initial_header_not_found_.inc(); + setMetadataNotFoundFilterState(); + ENVOY_LOG(warn, "Incorrect istio-peer-exchange ALPN magic. Peer missing TCP " + "MetadataExchange filter."); + conn_state_ = Invalid; + return; + } + proxy_data_length_ = absl::gntohl(initial_header.data_size); + // Drain the initial header length bytes read. + data.drain(initial_header_length); + conn_state_ = ReadingProxyHeader; +} + +void MetadataExchangeFilter::tryReadProxyData(Buffer::Instance& data) { + if (conn_state_ != ReadingProxyHeader && conn_state_ != NeedMoreDataProxyHeader) { + return; + } + if (data.length() < proxy_data_length_) { + // Not enough data to read. Wait for it to come. + ENVOY_LOG(debug, "Alpn Protocol matched. Waiting to read more metadata."); + conn_state_ = NeedMoreDataProxyHeader; + return; + } + std::string proxy_data_buf = + std::string(static_cast(data.linearize(proxy_data_length_)), proxy_data_length_); + Protobuf::Any proxy_data; + if (!proxy_data.ParseFromString(proxy_data_buf)) { + config_->stats().header_not_found_.inc(); + setMetadataNotFoundFilterState(); + ENVOY_LOG(warn, "Alpn protocol matched. Magic matched. Metadata Not found."); + conn_state_ = Invalid; + return; + } + data.drain(proxy_data_length_); + + // Set Metadata + Protobuf::Struct value_struct = MessageUtil::anyConvert(proxy_data); + auto key_metadata_it = value_struct.fields().find(ExchangeMetadataHeader); + if (key_metadata_it != value_struct.fields().end()) { + updatePeer(*Istio::Common::convertStructToWorkloadMetadata( + key_metadata_it->second.struct_value(), config_->additional_labels_)); + } +} + +void MetadataExchangeFilter::updatePeer(const Istio::Common::WorkloadMetadataObject& value) { + updatePeer(value, config_->filter_direction_); +} + +void MetadataExchangeFilter::updatePeer(const Istio::Common::WorkloadMetadataObject& value, + FilterDirection direction) { + auto filter_state_key = direction == FilterDirection::Downstream ? Istio::Common::DownstreamPeer + : Istio::Common::UpstreamPeer; + auto obj_key = direction == FilterDirection::Downstream ? Istio::Common::DownstreamPeerObj + : Istio::Common::UpstreamPeerObj; + auto pb = value.serializeAsProto(); + auto peer_info = std::make_shared(MetadataExchangeConfig::peerInfoPrototype()); + peer_info->setValue(absl::string_view(pb->SerializeAsString())); + + auto& filter_state = *read_callbacks_->connection().streamInfo().filterState(); + filter_state.setData(filter_state_key, std::move(peer_info), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + + // Also store WorkloadMetadataObject for FIELD accessor and peerInfoRead() detection + auto workload_metadata = std::make_unique(value); + filter_state.setData(obj_key, std::move(workload_metadata), + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); +} + +std::string MetadataExchangeFilter::getMetadataId() { return local_info_.node().id(); } + +void MetadataExchangeFilter::setMetadataNotFoundFilterState() { + if (config_->metadata_provider_) { + Network::Address::InstanceConstSharedPtr upstream_peer; + const StreamInfo::StreamInfo& info = read_callbacks_->connection().streamInfo(); + if (info.upstreamInfo()) { + auto upstream_host = info.upstreamInfo().value().get().upstreamHost(); + if (upstream_host) { + const auto address = upstream_host->address(); + ENVOY_LOG(debug, "Trying to check upstream host info of host {}", address->asString()); + switch (address->type()) { + case Network::Address::Type::Ip: + upstream_peer = upstream_host->address(); + break; + case Network::Address::Type::EnvoyInternal: + if (upstream_host->metadata()) { + ENVOY_LOG(debug, "Trying to check filter metadata of host {}", + upstream_host->address()->asString()); + const auto& filter_metadata = upstream_host->metadata()->filter_metadata(); + const auto& it = filter_metadata.find("envoy.filters.listener.original_dst"); + if (it != filter_metadata.end()) { + const auto& destination_it = it->second.fields().find("local"); + if (destination_it != it->second.fields().end()) { + upstream_peer = Network::Utility::parseInternetAddressAndPortNoThrow( + destination_it->second.string_value(), /*v6only=*/false); + } + } + } + break; + default: + break; + } + } + } + // Get our metadata differently based on the direction of the filter + auto downstream_peer_address = [&]() -> Network::Address::InstanceConstSharedPtr { + if (upstream_peer) { + // Query upstream peer data and save it in metadata for stats + const auto metadata_object = config_->metadata_provider_->GetMetadata(upstream_peer); + if (metadata_object) { + ENVOY_LOG(debug, "Metadata found for upstream peer address {}", + upstream_peer->asString()); + updatePeer(metadata_object.value(), FilterDirection::Upstream); + } + } + + // Regardless, return the downstream address for downstream metadata + return read_callbacks_->connection().connectionInfoProvider().remoteAddress(); + }; + + auto upstream_peer_address = [&]() -> Network::Address::InstanceConstSharedPtr { + if (upstream_peer) { + return upstream_peer; + } + ENVOY_LOG(debug, "Upstream peer address is null. Fall back to localAddress"); + return read_callbacks_->connection().connectionInfoProvider().localAddress(); + }; + const Network::Address::InstanceConstSharedPtr peer_address = + config_->filter_direction_ == FilterDirection::Downstream ? downstream_peer_address() + : upstream_peer_address(); + ENVOY_LOG(debug, "Look up metadata based on peer address {}", peer_address->asString()); + const auto metadata_object = config_->metadata_provider_->GetMetadata(peer_address); + if (metadata_object) { + ENVOY_LOG(trace, "Metadata found for peer address {}", peer_address->asString()); + updatePeer(metadata_object.value()); + config_->stats().metadata_added_.inc(); + return; + } else { + ENVOY_LOG(debug, "Metadata not found for peer address {}", peer_address->asString()); + } + } + read_callbacks_->connection().streamInfo().filterState()->setData( + Istio::Common::NoPeer, std::make_shared(true), + StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); +} + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/source/extensions/filters/network/metadata_exchange/metadata_exchange.h b/source/extensions/filters/network/metadata_exchange/metadata_exchange.h new file mode 100644 index 00000000000..678ccc6b702 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange.h @@ -0,0 +1,182 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "envoy/local_info/local_info.h" +#include "envoy/network/filter.h" +#include "envoy/runtime/runtime.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" +#include "envoy/stream_info/filter_state.h" +#include "source/common/common/stl_helpers.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/filters/common/expr/cel_state.h" +#include "source/extensions/filters/network/metadata_exchange/config/metadata_exchange.pb.h" +#include "source/extensions/common/workload_discovery/api.h" + +#include "extensions/common/metadata_object.h" + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { + +using ::Envoy::Extensions::Filters::Common::Expr::CelStatePrototype; +using ::Envoy::Extensions::Filters::Common::Expr::CelStateType; + +/** + * All MetadataExchange filter stats. @see stats_macros.h + */ +#define ALL_METADATA_EXCHANGE_STATS(COUNTER) \ + COUNTER(alpn_protocol_not_found) \ + COUNTER(alpn_protocol_found) \ + COUNTER(initial_header_not_found) \ + COUNTER(header_not_found) \ + COUNTER(metadata_added) + +/** + * Struct definition for all MetadataExchange stats. @see stats_macros.h + */ +struct MetadataExchangeStats { + ALL_METADATA_EXCHANGE_STATS(GENERATE_COUNTER_STRUCT) +}; + +/** + * Direction of the flow of traffic in which this this MetadataExchange filter + * is placed. + */ +enum class FilterDirection { Downstream, Upstream }; + +/** + * Configuration for the MetadataExchange filter. + */ +class MetadataExchangeConfig { +public: + MetadataExchangeConfig(const std::string& stat_prefix, const std::string& protocol, + const FilterDirection filter_direction, bool enable_discovery, + const absl::flat_hash_set additional_labels, + Server::Configuration::ServerFactoryContext& factory_context, + Stats::Scope& scope); + + const MetadataExchangeStats& stats() { return stats_; } + + // Scope for the stats. + Stats::Scope& scope_; + // Stat prefix. + const std::string stat_prefix_; + // Expected Alpn Protocol. + const std::string protocol_; + // Direction of filter. + const FilterDirection filter_direction_; + // Set if WDS is enabled. + Extensions::Common::WorkloadDiscovery::WorkloadMetadataProviderSharedPtr metadata_provider_; + // Stats for MetadataExchange Filter. + MetadataExchangeStats stats_; + const absl::flat_hash_set additional_labels_; + + static const CelStatePrototype& peerInfoPrototype() { + static const CelStatePrototype* const prototype = new CelStatePrototype( + true, CelStateType::Protobuf, "type.googleapis.com/google.protobuf.Struct", + StreamInfo::FilterState::LifeSpan::Connection); + return *prototype; + } + +private: + MetadataExchangeStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return MetadataExchangeStats{ALL_METADATA_EXCHANGE_STATS(POOL_COUNTER_PREFIX(scope, prefix))}; + } +}; + +using MetadataExchangeConfigSharedPtr = std::shared_ptr; + +/** + * A MetadataExchange filter instance. One per connection. + */ +class MetadataExchangeFilter : public Network::Filter, + protected Logger::Loggable { +public: + MetadataExchangeFilter(MetadataExchangeConfigSharedPtr config, + const LocalInfo::LocalInfo& local_info) + : config_(config), local_info_(local_info), conn_state_(ConnProtocolNotRead) {} + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; + Network::FilterStatus onNewConnection() override; + Network::FilterStatus onWrite(Buffer::Instance& data, bool end_stream) override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + void initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) override { + write_callbacks_ = &callbacks; + } + +private: + // Writes node metadata in write pipeline of the filter chain. + // Also, sets node metadata in Dynamic Metadata to be available for subsequent + // filters. + void writeNodeMetadata(); + + // Tries to read inital proxy header in the data bytes. + void tryReadInitialProxyHeader(Buffer::Instance& data); + + // Tries to read data after initial proxy header. This is currently in the + // form of google::protobuf::any which encapsulates google::protobuf::struct. + void tryReadProxyData(Buffer::Instance& data); + + // Helper function to share the metadata with other filters. + void updatePeer(const Istio::Common::WorkloadMetadataObject& obj, FilterDirection direction); + void updatePeer(const Istio::Common::WorkloadMetadataObject& obj); + + // Helper function to get metadata id. + std::string getMetadataId(); + + // Helper function to set filterstate when no client mxc found. + void setMetadataNotFoundFilterState(); + + // Config for MetadataExchange filter. + MetadataExchangeConfigSharedPtr config_; + // LocalInfo instance. + const LocalInfo::LocalInfo& local_info_; + // Read callback instance. + Network::ReadFilterCallbacks* read_callbacks_{}; + // Write callback instance. + Network::WriteFilterCallbacks* write_callbacks_{}; + // Stores the length of proxy data that contains node metadata. + uint64_t proxy_data_length_{0}; + + const std::string ExchangeMetadataHeader = "x-envoy-peer-metadata"; + const std::string ExchangeMetadataHeaderId = "x-envoy-peer-metadata-id"; + + // Type url of google::protobuf::struct. + const std::string StructTypeUrl = "type.googleapis.com/google.protobuf.Struct"; + + // Captures the state machine of what is going on in the filter. + enum { + ConnProtocolNotRead, // Connection Protocol has not been read yet + WriteMetadata, // Write node metadata + ReadingInitialHeader, // MetadataExchangeInitialHeader is being read + ReadingProxyHeader, // Proxy Header is being read + NeedMoreDataInitialHeader, // Need more data to be read + NeedMoreDataProxyHeader, // Need more data to be read + Done, // Alpn Protocol Found and all the read is done + Invalid, // Invalid state, all operations fail + } conn_state_; +}; + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/include/istio/utils/status.h b/source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.cc similarity index 61% rename from include/istio/utils/status.h rename to source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.cc index d0741a94f39..2a3124fe41c 100644 --- a/include/istio/utils/status.h +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.cc @@ -1,4 +1,4 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. +/* Copyright 2019 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,14 @@ * limitations under the License. */ -#ifndef MIXERCONTROL_UTILS_STATUS_H -#define MIXERCONTROL_UTILS_STATUS_H +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h" -namespace istio { -namespace utils { +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { -// Convert Status::code to HTTP code -int StatusHttpCode(int code); +const uint32_t MetadataExchangeInitialHeader::magic_number; -} // namespace utils -} // namespace istio - -#endif // MIXERCONTROL_UTILS_STATUS_H +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h b/source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h new file mode 100644 index 00000000000..009f5e51712 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h @@ -0,0 +1,37 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "envoy/common/platform.h" + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { + +// Used with MetadataExchangeHeaderProto to be extensible. +PACKED_STRUCT(struct MetadataExchangeInitialHeader { + uint32_t magic; // Magic number in network byte order. Most significant byte + // is placed first. + static const uint32_t magic_number = 0x3D230467; // decimal 1025705063 + uint32_t data_size; // Size of the data blob in network byte order. Most + // significant byte is placed first. +}); + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/filters/network/metadata_exchange/metadata_exchange_test.cc b/source/extensions/filters/network/metadata_exchange/metadata_exchange_test.cc new file mode 100644 index 00000000000..11cf79df273 --- /dev/null +++ b/source/extensions/filters/network/metadata_exchange/metadata_exchange_test.cc @@ -0,0 +1,215 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange.h" + +#include "gmock/gmock.h" +#include "google/protobuf/util/message_differencer.h" +#include "gtest/gtest.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/filters/network/metadata_exchange/metadata_exchange_initial_header.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/server_factory_context.h" + +using ::google::protobuf::util::MessageDifferencer; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Tcp { +namespace MetadataExchange { +namespace { + +MATCHER_P(MapEq, rhs, "") { return MessageDifferencer::Equals(arg, rhs); } + +void ConstructProxyHeaderData(::Envoy::Buffer::OwnedImpl& serialized_header, + Envoy::Protobuf::Any& proxy_header, + MetadataExchangeInitialHeader* initial_header) { + std::string serialized_proxy_header = proxy_header.SerializeAsString(); + memset(initial_header, 0, sizeof(MetadataExchangeInitialHeader)); + initial_header->magic = absl::ghtonl(MetadataExchangeInitialHeader::magic_number); + initial_header->data_size = absl::ghtonl(serialized_proxy_header.length()); + serialized_header.add(::Envoy::Buffer::OwnedImpl{absl::string_view( + reinterpret_cast(initial_header), sizeof(MetadataExchangeInitialHeader))}); + serialized_header.add(::Envoy::Buffer::OwnedImpl{serialized_proxy_header}); +} + +} // namespace + +class MetadataExchangeFilterTest : public testing::Test { +public: + MetadataExchangeFilterTest() { ENVOY_LOG_MISC(info, "test"); } + + void initialize() { initialize(absl::flat_hash_set()); } + + void initialize(absl::flat_hash_set additional_labels) { + config_ = std::make_shared( + stat_prefix_, "istio2", FilterDirection::Downstream, false, additional_labels, context_, + *scope_.rootScope()); + filter_ = std::make_unique(config_, local_info_); + filter_->initializeReadFilterCallbacks(read_filter_callbacks_); + filter_->initializeWriteFilterCallbacks(write_filter_callbacks_); + metadata_node_.set_id("test"); + auto node_metadata_map = metadata_node_.mutable_metadata()->mutable_fields(); + (*node_metadata_map)["namespace"].set_string_value("default"); + (*node_metadata_map)["labels"].set_string_value("{app, details}"); + EXPECT_CALL(read_filter_callbacks_.connection_, streamInfo()) + .WillRepeatedly(ReturnRef(stream_info_)); + EXPECT_CALL(local_info_, node()).WillRepeatedly(ReturnRef(metadata_node_)); + } + + void initializeStructValues() { + (*details_value_.mutable_fields())["namespace"].set_string_value("default"); + (*details_value_.mutable_fields())["labels"].set_string_value("{app, details}"); + + (*productpage_value_.mutable_fields())["namespace"].set_string_value("default"); + (*productpage_value_.mutable_fields())["labels"].set_string_value("{app, productpage}"); + } + + NiceMock context_; + Envoy::Protobuf::Struct details_value_; + Envoy::Protobuf::Struct productpage_value_; + MetadataExchangeConfigSharedPtr config_; + std::unique_ptr filter_; + Stats::IsolatedStoreImpl scope_; + std::string stat_prefix_{"test.metadataexchange"}; + NiceMock read_filter_callbacks_; + NiceMock write_filter_callbacks_; + Network::MockConnection connection_; + NiceMock local_info_; + NiceMock stream_info_; + envoy::config::core::v3::Node metadata_node_; +}; + +TEST_F(MetadataExchangeFilterTest, MetadataExchangeFound) { + initialize(); + initializeStructValues(); + + EXPECT_CALL(read_filter_callbacks_.connection_, nextProtocol()).WillRepeatedly(Return("istio2")); + + ::Envoy::Buffer::OwnedImpl data; + MetadataExchangeInitialHeader initial_header; + Envoy::Protobuf::Any productpage_any_value; + productpage_any_value.set_type_url("type.googleapis.com/google.protobuf.Struct"); + *productpage_any_value.mutable_value() = productpage_value_.SerializeAsString(); + ConstructProxyHeaderData(data, productpage_any_value, &initial_header); + ::Envoy::Buffer::OwnedImpl world{"world"}; + data.add(world); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(data, false)); + EXPECT_EQ(data.toString(), "world"); + + EXPECT_EQ(0UL, config_->stats().initial_header_not_found_.value()); + EXPECT_EQ(0UL, config_->stats().header_not_found_.value()); + EXPECT_EQ(1UL, config_->stats().alpn_protocol_found_.value()); +} + +TEST_F(MetadataExchangeFilterTest, MetadataExchangeAdditionalLabels) { + initialize({"role"}); + initializeStructValues(); + + EXPECT_CALL(read_filter_callbacks_.connection_, nextProtocol()).WillRepeatedly(Return("istio2")); + + ::Envoy::Buffer::OwnedImpl data; + MetadataExchangeInitialHeader initial_header; + Envoy::Protobuf::Any productpage_any_value; + productpage_any_value.set_type_url("type.googleapis.com/google.protobuf.Struct"); + *productpage_any_value.mutable_value() = productpage_value_.SerializeAsString(); + ConstructProxyHeaderData(data, productpage_any_value, &initial_header); + ::Envoy::Buffer::OwnedImpl world{"world"}; + data.add(world); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(data, false)); + EXPECT_EQ(data.toString(), "world"); + + EXPECT_EQ(0UL, config_->stats().initial_header_not_found_.value()); + EXPECT_EQ(0UL, config_->stats().header_not_found_.value()); + EXPECT_EQ(1UL, config_->stats().alpn_protocol_found_.value()); +} + +TEST_F(MetadataExchangeFilterTest, MetadataExchangeNotFound) { + initialize(); + + EXPECT_CALL(read_filter_callbacks_.connection_, nextProtocol()).WillRepeatedly(Return("istio")); + + ::Envoy::Buffer::OwnedImpl data{}; + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(data, false)); + EXPECT_EQ(1UL, config_->stats().alpn_protocol_not_found_.value()); +} + +// Regression test for https://github.com/istio/istio/issues/59183 +// Verifies that TCP metadata exchange stores peer info under both keys: +// - downstream_peer (CelState for CEL expressions) +// - downstream_peer_obj (WorkloadMetadataObject for FIELD accessor and istio_stats peerInfoRead) +TEST_F(MetadataExchangeFilterTest, MetadataExchangeStoresBothFilterStateKeys) { + initialize(); + + EXPECT_CALL(read_filter_callbacks_.connection_, nextProtocol()).WillRepeatedly(Return("istio2")); + + // Create inner peer metadata struct with workload info + // Field names must match constants in metadata_object.h + Envoy::Protobuf::Struct inner_metadata; + (*inner_metadata.mutable_fields())["NAMESPACE"].set_string_value("test-ns"); + (*inner_metadata.mutable_fields())["WORKLOAD_NAME"].set_string_value("test-workload"); + (*inner_metadata.mutable_fields())["CLUSTER_ID"].set_string_value("test-cluster"); + // App name comes from LABELS.app (nested struct) + Envoy::Protobuf::Struct labels; + (*labels.mutable_fields())["app"].set_string_value("test-app"); + *(*inner_metadata.mutable_fields())["LABELS"].mutable_struct_value() = labels; + + // Wrap in outer struct with x-envoy-peer-metadata key (required by filter) + Envoy::Protobuf::Struct outer_metadata; + *(*outer_metadata.mutable_fields())["x-envoy-peer-metadata"].mutable_struct_value() = + inner_metadata; + + ::Envoy::Buffer::OwnedImpl data; + MetadataExchangeInitialHeader initial_header; + Envoy::Protobuf::Any peer_any_value; + peer_any_value.set_type_url("type.googleapis.com/google.protobuf.Struct"); + *peer_any_value.mutable_value() = outer_metadata.SerializeAsString(); + ConstructProxyHeaderData(data, peer_any_value, &initial_header); + data.add(::Envoy::Buffer::OwnedImpl{"payload"}); + + EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(data, false)); + EXPECT_EQ(data.toString(), "payload"); + + // Verify CelState is stored under downstream_peer (for CEL expression compatibility) + const auto* cel_state = stream_info_.filterState() + ->getDataReadOnly( + Istio::Common::DownstreamPeer); + ASSERT_NE(cel_state, nullptr) << "CelState should be stored under downstream_peer"; + + // Verify WorkloadMetadataObject is stored under downstream_peer_obj + // This is critical for istio_stats peerInfoRead() to detect peer metadata + const auto* peer_obj = + stream_info_.filterState()->getDataReadOnly( + Istio::Common::DownstreamPeerObj); + ASSERT_NE(peer_obj, nullptr) + << "WorkloadMetadataObject should be stored under downstream_peer_obj"; + + // Verify the WorkloadMetadataObject has correct data + EXPECT_EQ("test-ns", peer_obj->namespace_name_); + EXPECT_EQ("test-workload", peer_obj->workload_name_); + EXPECT_EQ("test-cluster", peer_obj->cluster_name_); + EXPECT_EQ("test-app", peer_obj->app_name_); +} + +} // namespace MetadataExchange +} // namespace Tcp +} // namespace Envoy diff --git a/source/extensions/filters/network/peer_metadata/BUILD b/source/extensions/filters/network/peer_metadata/BUILD new file mode 100644 index 00000000000..037d2312d37 --- /dev/null +++ b/source/extensions/filters/network/peer_metadata/BUILD @@ -0,0 +1,75 @@ +# Copyright 2026 Istio Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +########################################################################## + +# Ambient Peer Metadata filters +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", +) +load( + "@envoy//bazel:envoy_library.bzl", + "envoy_proto_library", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +envoy_proto_library( + name = "config", + srcs = ["config.proto"], +) + +envoy_cc_library( + name = "peer_metadata", + srcs = [ + "peer_metadata.cc", + ], + hdrs = [ + "peer_metadata.h", + ], + repository = "@envoy", + deps = [ + ":config_cc_proto", + "//extensions/common:metadata_object_lib", + "@envoy//envoy/buffer:buffer_interface", + "@envoy//envoy/local_info:local_info_interface", + "@envoy//envoy/network:address_interface", + "@envoy//envoy/network:filter_interface", + "@envoy//envoy/server:filter_config_interface", + "@envoy//source/common/common:minimal_logger_lib", + "@envoy//source/common/router:string_accessor_lib", + "@envoy//source/common/singleton:const_singleton", + "@envoy//source/common/stream_info:bool_accessor_lib", + "@envoy//source/common/tcp_proxy", + "@envoy//source/extensions/filters/common/expr:cel_state_lib", + "@envoy//source/extensions/filters/network/common:factory_base_lib", + ], +) + +envoy_cc_test( + name = "peer_metadata_test", + srcs = ["peer_metadata_test.cc"], + repository = "@envoy", + deps = [ + ":peer_metadata", + "@abseil-cpp//absl/strings", + "@envoy//envoy/router:string_accessor_interface", + "@envoy//test/mocks/local_info:local_info_mocks", + "@envoy//test/mocks/network:network_mocks", + ], +) diff --git a/source/extensions/filters/network/peer_metadata/config.proto b/source/extensions/filters/network/peer_metadata/config.proto new file mode 100644 index 00000000000..92fb707d823 --- /dev/null +++ b/source/extensions/filters/network/peer_metadata/config.proto @@ -0,0 +1,44 @@ +/* Copyright 2026 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package envoy.extensions.network_filters.peer_metadata; + +message Config { + // What filter state to use to save the baggage value that encodes the proxy + // workload. + // + // The upstream filter that will populate the baggage header in the HBONE + // request should be configured to use the same key. + // + // Why share baggage value via filter state instead of configruing upstream + // filter to use the baggage key value directly? + // + // ztunnel and waypoint have to be aware of the baggage header format, + // because they should be able to parse baggage headers to extract the + // metadata and report the metrics. However, pilot does not need to be aware + // of the baggage encoding yet. + // + // If instead of using custom filter to generate baggage header value we just + // let pilot generate it, it would spread the logic for generating baggage to + // the pilot as well. While not a big deal, if there is no clear reason to do + // it, let's not duplicate the implementation of baggage logic in pilot and + // just re-use the logic we already have in Envoy. + string baggage_key = 1; +} + +message UpstreamConfig { +} diff --git a/source/extensions/filters/network/peer_metadata/peer_metadata.cc b/source/extensions/filters/network/peer_metadata/peer_metadata.cc new file mode 100644 index 00000000000..e06a8cf5c73 --- /dev/null +++ b/source/extensions/filters/network/peer_metadata/peer_metadata.cc @@ -0,0 +1,450 @@ +/* Copyright 2026 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "source/extensions/filters/network/peer_metadata/peer_metadata.h" + +#include + +#include "source/common/router/string_accessor_impl.h" +#include "source/common/stream_info/bool_accessor_impl.h" +#include "source/common/tcp_proxy/tcp_proxy.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace PeerMetadata { + +namespace { + +using CelState = ::Envoy::Extensions::Filters::Common::Expr::CelState; +using CelStateType = ::Envoy::Extensions::Filters::Common::Expr::CelStateType; + +std::string baggageValue(const LocalInfo::LocalInfo& local_info) { + const auto obj = ::Istio::Common::convertStructToWorkloadMetadata(local_info.node().metadata()); + return obj->baggage(); +} + +std::optional internalListenerName(const Network::Address::Instance& address) { + const auto internal = address.envoyInternalAddress(); + if (internal != nullptr) { + return internal->addressId(); + } + return std::nullopt; +} + +bool allowedInternalListener(const Network::Address::Instance& address) { + const auto name = internalListenerName(address); + if (!name) { + return false; + } + // internal_outbound is a listener name used in proxy e2e tests, so we allow it here as well. + return *name == "connect_originate" || *name == "inner_connect_originate" || + *name == "internal_outbound"; +} + +bool discoveryDisabled(const ::envoy::config::core::v3::Metadata& metadata) { + const auto& value = ::Envoy::Config::Metadata::metadataValue( + &metadata, FilterNames::get().Name, FilterNames::get().DisableDiscoveryField); + return value.bool_value(); +} + +} // namespace + +const uint32_t PeerMetadataHeader::magic_number = 0xabcd1234; + +Filter::Filter(const Config& config, const LocalInfo::LocalInfo& local_info) + : config_(config), baggage_(baggageValue(local_info)) {} + +Network::FilterStatus Filter::onData(Buffer::Instance&, bool) { + return Network::FilterStatus::Continue; +} + +Network::FilterStatus Filter::onNewConnection() { + ENVOY_LOG(trace, "New connection from downstream"); + populateBaggage(); + if (disableDiscovery()) { + state_ = PeerMetadataState::PassThrough; + ENVOY_LOG(trace, "Peer metadata discovery disabled via metadata"); + } + return Network::FilterStatus::Continue; +} + +void Filter::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) { + read_callbacks_ = &callbacks; +} + +Network::FilterStatus Filter::onWrite(Buffer::Instance& buffer, bool) { + ENVOY_LOG(trace, "Writing {} bytes to the downstream connection", buffer.length()); + switch (state_) { + case PeerMetadataState::WaitingForData: { + // If we are receiving data for downstream - there is no point in waiting + // for peer metadata anymore, if the upstream sent it, we'd have it by + // now. So we can check if the peer metadata is available or not, and if + // no peer metadata available, we can give up waiting for it. + std::optional peer_metadata = discoverPeerMetadata(); + if (peer_metadata) { + propagatePeerMetadata(*peer_metadata); + } else { + propagateNoPeerMetadata(); + } + state_ = PeerMetadataState::PassThrough; + break; + } + default: + break; + } + return Network::FilterStatus::Continue; +} + +void Filter::initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) { + write_callbacks_ = &callbacks; +} + +void Filter::populateBaggage() { + if (config_.baggage_key().empty()) { + ENVOY_LOG(trace, "Not populating baggage filter state because baggage key is not set"); + return; + } + + ENVOY_LOG(trace, "Populating baggage value {} in the filter state with key {}", baggage_, + config_.baggage_key()); + ASSERT(read_callbacks_); + read_callbacks_->connection().streamInfo().filterState()->setData( + config_.baggage_key(), std::make_shared(baggage_), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::FilterChain); +} + +bool Filter::disableDiscovery() const { + ASSERT(read_callbacks_); + const auto& metadata = read_callbacks_->connection().streamInfo().dynamicMetadata(); + return discoveryDisabled(metadata); +} + +// discoveryPeerMetadata is called to check if the baggage HTTP2 CONNECT +// response headers have been populated already in the filter state. +// +// NOTE: It's safe to call this function during any step of processing - it +// will not do anything if the filter is not in the right state. +std::optional Filter::discoverPeerMetadata() { + ENVOY_LOG(trace, "Trying to discover peer metadata from filter state set by TCP Proxy"); + ASSERT(write_callbacks_); + + const Network::Connection& conn = write_callbacks_->connection(); + const StreamInfo::StreamInfo& stream = conn.streamInfo(); + const TcpProxy::TunnelResponseHeaders* state = + stream.filterState().getDataReadOnly( + TcpProxy::TunnelResponseHeaders::key()); + if (!state) { + ENVOY_LOG(trace, "TCP Proxy didn't set expected filter state"); + return std::nullopt; + } + + const Http::HeaderMap& headers = state->value(); + const auto baggage = headers.get(Headers::get().Baggage); + if (baggage.empty()) { + ENVOY_LOG( + trace, + "TCP Proxy saved response headers to the filter state, but there is no baggage header"); + return std::nullopt; + } + + ENVOY_LOG(trace, + "Successfully discovered peer metadata from the baggage header saved by TCP Proxy"); + + std::string identity{}; + const auto upstream = write_callbacks_->connection().streamInfo().upstreamInfo(); + if (upstream) { + const auto conn = upstream->upstreamSslConnection(); + if (conn) { + identity = absl::StrJoin(conn->uriSanPeerCertificate(), ","); + ENVOY_LOG(trace, "Discovered upstream peer identity to be {}", identity); + } + } + + std::unique_ptr<::Istio::Common::WorkloadMetadataObject> metadata = + ::Istio::Common::convertBaggageToWorkloadMetadata(baggage[0]->value().getStringView(), + identity); + + google::protobuf::Struct data = convertWorkloadMetadataToStruct(*metadata); + google::protobuf::Any wrapped; + wrapped.PackFrom(data); + return wrapped; +} + +void Filter::propagatePeerMetadata(const google::protobuf::Any& peer_metadata) { + ENVOY_LOG(trace, "Sending peer metadata downstream with the data stream"); + ASSERT(write_callbacks_); + + if (state_ != PeerMetadataState::WaitingForData) { + // It's only safe and correct to send the peer metadata downstream with + // the data if we haven't done that already, otherwise the downstream + // could be very confused by the data they received. + ENVOY_LOG(trace, "Filter has already sent the peer metadata downstream"); + return; + } + + std::string data = peer_metadata.SerializeAsString(); + PeerMetadataHeader header{PeerMetadataHeader::magic_number, static_cast(data.size())}; + + Buffer::OwnedImpl buffer{ + std::string_view(reinterpret_cast(&header), sizeof(header))}; + buffer.add(data); + write_callbacks_->injectWriteDataToFilterChain(buffer, false); +} + +void Filter::propagateNoPeerMetadata() { + ENVOY_LOG(trace, "Sending no peer metadata downstream with the data"); + ASSERT(write_callbacks_); + + PeerMetadataHeader header{PeerMetadataHeader::magic_number, 0}; + Buffer::OwnedImpl buffer{ + std::string_view(reinterpret_cast(&header), sizeof(header))}; + write_callbacks_->injectWriteDataToFilterChain(buffer, false); +} + +UpstreamFilter::UpstreamFilter() {} + +Network::FilterStatus UpstreamFilter::onData(Buffer::Instance& buffer, bool end_stream) { + ENVOY_LOG(trace, "Read {} bytes from the upstream connection", buffer.length()); + + switch (state_) { + case PeerMetadataState::WaitingForData: + if (disableDiscovery()) { + state_ = PeerMetadataState::PassThrough; + break; + } + if (consumePeerMetadata(buffer, end_stream)) { + state_ = PeerMetadataState::PassThrough; + } else { + // If we got here it means that we are waiting for more data to arrive. + // NOTE: if error happened, we will not get here, consumePeerMetadata + // will just return true and we will enter PassThrough state. + return Network::FilterStatus::StopIteration; + } + break; + default: + break; + } + + return Network::FilterStatus::Continue; +} + +Network::FilterStatus UpstreamFilter::onNewConnection() { return Network::FilterStatus::Continue; } + +void UpstreamFilter::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) { + callbacks_ = &callbacks; +} + +// We want to enable baggage based peer metadata discovery if all of the following is true +// - the upstream host is an internal listener, and specifically connect_originate or +// inner_connect_originate internal listener - those are the only listeners that +// support baggage-based peer metadata discovery +// - communication with upstream happens in plain text, e.g., there is no TLS upstream +// transport socket or PROXY transport socket there - we need it in the current +// implemetation of the baggage-based peer metadata discovery because we inject peer +// metadata into the data stream and transport sockets that modify the data stream +// interfere with that (NOTE: in the future release we are planning to lift this +// limitation by communicating over shared memory instead). +// +// We can easily check if the upstream host is an internal listener, so checking the first +// condition is easy. We can't easily check the second condition in the filter itself, so +// instead we rely on istiod providing that information in form of the host metadata on the +// endpoint or cluster level. +bool UpstreamFilter::disableDiscovery() const { + ASSERT(callbacks_); + + const auto upstream = callbacks_->connection().streamInfo().upstreamInfo(); + if (!upstream) { + ENVOY_LOG(error, "No upstream information, cannot confirm that upstream uses HBONE"); + return false; + } + + const auto host = upstream->upstreamHost(); + if (!host) { + ENVOY_LOG(error, "No upstream host, cannot confirm that upstream host uses HBONE"); + return false; + } + + if (!allowedInternalListener(*host->address())) { + ENVOY_LOG( + trace, + "Upstream host is not connect_originate or inner_connect_originate internal listener"); + return true; + } + + if (discoveryDisabled(*host->metadata()) || discoveryDisabled(host->cluster().metadata())) { + ENVOY_LOG(trace, "Peer metadata discovery explicitly disabled via metadata"); + return true; + } + + return false; +} + +bool UpstreamFilter::consumePeerMetadata(Buffer::Instance& buffer, bool end_stream) { + ENVOY_LOG(trace, "Trying to consume peer metadata from the data stream"); + using namespace ::Istio::Common; + + ASSERT(callbacks_); + + if (state_ != PeerMetadataState::WaitingForData) { + ENVOY_LOG(trace, "The filter already consumed peer metadata from the data stream"); + return true; + } + + if (buffer.length() < sizeof(PeerMetadataHeader)) { + if (end_stream) { + ENVOY_LOG(trace, "Not enough data in the data stream for peer metadata header and no more " + "data is coming"); + populateNoPeerMetadata(); + return true; + } + ENVOY_LOG(trace, + "Not enough data in the data stream for peer metadata header, waiting for more data"); + return false; + } + + PeerMetadataHeader header; + buffer.copyOut(0, sizeof(PeerMetadataHeader), &header); + + if (header.magic != PeerMetadataHeader::magic_number) { + ENVOY_LOG(trace, "Magic number in the peer metadata header didn't match expected value"); + populateNoPeerMetadata(); + return true; + } + + if (header.data_size == 0) { + ENVOY_LOG(trace, "Peer metadata is empty"); + populateNoPeerMetadata(); + buffer.drain(sizeof(PeerMetadataHeader)); + return true; + } + + const size_t peer_metadata_size = sizeof(PeerMetadataHeader) + header.data_size; + + if (buffer.length() < peer_metadata_size) { + if (end_stream) { + ENVOY_LOG(trace, + "Not enough data in the data stream for peer metadata and no more data is coming"); + populateNoPeerMetadata(); + return true; + } + ENVOY_LOG(trace, "Not enough data in the data stream for peer metadata, waiting for more data"); + return false; + } + + std::string_view data{static_cast(buffer.linearize(peer_metadata_size)), + peer_metadata_size}; + data = data.substr(sizeof(PeerMetadataHeader)); + google::protobuf::Any any; + if (!any.ParseFromArray(data.data(), data.size())) { + ENVOY_LOG(trace, "Failed to parse peer metadata proto from the data stream"); + populateNoPeerMetadata(); + return true; + } + + google::protobuf::Struct peer_metadata; + if (!any.UnpackTo(&peer_metadata)) { + ENVOY_LOG(trace, "Failed to unpack peer metadata struct"); + populateNoPeerMetadata(); + return true; + } + + std::unique_ptr workload = convertStructToWorkloadMetadata(peer_metadata); + populatePeerMetadata(*workload); + buffer.drain(peer_metadata_size); + ENVOY_LOG(trace, "Successfully consumed peer metadata from the data stream"); + return true; +} + +const CelStatePrototype& UpstreamFilter::peerInfoPrototype() { + static const CelStatePrototype* const prototype = new CelStatePrototype( + true, CelStateType::Protobuf, "type.googleapis.com/google.protobuf.Struct", + StreamInfo::FilterState::LifeSpan::Connection); + return *prototype; +} + +void UpstreamFilter::populatePeerMetadata(const ::Istio::Common::WorkloadMetadataObject& peer) { + ENVOY_LOG(trace, "Populating peer metadata in the upstream filter state"); + ASSERT(callbacks_); + + auto proto = ::Istio::Common::convertWorkloadMetadataToStruct(peer); + auto cel = std::make_shared(peerInfoPrototype()); + cel->setValue(std::string_view(proto.SerializeAsString())); + callbacks_->connection().streamInfo().filterState()->setData( + ::Istio::Common::UpstreamPeer, std::move(cel), StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); +} + +void UpstreamFilter::populateNoPeerMetadata() { + ENVOY_LOG(trace, "Populating no peer metadata in the upstream filter state"); + ASSERT(callbacks_); + + callbacks_->connection().streamInfo().filterState()->setData( + ::Istio::Common::NoPeer, std::make_shared(true), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); +} + +ConfigFactory::ConfigFactory() + : Common::ExceptionFreeFactoryBase("envoy.filters.network.peer_metadata", + /*is_termnial*/ false) {} + +absl::StatusOr +ConfigFactory::createFilterFactoryFromProtoTyped(const Config& config, + Server::Configuration::FactoryContext& context) { + return [config, &context](Network::FilterManager& filter_manager) -> void { + const auto& local_info = context.serverFactoryContext().localInfo(); + filter_manager.addFilter(std::make_shared(config, local_info)); + }; +} + +Network::FilterFactoryCb UpstreamConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& config, Server::Configuration::UpstreamFactoryContext&) { + return createFilterFactory(dynamic_cast(config)); +} + +ProtobufTypes::MessagePtr UpstreamConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +std::string UpstreamConfigFactory::name() const { + return "envoy.filters.network.upstream.peer_metadata"; +} + +bool UpstreamConfigFactory::isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::ServerFactoryContext&) { + // This filter must be last filter in the upstream filter chain, so that + // it'd be the first filter to see and process the data coming back, + // because it has to remove the preamble set by the network filter. + return true; +} + +Network::FilterFactoryCb UpstreamConfigFactory::createFilterFactory(const UpstreamConfig&) { + return [](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(std::make_shared()); + }; +} + +namespace { + +REGISTER_FACTORY(ConfigFactory, Server::Configuration::NamedNetworkFilterConfigFactory); +REGISTER_FACTORY(UpstreamConfigFactory, + Server::Configuration::NamedUpstreamNetworkFilterConfigFactory); + +} // namespace + +} // namespace PeerMetadata +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/peer_metadata/peer_metadata.h b/source/extensions/filters/network/peer_metadata/peer_metadata.h new file mode 100644 index 00000000000..a1796e3fbcc --- /dev/null +++ b/source/extensions/filters/network/peer_metadata/peer_metadata.h @@ -0,0 +1,248 @@ +/* Copyright 2026 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * PeerMetadata network and upstream network filters are used in one of ambient + * peer metadata discovery mechanims. The peer metadata discovery mechanism + * these filters are part of relies on peer reporting their own metadata in + * HBONE CONNECT request and response headers. + * + * The purpose of these filters is to extract this metadata from the request/ + * response headers and propagate it to the Istio filters reporting telemetry + * where this metadata will be used as labels. + * + * The filters in this folder are specifically concerned with extracting and + * propagating upstream peer metadata. The working setup includes a combination + * of several filters that together get the job done. + * + * A bit of background, here is a very simplified description of how Istio + * waypoint processes a request: + * + * 1. connect_terminate listener recieves an incoming HBONE connection; + * * it uwraps HBONE tunnel and extracts the data passed inside it; + * * it passes the data inside the HBONE tunnel to a main_internal listener + * that performs the next stage of processing; + * 2. main_internal listener is responsible for parsing the data as L7 data + * (HTTP/gRPC), applying configured L7 policies, picking the endpoint to + * route the request to and reports L7 stats + * * At this level we are processing the incoming request at L7 level and + * have access to things like status of the request and can report + * meaningful metrics; + * * To report in metrics where the request came from and where it went + * after we need to know the details of downstream and upstream peers - + * that's what we call peer metadata; + * * Once we've done with L7 processing of the request, we pass the request + * to the connect_originate (or inner_connect_originate in case of double + * HBONE) listener that will handle the next stage of processing; + * 3. connect_originate - is responsible for wrapping processed L7 traffic into + * an HBONE tunnel and sending it out + * * This stage of processing treats data as a stream of bytes without any + * knowledge of L7 protocol details; + * * It takes the upstream peer address as input an establishes an HBONE + * tunnel to the destination and sends the data via that tunnel. + * + * With that picture in mind, what we want to do is in connect_originate (or + * inner_connect_originate in case of double-HBONE) when we establish HBONE + * tunnel, we want to extract peer metadata from the CONNECT response and + * propagate it to the main_internal. + * + * To establish HBONE tunnel we rely on Envoy TCP Proxy filter, so we don't + * handle HTTP2 CONNECT responses or requests directly, instead we rely on the + * TCP Proxy filter to extract required information from the response and save + * it in the filter state. We then use the custom network filter to take filter + * state proved by TCP Proxy filter, encode it, and send it to main_internal + * *as data* before any actual response data. This is what the network filter + * defined here is responsible for. + * + * In main_internal we use a custom upstream network filter to extract and + * remove the metadata from the data stream and populate filter state that + * could be used by Istio telemetry filters. That's what the upstream network + * filter defined here is responsible for. + * + * Why do we do it this way? Generally in Envoy we use filter state and dynamic + * metadata to communicate additional information between filters. While it's + * possible to propagate filter state from downstream to upstream, i.e., we + * could set filter state in connect_terminate and propagate it to + * main_internal and then to connect_originate, it's not possible to propagate + * filter state from upstream to downstream, i.e., we cannot make filter state + * set in connect_originate available to main_internal directly. That's why we + * push that metadata with the data instead. + */ +#pragma once + +#include +#include + +#include "envoy/local_info/local_info.h" +#include "envoy/network/filter.h" +#include "envoy/server/filter_config.h" +#include "extensions/common/metadata_object.h" +#include "source/common/common/logger.h" +#include "source/common/singleton/const_singleton.h" +#include "source/extensions/filters/common/expr/cel_state.h" +#include "source/extensions/filters/network/common/factory_base.h" +#include "source/extensions/filters/network/peer_metadata/config.pb.h" +#include "source/extensions/filters/network/peer_metadata/config.pb.validate.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace PeerMetadata { + +using Config = ::envoy::extensions::network_filters::peer_metadata::Config; +using UpstreamConfig = ::envoy::extensions::network_filters::peer_metadata::UpstreamConfig; +using CelStatePrototype = ::Envoy::Extensions::Filters::Common::Expr::CelStatePrototype; + +struct HeaderValues { + const Http::LowerCaseString Baggage{"baggage"}; +}; + +using Headers = ConstSingleton; + +struct FilterNameValues { + const std::string Name = "istio.peer_metadata"; + const std::string DisableDiscoveryField = "disable_baggage_discovery"; +}; + +using FilterNames = ConstSingleton; + +enum class PeerMetadataState { + WaitingForData, + PassThrough, +}; + +PACKED_STRUCT(struct PeerMetadataHeader { + uint32_t magic; + static const uint32_t magic_number; + uint32_t data_size; +}); + +/** + * This is a regular network filter that will be installed in the + * connect_originate or inner_connect_originate filter chains. It will take + * baggage header information from filter state (we expect TCP Proxy to + * populate it), collect other details that are missing from the baggage, i.e. + * the upstream peer principle, encode those details into a sequence of bytes + * and will inject it dowstream. + */ +class Filter : public Network::Filter, Logger::Loggable { +public: + Filter(const Config& config, const LocalInfo::LocalInfo& local_info); + + // Network::ReadFilter + Network::FilterStatus onNewConnection() override; + Network::FilterStatus onData(Buffer::Instance&, bool) override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; + + // Network::WriteFilter + Network::FilterStatus onWrite(Buffer::Instance& buffer, bool) override; + void initializeWriteFilterCallbacks(Network::WriteFilterCallbacks& callbacks) override; + +private: + void populateBaggage(); + bool disableDiscovery() const; + std::optional discoverPeerMetadata(); + void propagatePeerMetadata(const google::protobuf::Any& peer_metadata); + void propagateNoPeerMetadata(); + + PeerMetadataState state_ = PeerMetadataState::WaitingForData; + Network::WriteFilterCallbacks* write_callbacks_{}; + Network::ReadFilterCallbacks* read_callbacks_{}; + Config config_; + std::string baggage_; +}; + +/** + * This is an upstream network filter complementing the filter above. It will + * be installed in all the service clusters that may use HBONE (or double + * HBONE) to communicate with the upstream peers and it will parse and remove + * the data injected by the filter above. The parsed peer metadata details will + * be saved in the filter state. + * + * NOTE: This filter has built-in safety checks that would prevent it from + * trying to interpret the actual connection data as peer metadata injected + * by the filter above. However, those checks are rather shallow and rely on a + * bunch of implicit assumptions (i.e., the magic number does not match + * accidentally, the upstream host actually sends back some data that we can + * check, etc). What I'm trying to say is that in correct setup we don't need + * to rely on those checks for correctness and if it's not the case, then we + * definitely have a bug. + */ +class UpstreamFilter : public Network::ReadFilter, Logger::Loggable { +public: + UpstreamFilter(); + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& buffer, bool end_stream) override; + Network::FilterStatus onNewConnection() override; + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; + +private: + bool disableDiscovery() const; + bool consumePeerMetadata(Buffer::Instance& buffer, bool end_stream); + + static const CelStatePrototype& peerInfoPrototype(); + + void populatePeerMetadata(const ::Istio::Common::WorkloadMetadataObject& peer); + void populateNoPeerMetadata(); + + PeerMetadataState state_ = PeerMetadataState::WaitingForData; + Network::ReadFilterCallbacks* callbacks_{}; +}; + +/** + * PeerMetadata network filter factory. + * + * This filter is responsible for collecting peer metadata from filter state + * and other sources, encoding it and passing it downstream before the actual + * data. + */ +class ConfigFactory : public Common::ExceptionFreeFactoryBase { +public: + ConfigFactory(); + +private: + absl::StatusOr + createFilterFactoryFromProtoTyped(const Config& config, + Server::Configuration::FactoryContext& context) override; +}; + +/** + * PeerMetadata upstream network filter factory. + * + * This filter is responsible for detecting the peer metadata passed in the + * data stream, parsing it, populating filter state based on that and finally + * removing it from the data stream, so that downstream filters can process + * the data as usual. + */ +class UpstreamConfigFactory + : public Server::Configuration::NamedUpstreamNetworkFilterConfigFactory { +public: + Network::FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& config, + Server::Configuration::UpstreamFactoryContext&) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + std::string name() const override; + bool isTerminalFilterByProto(const Protobuf::Message&, + Server::Configuration::ServerFactoryContext&) override; + +private: + Network::FilterFactoryCb createFilterFactory(const UpstreamConfig&); +}; + +} // namespace PeerMetadata +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/peer_metadata/peer_metadata_test.cc b/source/extensions/filters/network/peer_metadata/peer_metadata_test.cc new file mode 100644 index 00000000000..4f9962d4ced --- /dev/null +++ b/source/extensions/filters/network/peer_metadata/peer_metadata_test.cc @@ -0,0 +1,503 @@ +/* Copyright 2026 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "source/extensions/filters/network/peer_metadata/peer_metadata.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" + +#include "envoy/router/string_accessor.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "source/common/http/utility.h" +#include "source/common/network/address_impl.h" +#include "source/common/tcp_proxy/tcp_proxy.h" + +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/ssl/mocks.h" +#include "test/mocks/upstream/host.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace PeerMetadata { +namespace { + +constexpr std::string_view defaultBaggageKey = "io.istio.baggage"; +constexpr std::string_view defaultIdentity = "spiffe://cluster.local/ns/default/sa/default"; +constexpr std::string_view defaultBaggage = + "k8s.deployment.name=server,service.name=server-service,service.version=v2,k8s.namespace.name=" + "default,k8s.cluster.name=cluster"; + +using ::testing::Const; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::ReturnRef; + +bool parsePeerMetadataHeader(const std::string& data, PeerMetadataHeader& header) { + if (data.size() < sizeof(header)) { + return false; + } + std::memcpy(&header, data.data(), sizeof(header)); + return true; +} + +bool parsePeerMetadata(const std::string& data, PeerMetadataHeader& header, + google::protobuf::Any& any) { + if (!parsePeerMetadataHeader(data, header)) { + return false; + } + return any.ParseFromArray(data.data() + sizeof(header), data.size() - sizeof(header)); +} + +Config configWithBaggageKey(std::string_view baggage_key) { + Config config; + config.set_baggage_key(baggage_key); + return config; +} + +class PeerMetadataFilterTest : public ::testing::Test { +public: + PeerMetadataFilterTest() {} + + void populateNodeMetadata(std::string_view workload_name, std::string_view workload_type, + std::string_view service_name, std::string_view service_version, + std::string_view ns, std::string_view cluster) { + auto metadata = node_metadata_.mutable_metadata()->mutable_fields(); + (*metadata)["NAMESPACE"].set_string_value(ns); + (*metadata)["CLUSTER_ID"].set_string_value(cluster); + (*metadata)["WORKLOAD_NAME"].set_string_value(workload_name); + (*metadata)["OWNER"].set_string_value(absl::StrCat("kubernetes://apis/apps/v1/namespaces/", ns, + "/", workload_type, "s/", workload_name)); + auto labels = (*metadata)["LABELS"].mutable_struct_value()->mutable_fields(); + (*labels)["service.istio.io/canonical-name"].set_string_value(service_name); + (*labels)["service.istio.io/canonical-revision"].set_string_value(service_version); + } + + void populateTcpProxyResponseHeaders(const Http::ResponseHeaderMap& response_headers) { + auto headers = std::make_shared( + std::make_unique(response_headers)); + auto filter_state = stream_info_.filterState(); + filter_state->setData(TcpProxy::TunnelResponseHeaders::key(), headers, + StreamInfo::FilterState::StateType::Mutable, + StreamInfo::FilterState::LifeSpan::Connection); + } + + void populateUpstreamSans(const std::vector& sans) { + sans_ = sans; + auto ssl = std::make_shared>(); + ON_CALL(*ssl, uriSanPeerCertificate()).WillByDefault(Return(sans_)); + auto upstream = stream_info_.upstreamInfo(); + upstream->setUpstreamSslConnection(ssl); + } + + void disablePeerDiscovery() { + google::protobuf::Struct metadata; + auto fields = metadata.mutable_fields(); + (*fields)[FilterNames::get().DisableDiscoveryField].set_bool_value(true); + (*stream_info_.metadata_.mutable_filter_metadata())[FilterNames::get().Name].MergeFrom( + metadata); + } + + void initialize(const Config& config) { + ON_CALL(local_info_, node()).WillByDefault(ReturnRef(node_metadata_)); + + ON_CALL(read_filter_callbacks_.connection_, streamInfo()) + .WillByDefault(ReturnRef(stream_info_)); + ON_CALL(Const(read_filter_callbacks_.connection_), streamInfo()) + .WillByDefault(ReturnRef(stream_info_)); + ON_CALL(write_filter_callbacks_.connection_, streamInfo()) + .WillByDefault(ReturnRef(stream_info_)); + ON_CALL(Const(write_filter_callbacks_.connection_), streamInfo()) + .WillByDefault(ReturnRef(stream_info_)); + + filter_ = std::make_unique(config, local_info_); + filter_->initializeReadFilterCallbacks(read_filter_callbacks_); + filter_->initializeWriteFilterCallbacks(write_filter_callbacks_); + } + + ::envoy::config::core::v3::Node node_metadata_; + NiceMock local_info_; + NiceMock stream_info_; + NiceMock read_filter_callbacks_; + NiceMock write_filter_callbacks_; + std::vector sans_; + std::unique_ptr filter_; +}; + +TEST_F(PeerMetadataFilterTest, TestPopulateBaggage) { + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(configWithBaggageKey(defaultBaggageKey)); + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + const auto filter_state = stream_info_.filterState(); + EXPECT_TRUE(filter_state->hasDataWithName(defaultBaggageKey)); + const auto* baggage = filter_state->getDataReadOnly(defaultBaggageKey); + EXPECT_EQ(baggage->asString(), + "k8s.deployment.name=workload,service.name=workload-service,service.version=v1,k8s." + "namespace.name=default,k8s.cluster.name=cluster"); +} + +TEST_F(PeerMetadataFilterTest, TestDoNotPopulateBaggage) { + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(Config{}); + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + const auto filter_state = stream_info_.filterState(); + EXPECT_FALSE( + filter_state->hasDataAtOrAboveLifeSpan(StreamInfo::FilterState::LifeSpan::FilterChain)); +} + +TEST_F(PeerMetadataFilterTest, TestPeerMetadataDiscoveredAndPropagated) { + const std::string baggage{defaultBaggage}; + const std::string identity{defaultIdentity}; + + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(configWithBaggageKey(defaultBaggageKey)); + + Http::TestResponseHeaderMapImpl headers{{Headers::get().Baggage.get(), baggage}}; + populateTcpProxyResponseHeaders(headers); + + std::vector sans{identity}; + populateUpstreamSans(sans); + + std::string injected; + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillRepeatedly(Invoke([&injected](Buffer::Instance& buffer, bool) { + injected = std::string(static_cast(buffer.linearize(buffer.length())), + buffer.length()); + })); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + Buffer::OwnedImpl data; + EXPECT_EQ(filter_->onWrite(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_FALSE(injected.empty()); + + PeerMetadataHeader header; + google::protobuf::Any any; + EXPECT_TRUE(parsePeerMetadata(injected, header, any)); + EXPECT_EQ(header.magic, PeerMetadataHeader::magic_number); + EXPECT_GT(header.data_size, 0); + + google::protobuf::Struct metadata; + EXPECT_TRUE(any.UnpackTo(&metadata)); + + std::unique_ptr workload = + Istio::Common::convertStructToWorkloadMetadata(metadata); + EXPECT_EQ(workload->baggage(), baggage); + EXPECT_EQ(workload->identity(), identity); +} + +TEST_F(PeerMetadataFilterTest, TestNoFiltersStateFromTcpProxy) { + const std::string identity{defaultIdentity}; + + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(configWithBaggageKey(defaultBaggageKey)); + + std::vector sans{identity}; + populateUpstreamSans(sans); + + std::string injected; + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillRepeatedly(Invoke([&injected](Buffer::Instance& buffer, bool) { + injected = std::string(static_cast(buffer.linearize(buffer.length())), + buffer.length()); + })); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + Buffer::OwnedImpl data; + EXPECT_EQ(filter_->onWrite(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_FALSE(injected.empty()); + + PeerMetadataHeader header; + EXPECT_TRUE(parsePeerMetadataHeader(injected, header)); + EXPECT_EQ(header.magic, PeerMetadataHeader::magic_number); + EXPECT_EQ(header.data_size, 0); +} + +TEST_F(PeerMetadataFilterTest, TestNoBaggageHeaderFromTcpProxy) { + const std::string baggage{defaultBaggage}; + const std::string identity{defaultIdentity}; + + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(configWithBaggageKey(defaultBaggageKey)); + + Http::TestResponseHeaderMapImpl headers{{"bibbage", baggage}}; + populateTcpProxyResponseHeaders(headers); + + std::vector sans{identity}; + populateUpstreamSans(sans); + + std::string injected; + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillRepeatedly(Invoke([&injected](Buffer::Instance& buffer, bool) { + injected = std::string(static_cast(buffer.linearize(buffer.length())), + buffer.length()); + })); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + Buffer::OwnedImpl data; + EXPECT_EQ(filter_->onWrite(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_FALSE(injected.empty()); + + PeerMetadataHeader header; + EXPECT_TRUE(parsePeerMetadataHeader(injected, header)); + EXPECT_EQ(header.magic, PeerMetadataHeader::magic_number); + EXPECT_EQ(header.data_size, 0); +} + +TEST_F(PeerMetadataFilterTest, TestDisablePeerDiscovery) { + const std::string baggage{defaultBaggage}; + const std::string identity{defaultIdentity}; + + populateNodeMetadata("workload", "deployment", "workload-service", "v1", "default", "cluster"); + initialize(configWithBaggageKey(defaultBaggageKey)); + + Http::TestResponseHeaderMapImpl headers{{Headers::get().Baggage.get(), baggage}}; + populateTcpProxyResponseHeaders(headers); + + std::vector sans{identity}; + populateUpstreamSans(sans); + + disablePeerDiscovery(); + + std::string injected; + ON_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)) + .WillByDefault(Invoke([&injected](Buffer::Instance& buffer, bool) { + injected = std::string(static_cast(buffer.linearize(buffer.length())), + buffer.length()); + })); + EXPECT_CALL(write_filter_callbacks_, injectWriteDataToFilterChain(_, false)).Times(0); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + Buffer::OwnedImpl data; + EXPECT_EQ(filter_->onWrite(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_TRUE(injected.empty()); +} + +std::shared_ptr> +makeInternalListenerHost(std::string_view listener_name) { + auto host = std::make_shared>(); + host->address_ = + std::make_shared(std::string(listener_name)); + ON_CALL(*host, address()).WillByDefault(Return(host->address_)); + return host; +} + +std::shared_ptr> makeIpv4Host(std::string_view address) { + auto host = std::make_shared>(); + host->address_ = std::make_shared(std::string(address)); + ON_CALL(*host, address()).WillByDefault(Return(host->address_)); + return host; +} + +std::string encodePeerMetadataHeaderOnly(const PeerMetadataHeader& header) { + return std::string(std::string_view(reinterpret_cast(&header), sizeof(header))); +} + +std::string encodeMetadataOnly(std::string_view baggage, std::string_view identity) { + std::unique_ptr<::Istio::Common::WorkloadMetadataObject> metadata = + ::Istio::Common::convertBaggageToWorkloadMetadata(baggage, identity); + google::protobuf::Struct data = convertWorkloadMetadataToStruct(*metadata); + google::protobuf::Any wrapped; + wrapped.PackFrom(data); + return wrapped.SerializeAsString(); +} + +std::string encodePeerMetadata(std::string_view baggage, std::string_view identity) { + std::string metadata = encodeMetadataOnly(baggage, identity); + PeerMetadataHeader header{PeerMetadataHeader::magic_number, + static_cast(metadata.size())}; + return encodePeerMetadataHeaderOnly(header) + metadata; +} + +class PeerMetadataUpstreamFilterTest : public ::testing::Test { +public: + PeerMetadataUpstreamFilterTest() {} + + void initialize() { + ON_CALL(callbacks_.connection_, streamInfo()).WillByDefault(ReturnRef(stream_info_)); + ON_CALL(Const(callbacks_.connection_), streamInfo()).WillByDefault(ReturnRef(stream_info_)); + + host_metadata_ = std::make_shared<::envoy::config::core::v3::Metadata>(); + + filter_ = std::make_unique(); + filter_->initializeReadFilterCallbacks(callbacks_); + } + + ::envoy::config::core::v3::Metadata& hostMetadata() { return *host_metadata_; } + + ::envoy::config::core::v3::Metadata& clusterMetadata() { + return upstream_host_->cluster_.metadata_; + } + + std::optional<::Istio::Common::WorkloadMetadataObject> peerInfoFromFilterState() const { + const auto& filter_state = stream_info_.filterState(); + const auto* cel_state = + filter_state.getDataReadOnly( + ::Istio::Common::UpstreamPeer); + if (!cel_state) { + return std::nullopt; + } + + google::protobuf::Struct obj; + if (!obj.ParseFromString(std::string_view(cel_state->value()))) { + return std::nullopt; + } + + std::unique_ptr<::Istio::Common::WorkloadMetadataObject> peer_info = + ::Istio::Common::convertStructToWorkloadMetadata(obj); + if (!peer_info) { + return std::nullopt; + } + + return *peer_info; + } + + void setUpstreamHost(const std::shared_ptr>& host) { + upstream_host_ = host; + ON_CALL(Const(*upstream_host_), metadata()).WillByDefault(Return(host_metadata_)); + stream_info_.upstreamInfo()->setUpstreamHost(upstream_host_); + } + + NiceMock stream_info_; + NiceMock callbacks_; + std::shared_ptr> upstream_host_; + std::shared_ptr<::envoy::config::core::v3::Metadata> host_metadata_; + std::unique_ptr filter_; +}; + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataConsumedAndPropagated) { + initialize(); + setUpstreamHost(makeInternalListenerHost("connect_originate")); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + Buffer::OwnedImpl data; + data.add(encodePeerMetadata(defaultBaggage, defaultIdentity)); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + + EXPECT_EQ(data.length(), 0); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_TRUE(peer_info.has_value()); + EXPECT_EQ(peer_info->identity(), defaultIdentity); + EXPECT_EQ(peer_info->baggage(), defaultBaggage); +} + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataNotConsumedForUnknownInternalListeners) { + initialize(); + setUpstreamHost(makeInternalListenerHost("not_connect_originate")); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + std::string peer_metadata = encodePeerMetadata(defaultBaggage, defaultIdentity); + Buffer::OwnedImpl data; + data.add(peer_metadata); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_EQ(data.length(), peer_metadata.size()); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_FALSE(peer_info.has_value()); +} + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataNotConsumedForExternalHosts) { + initialize(); + setUpstreamHost(makeIpv4Host("192.168.0.1")); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + std::string peer_metadata = encodePeerMetadata(defaultBaggage, defaultIdentity); + Buffer::OwnedImpl data; + data.add(peer_metadata); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + EXPECT_EQ(data.length(), peer_metadata.size()); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_FALSE(peer_info.has_value()); +} + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataNotConsumedWhenDisabledViaHostMetadata) { + initialize(); + setUpstreamHost(makeInternalListenerHost("connect_originate")); + + google::protobuf::Struct metadata; + auto fields = metadata.mutable_fields(); + (*fields)[FilterNames::get().DisableDiscoveryField].set_bool_value(true); + (*hostMetadata().mutable_filter_metadata())[FilterNames::get().Name].MergeFrom(metadata); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + std::string peer_metadata = encodePeerMetadata(defaultBaggage, defaultIdentity); + Buffer::OwnedImpl data; + data.add(peer_metadata); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + + EXPECT_EQ(data.length(), peer_metadata.size()); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_FALSE(peer_info.has_value()); +} + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataNotConsumedWhenDisabledViaClusterMetadata) { + initialize(); + setUpstreamHost(makeInternalListenerHost("connect_originate")); + + google::protobuf::Struct metadata; + auto fields = metadata.mutable_fields(); + (*fields)[FilterNames::get().DisableDiscoveryField].set_bool_value(true); + (*clusterMetadata().mutable_filter_metadata())[FilterNames::get().Name].MergeFrom(metadata); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + std::string peer_metadata = encodePeerMetadata(defaultBaggage, defaultIdentity); + Buffer::OwnedImpl data; + data.add(peer_metadata); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + + EXPECT_EQ(data.length(), peer_metadata.size()); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_FALSE(peer_info.has_value()); +} + +TEST_F(PeerMetadataUpstreamFilterTest, TestPeerMetadataNotConsumedWhenMalformed) { + initialize(); + setUpstreamHost(makeInternalListenerHost("connect_originate")); + + EXPECT_EQ(filter_->onNewConnection(), Network::FilterStatus::Continue); + + std::string malformed{"well hello there, my friend!"}; + Buffer::OwnedImpl data; + data.add(malformed); + EXPECT_EQ(filter_->onData(data, /*end_stream*/ false), Network::FilterStatus::Continue); + + EXPECT_EQ(data.length(), malformed.size()); + const auto peer_info = peerInfoFromFilterState(); + EXPECT_FALSE(peer_info.has_value()); +} + +} // namespace +} // namespace PeerMetadata +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/src/envoy/BUILD b/src/envoy/BUILD deleted file mode 100644 index 45e3251dcb6..00000000000 --- a/src/envoy/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_binary", -) - -envoy_cc_binary( - name = "envoy", - repository = "@envoy", - visibility = ["//visibility:public"], - deps = [ - "//src/envoy/http/authn:filter_lib", - "//src/envoy/http/jwt_auth:http_filter_factory", - "//src/envoy/http/mixer:filter_lib", - "//src/envoy/tcp/mixer:filter_lib", - "//src/envoy/alts:alts_socket_factory", - "@envoy//source/exe:envoy_main_entry_lib", - ], -) - -pkg_tar( - name = "envoy_tar", - extension = "tar.gz", - files = [":envoy"], - mode = "0755", - package_dir = "/usr/local/bin/", -) diff --git a/src/envoy/alts/BUILD b/src/envoy/alts/BUILD deleted file mode 100644 index 0076e57d8e6..00000000000 --- a/src/envoy/alts/BUILD +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", -) - -load( - "@envoy_api//bazel:api_build_system.bzl", - "api_proto_library", -) - -api_proto_library( - name = "alts_socket_proto", - srcs = [":alts_socket.proto"], - visibility = ["//visibility:public"], - require_py = 0, -) - -envoy_cc_library( - name = "grpc_tsi_wrapper", - repository = "@envoy", - visibility = ["//visibility:private"], - hdrs = [ - "transport_security_interface_wrapper.h", - ], - external_deps = [ - "grpc", - ], -) - -envoy_cc_library( - name = "tsi_handshaker", - repository = "@envoy", - visibility = ["//visibility:public"], - srcs = [ - "tsi_handshaker.cc", - ], - hdrs = [ - "tsi_handshaker.h", - ], - deps = [ - ":grpc_tsi_wrapper", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "tsi_frame_protector", - repository = "@envoy", - visibility = ["//visibility:public"], - srcs = [ - "tsi_frame_protector.cc", - ], - hdrs = [ - "tsi_frame_protector.h", - ], - deps = [ - ":grpc_tsi_wrapper", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "tsi_transport_socket", - repository = "@envoy", - visibility = ["//visibility:public"], - srcs = [ - "tsi_transport_socket.cc", - ], - hdrs = [ - "tsi_transport_socket.h", - ], - deps = [ - ":tsi_frame_protector", - ":tsi_handshaker", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "alts_socket_factory", - repository = "@envoy", - visibility = ["//visibility:public"], - srcs = [ - "alts_socket_factory.cc", - ], - hdrs = [ - "alts_socket_factory.h", - ], - deps = [ - ":grpc_tsi_wrapper", - ":tsi_transport_socket", - ":alts_socket_proto_cc", - "@envoy//source/exe:envoy_common_lib", - ], -) diff --git a/src/envoy/alts/README.md b/src/envoy/alts/README.md deleted file mode 100644 index 5eeaa79bb60..00000000000 --- a/src/envoy/alts/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# ALTS support (experimental) - -*The code in this directory is experimental. Do not use in production* - -A prototype of -[ALTS](https://cloud.google.com/security/encryption-in-transit/application-layer-transport-security/) -support for Istio/Envoy. It depends on ALTS stack in gRPC library and implemented as Envoy's -[transport socket](https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/base.proto#core-transportsocket). - -An example config is in `example.yaml`. Note: If you want to enable the peer validation, please -uncomment and replace the content of `peer_service_accounts` with the actual service account in your -environment. Please make sure the service account is correct otherwise the ALTS connection will be -closed due to validation failure. diff --git a/src/envoy/alts/alts_socket.proto b/src/envoy/alts/alts_socket.proto deleted file mode 100644 index 7f7bf7a6c5e..00000000000 --- a/src/envoy/alts/alts_socket.proto +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 Istio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package envoy.security.v2; - -import "google/protobuf/duration.proto"; -import "validate/validate.proto"; - -message AltsSocket { - // The location of a handshaker service, this is usually 169.254.169.254:8080 - // on GCE - string handshaker_service = 1 [(validate.rules).string.min_bytes = 1]; - - // The acceptable service accounts from peer, peers not in the list will be - // rejected in the handshake validation step. - // If empty, no validation will be performed. - repeated string peer_service_accounts = 2; -} diff --git a/src/envoy/alts/alts_socket_factory.cc b/src/envoy/alts/alts_socket_factory.cc deleted file mode 100644 index 824be574eea..00000000000 --- a/src/envoy/alts/alts_socket_factory.cc +++ /dev/null @@ -1,158 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/alts/alts_socket_factory.h" -#include "absl/strings/str_join.h" -#include "common/common/assert.h" -#include "common/protobuf/protobuf.h" -#include "common/protobuf/utility.h" -#include "envoy/registry/registry.h" -#include "envoy/server/transport_socket_config.h" -#include "src/envoy/alts/alts_socket.pb.h" -#include "src/envoy/alts/alts_socket.pb.validate.h" -#include "src/envoy/alts/transport_security_interface_wrapper.h" -#include "src/envoy/alts/tsi_handshaker.h" -#include "src/envoy/alts/tsi_transport_socket.h" - -namespace Envoy { -namespace Server { -namespace Configuration { - -using ::google::protobuf::RepeatedPtrField; - -// Returns true if the peer's service account is found in peers, otherwise -// returns false and fills out err with an error message. -static bool doValidate(const tsi_peer &peer, - const std::unordered_set &peers, - std::string &err) { - for (size_t i = 0; i < peer.property_count; ++i) { - std::string name = std::string(peer.properties[i].name); - std::string value = std::string(peer.properties[i].value.data, - peer.properties[i].value.length); - if (name.compare(TSI_ALTS_SERVICE_ACCOUNT_PEER_PROPERTY) == 0 && - peers.find(value) != peers.end()) { - return true; - } - } - - err = "Couldn't find peer's service account in peer_service_accounts: " + - absl::StrJoin(peers, ","); - return false; -} - -ProtobufTypes::MessagePtr -AltsTransportSocketConfigFactory::createEmptyConfigProto() { - return std::make_unique(); -} - -std::string -Envoy::Server::Configuration::AltsTransportSocketConfigFactory::name() const { - return "alts"; -} - -Network::TransportSocketFactoryPtr -UpstreamAltsTransportSocketConfigFactory::createTransportSocketFactory( - const Protobuf::Message &message, TransportSocketFactoryContext &) { - auto config = - MessageUtil::downcastAndValidate( - message); - - std::string handshaker_service = config.handshaker_service(); - const auto &peer_service_accounts = config.peer_service_accounts(); - std::unordered_set peers(peer_service_accounts.cbegin(), - peer_service_accounts.cend()); - - Security::HandshakeValidator validator; - // Skip validation if peers is empty. - if (!peers.empty()) { - validator = [peers](const tsi_peer &peer, std::string &err) { - return doValidate(peer, peers, err); - }; - } - - return std::make_unique( - [handshaker_service](Event::Dispatcher &dispatcher) { - grpc_alts_credentials_options *options = - grpc_alts_credentials_client_options_create(); - - tsi_handshaker *handshaker = nullptr; - - // Specifying target name as empty since TSI won't take care of - // validating peer identity in this use case. The validation will be - // implemented in TsiSocket later. - alts_tsi_handshaker_create(options, "", handshaker_service.c_str(), - true /* is_client */, &handshaker); - - ASSERT(handshaker != nullptr); - - grpc_alts_credentials_options_destroy(options); - - return std::make_unique(handshaker, - dispatcher); - }, - validator); -} - -Network::TransportSocketFactoryPtr -DownstreamAltsTransportSocketConfigFactory::createTransportSocketFactory( - const std::string &, const std::vector &, bool, - const Protobuf::Message &message, TransportSocketFactoryContext &) { - auto config = - MessageUtil::downcastAndValidate( - message); - - std::string handshaker_service = config.handshaker_service(); - const auto &peer_service_accounts = config.peer_service_accounts(); - std::unordered_set peers(peer_service_accounts.cbegin(), - peer_service_accounts.cend()); - - Security::HandshakeValidator validator; - // Skip validation if peers is empty. - if (!peers.empty()) { - validator = [peers](const tsi_peer &peer, std::string &err) { - return doValidate(peer, peers, err); - }; - } - - return std::make_unique( - [handshaker_service](Event::Dispatcher &dispatcher) { - grpc_alts_credentials_options *options = - grpc_alts_credentials_server_options_create(); - - tsi_handshaker *handshaker = nullptr; - - alts_tsi_handshaker_create(options, nullptr, handshaker_service.c_str(), - false /* is_client */, &handshaker); - - ASSERT(handshaker != nullptr); - - grpc_alts_credentials_options_destroy(options); - - return std::make_unique(handshaker, - dispatcher); - }, - validator); -} - -static Registry::RegisterFactory - upstream_registered_; - -static Registry::RegisterFactory - downstream_registered_; -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/alts/alts_socket_factory.h b/src/envoy/alts/alts_socket_factory.h deleted file mode 100644 index 85f3004bdc6..00000000000 --- a/src/envoy/alts/alts_socket_factory.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "envoy/server/transport_socket_config.h" - -namespace Envoy { -namespace Server { -namespace Configuration { - -// ALTS config registry -class AltsTransportSocketConfigFactory - : public virtual TransportSocketConfigFactory { - public: - ProtobufTypes::MessagePtr createEmptyConfigProto() override; - std::string name() const override; -}; - -class UpstreamAltsTransportSocketConfigFactory - : public AltsTransportSocketConfigFactory, - public UpstreamTransportSocketConfigFactory { - public: - Network::TransportSocketFactoryPtr createTransportSocketFactory( - const Protobuf::Message &, TransportSocketFactoryContext &) override; -}; - -class DownstreamAltsTransportSocketConfigFactory - : public AltsTransportSocketConfigFactory, - public DownstreamTransportSocketConfigFactory { - public: - Network::TransportSocketFactoryPtr createTransportSocketFactory( - const std::string &, const std::vector &, bool, - const Protobuf::Message &, TransportSocketFactoryContext &) override; -}; -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/alts/example.yaml b/src/envoy/alts/example.yaml deleted file mode 100644 index d157684969b..00000000000 --- a/src/envoy/alts/example.yaml +++ /dev/null @@ -1,59 +0,0 @@ -static_resources: - listeners: - - address: - socket_address: - address: 127.0.0.1 - port_value: 5000 - filter_chains: - - filters: - - name: envoy.tcp_proxy - config: - stat_prefix: client_tcp - cluster: server_envoy - - address: - socket_address: - address: 127.0.0.1 - port_value: 5005 - filter_chains: - - transport_socket: - name: alts - config: - handshaker_service: "169.254.169.254:8080" - # If you want to enable the peer validation, please uncomment peer_service_accounts and - # replace it with the actual service account used in your environment. - # peer_service_accounts: ["test-service-account"] - filters: - - name: envoy.tcp_proxy - config: - stat_prefix: server_tcp - cluster: tcp_backend - clusters: - - name: server_envoy - transport_socket: - name: alts - config: - handshaker_service: "169.254.169.254:8080" - # If you want to enable the peer validation, please uncomment peer_service_accounts and - # replace it with the actual service account used in your environment. - # peer_service_accounts: ["test-service-account"] - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - hosts: - - socket_address: - address: 127.0.0.1 - port_value: 5005 - - name: tcp_backend - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - hosts: - - socket_address: - address: 127.0.0.1 - port_value: 5050 -admin: - access_log_path: "/dev/null" - address: - socket_address: - address: 0.0.0.0 - port_value: 8001 diff --git a/src/envoy/alts/transport_security_interface_wrapper.h b/src/envoy/alts/transport_security_interface_wrapper.h deleted file mode 100644 index a3d770fae98..00000000000 --- a/src/envoy/alts/transport_security_interface_wrapper.h +++ /dev/null @@ -1,11 +0,0 @@ -// Some gRPC headers contains old style cast and unused parameter which doesn't -// compile with -Werror, ignoring those compiler warning since we don't have -// control on those source codes. This works with GCC and Clang. - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wold-style-cast" -#include "grpc/grpc_security.h" -#include "src/core/tsi/alts/handshaker/alts_tsi_handshaker.h" -#include "src/core/tsi/transport_security_interface.h" -#pragma GCC diagnostic pop diff --git a/src/envoy/alts/tsi_frame_protector.cc b/src/envoy/alts/tsi_frame_protector.cc deleted file mode 100644 index 94eed84aa98..00000000000 --- a/src/envoy/alts/tsi_frame_protector.cc +++ /dev/null @@ -1,79 +0,0 @@ -#include "src/envoy/alts/tsi_frame_protector.h" - -#include "common/common/assert.h" - -namespace Envoy { -namespace Security { - -TsiFrameProtector::TsiFrameProtector(tsi_frame_protector *frame_protector) - : frame_protector_(frame_protector) {} - -tsi_result TsiFrameProtector::protect(Buffer::Instance &input, - Buffer::Instance &output) { - ASSERT(frame_protector_); - - // TODO(lizan): tune size later - unsigned char protected_buffer[4096]; - size_t protected_buffer_size = sizeof(protected_buffer); - while (input.length() > 0) { - auto *message_bytes = - reinterpret_cast(input.linearize(input.length())); - size_t protected_buffer_size_to_send = protected_buffer_size; - size_t processed_message_size = input.length(); - tsi_result result = tsi_frame_protector_protect( - frame_protector_.get(), message_bytes, &processed_message_size, - protected_buffer, &protected_buffer_size_to_send); - if (result != TSI_OK) { - ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); - return result; - } - output.add(protected_buffer, protected_buffer_size_to_send); - input.drain(processed_message_size); - } - - ASSERT(input.length() == 0); - size_t still_pending_size; - do { - size_t protected_buffer_size_to_send = protected_buffer_size; - tsi_result result = tsi_frame_protector_protect_flush( - frame_protector_.get(), protected_buffer, - &protected_buffer_size_to_send, &still_pending_size); - if (result != TSI_OK) { - ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); - return result; - } - output.add(protected_buffer, protected_buffer_size_to_send); - } while (still_pending_size > 0); - - return TSI_OK; -} - -tsi_result TsiFrameProtector::unprotect(Buffer::Instance &input, - Buffer::Instance &output) { - ASSERT(frame_protector_); - - // TODO(lizan): Tune the buffer size. - unsigned char unprotected_buffer[4096]; - size_t unprotected_buffer_size = sizeof(unprotected_buffer); - - while (input.length() > 0) { - auto *message_bytes = - reinterpret_cast(input.linearize(input.length())); - size_t unprotected_buffer_size_to_send = unprotected_buffer_size; - size_t processed_message_size = input.length(); - tsi_result result = tsi_frame_protector_unprotect( - frame_protector_.get(), message_bytes, &processed_message_size, - unprotected_buffer, &unprotected_buffer_size_to_send); - if (result != TSI_OK) { - ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); - return result; - } - output.add(unprotected_buffer, unprotected_buffer_size_to_send); - input.drain(processed_message_size); - } - - return TSI_OK; -} - -} // namespace Security -} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/alts/tsi_frame_protector.h b/src/envoy/alts/tsi_frame_protector.h deleted file mode 100644 index ccd36a6a9e5..00000000000 --- a/src/envoy/alts/tsi_frame_protector.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include "common/common/c_smart_ptr.h" -#include "envoy/buffer/buffer.h" -#include "envoy/event/dispatcher.h" - -#include "src/envoy/alts/transport_security_interface_wrapper.h" - -namespace Envoy { -namespace Security { - -typedef CSmartPtr - CFrameProtectorPtr; - -/** - * A C++ wrapper for tsi_frame_protector interface. - * For detail of tsi_frame_protector, see - * https://github.com/grpc/grpc/blob/v1.10.0/src/core/tsi/transport_security_interface.h#L70 - * - * TODO(lizan): migrate to tsi_zero_copy_grpc_protector for further optimization - */ -class TsiFrameProtector final { - public: - explicit TsiFrameProtector(tsi_frame_protector* frame_protector); - - /** - * Wrapper for tsi_frame_protector_protect - * @param input supplies the input buffer, the method will drain it when it is - * protected. - * @param output supplies the output buffer - * @return tsi_result the status. - */ - tsi_result protect(Buffer::Instance& input, Buffer::Instance& output); - - /** - * Wrapper for tsi_frame_protector_unprotect - * @param input supplies the input buffer, the method will drain it when it is - * protected. - * @param output supplies the output buffer - * @return tsi_result the status. - */ - tsi_result unprotect(Buffer::Instance& input, Buffer::Instance& output); - - private: - CFrameProtectorPtr frame_protector_; -}; - -typedef std::unique_ptr TsiFrameProtectorPtr; - -} // namespace Security -} // namespace Envoy diff --git a/src/envoy/alts/tsi_handshaker.cc b/src/envoy/alts/tsi_handshaker.cc deleted file mode 100644 index d5139470c39..00000000000 --- a/src/envoy/alts/tsi_handshaker.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/envoy/alts/tsi_handshaker.h" -#include "common/buffer/buffer_impl.h" -#include "common/common/assert.h" - -namespace Envoy { -namespace Security { - -void TsiHandshaker::onNextDone(tsi_result status, void *user_data, - const unsigned char *bytes_to_send, - size_t bytes_to_send_size, - tsi_handshaker_result *handshaker_result) { - TsiHandshaker *handshaker = static_cast(user_data); - - Buffer::InstancePtr to_send = std::make_unique(); - if (bytes_to_send_size > 0) { - to_send->add(bytes_to_send, bytes_to_send_size); - } - - auto next_result = new TsiHandshakerCallbacks::NextResult{ - status, std::move(to_send), {handshaker_result}}; - - handshaker->dispatcher_.post([handshaker, next_result]() { - ASSERT(handshaker->calling_); - handshaker->calling_ = false; - - TsiHandshakerCallbacks::NextResultPtr next_result_ptr{next_result}; - - if (handshaker->delete_on_done_) { - handshaker->dispatcher_.deferredDelete( - Event::DeferredDeletablePtr{handshaker}); - return; - } - handshaker->callbacks_->onNextDone(std::move(next_result_ptr)); - }); -} - -TsiHandshaker::TsiHandshaker(tsi_handshaker *handshaker, - Event::Dispatcher &dispatcher) - : handshaker_(handshaker), dispatcher_(dispatcher) {} - -TsiHandshaker::~TsiHandshaker() { ASSERT(!calling_); } - -tsi_result TsiHandshaker::next(Envoy::Buffer::Instance &received) { - ASSERT(!calling_); - calling_ = true; - - uint64_t received_size = received.length(); - const unsigned char *bytes_to_send = nullptr; - size_t bytes_to_send_size = 0; - tsi_handshaker_result *result = nullptr; - tsi_result status = - tsi_handshaker_next(handshaker_.get(), - reinterpret_cast( - received.linearize(received_size)), - received_size, &bytes_to_send, &bytes_to_send_size, - &result, onNextDone, this); - - if (status != TSI_ASYNC) { - onNextDone(status, this, bytes_to_send, bytes_to_send_size, result); - } - return status; -} - -void TsiHandshaker::deferredDelete() { - if (calling_) { - delete_on_done_ = true; - } else { - dispatcher_.deferredDelete(Event::DeferredDeletablePtr{this}); - } -} -} // namespace Security -} // namespace Envoy diff --git a/src/envoy/alts/tsi_handshaker.h b/src/envoy/alts/tsi_handshaker.h deleted file mode 100644 index 01e8310aba8..00000000000 --- a/src/envoy/alts/tsi_handshaker.h +++ /dev/null @@ -1,112 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include -#include - -#include "common/common/c_smart_ptr.h" -#include "envoy/buffer/buffer.h" -#include "envoy/event/dispatcher.h" - -#include "src/envoy/alts/transport_security_interface_wrapper.h" - -namespace Envoy { -namespace Security { - -typedef CSmartPtr - TsiHandshakerResultPtr; -typedef CSmartPtr CHandshakerPtr; - -/** - * An interface to get callback from TsiHandshaker. - * TsiHandshaker will call this callback in the thread which its dispatcher - * posts. - */ -class TsiHandshakerCallbacks { - public: - virtual ~TsiHandshakerCallbacks() {} - - struct NextResult { - // A enum of the result - tsi_result status_; - - // The buffer to be sent to the peer - Buffer::InstancePtr to_send_; - - // A pointer to tsi_handshaker_result struct. Owned by instance. - TsiHandshakerResultPtr result_; - }; - - typedef std::unique_ptr NextResultPtr; - - /** - * Called when `next` is done, this may be called in line in `next` if the - * handshaker is not - * asynchronous. - * @param result - */ - virtual void onNextDone(NextResultPtr&& result) PURE; -}; - -/** - * A C++ wrapper for tsi_handshaker interface. - * For detail of tsi_handshaker, see - * https://github.com/grpc/grpc/blob/v1.10.0/src/core/tsi/transport_security_interface.h#L236 - */ -class TsiHandshaker final : public Event::DeferredDeletable { - public: - explicit TsiHandshaker(tsi_handshaker* handshaker, - Event::Dispatcher& dispatcher); - ~TsiHandshaker(); - - /** - * Conduct next step of handshake, see - * https://github.com/grpc/grpc/blob/v1.10.0/src/core/tsi/transport_security_interface.h#L416 - * @param received the buffer received from peer. - */ - tsi_result next(Buffer::Instance& received); - - /** - * Set handshaker callbacks, this must be called before calling next. - * @param callbacks supplies the callback instance. - */ - void setHandshakerCallbacks(TsiHandshakerCallbacks& callbacks) { - callbacks_ = &callbacks; - } - - /** - * Delete the handshaker when it is ready. This must be called after releasing - * from a smart - * pointer. The actual delete happens after ongoing next call are processed. - */ - void deferredDelete(); - - private: - static void onNextDone(tsi_result status, void* user_data, - const unsigned char* bytes_to_send, - size_t bytes_to_send_size, - tsi_handshaker_result* handshaker_result); - - CHandshakerPtr handshaker_; - TsiHandshakerCallbacks* callbacks_{nullptr}; - bool calling_{false}; - bool delete_on_done_{false}; - Event::Dispatcher& dispatcher_; -}; - -typedef std::unique_ptr TsiHandshakerPtr; -} // namespace Security -} // namespace Envoy diff --git a/src/envoy/alts/tsi_transport_socket.cc b/src/envoy/alts/tsi_transport_socket.cc deleted file mode 100644 index 9e61ce1efd9..00000000000 --- a/src/envoy/alts/tsi_transport_socket.cc +++ /dev/null @@ -1,228 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/envoy/alts/tsi_transport_socket.h" - -#include "common/common/assert.h" -#include "common/common/enum_to_int.h" - -namespace Envoy { -namespace Security { - -TsiSocket::TsiSocket(HandshakerFactory handshaker_factory, - HandshakeValidator handshake_validator) - : handshaker_factory_(handshaker_factory), - handshake_validator_(handshake_validator), - raw_buffer_callbacks_(*this) { - raw_buffer_socket_.setTransportSocketCallbacks(raw_buffer_callbacks_); -} - -TsiSocket::~TsiSocket() { ASSERT(!handshaker_); } - -void TsiSocket::setTransportSocketCallbacks( - Envoy::Network::TransportSocketCallbacks &callbacks) { - callbacks_ = &callbacks; - - handshaker_ = handshaker_factory_(callbacks.connection().dispatcher()); - handshaker_->setHandshakerCallbacks(*this); -} - -std::string TsiSocket::protocol() const { return ""; } - -Network::PostIoAction TsiSocket::doHandshake() { - ASSERT(!handshake_complete_); - ENVOY_CONN_LOG(debug, "TSI: doHandshake", callbacks_->connection()); - - if (!handshaker_next_calling_) { - doHandshakeNext(); - } - return Network::PostIoAction::KeepOpen; -} - -void TsiSocket::doHandshakeNext() { - ENVOY_CONN_LOG(debug, "TSI: doHandshake next: received: {}", - callbacks_->connection(), raw_read_buffer_.length()); - handshaker_next_calling_ = true; - Buffer::OwnedImpl handshaker_buffer; - handshaker_buffer.move(raw_read_buffer_); - handshaker_->next(handshaker_buffer); -} - -Network::PostIoAction TsiSocket::doHandshakeNextDone( - NextResultPtr &&next_result) { - ASSERT(next_result); - - ENVOY_CONN_LOG(debug, "TSI: doHandshake next done: status: {} to_send: {}", - callbacks_->connection(), next_result->status_, - next_result->to_send_->length()); - - tsi_result status = next_result->status_; - tsi_handshaker_result *handshaker_result = next_result->result_.get(); - - if (status != TSI_INCOMPLETE_DATA && status != TSI_OK) { - ENVOY_CONN_LOG(debug, "TSI: Handshake failed: status: {}", - callbacks_->connection(), status); - return Network::PostIoAction::Close; - } - - if (next_result->to_send_->length() > 0) { - raw_write_buffer_.move(*next_result->to_send_); - } - - if (status == TSI_OK && handshaker_result != nullptr) { - tsi_peer peer; - tsi_handshaker_result_extract_peer(handshaker_result, &peer); - ENVOY_CONN_LOG(debug, "TSI: Handshake successful: peer properties: {}", - callbacks_->connection(), peer.property_count); - for (size_t i = 0; i < peer.property_count; ++i) { - ENVOY_CONN_LOG(debug, " {}: {}", callbacks_->connection(), - peer.properties[i].name, - std::string(peer.properties[i].value.data, - peer.properties[i].value.length)); - } - if (handshake_validator_) { - std::string err; - bool peer_validated = handshake_validator_(peer, err); - if (peer_validated) { - ENVOY_CONN_LOG(info, "TSI: Handshake validation succeeded.", - callbacks_->connection()); - } else { - ENVOY_CONN_LOG(warn, "TSI: Handshake validation failed: {}", - callbacks_->connection(), err); - tsi_peer_destruct(&peer); - return Network::PostIoAction::Close; - } - } else { - ENVOY_CONN_LOG(info, "TSI: Handshake validation skipped.", - callbacks_->connection()); - } - tsi_peer_destruct(&peer); - - const unsigned char *unused_bytes; - size_t unused_byte_size; - - status = tsi_handshaker_result_get_unused_bytes( - handshaker_result, &unused_bytes, &unused_byte_size); - ASSERT(status == TSI_OK); - if (unused_byte_size > 0) { - raw_read_buffer_.add(unused_bytes, unused_byte_size); - } - ENVOY_CONN_LOG(debug, "TSI: Handshake successful: unused_bytes: {}", - callbacks_->connection(), unused_byte_size); - - tsi_frame_protector *frame_protector; - status = tsi_handshaker_result_create_frame_protector( - handshaker_result, NULL, &frame_protector); - ASSERT(status == TSI_OK); - frame_protector_ = std::make_unique(frame_protector); - - handshake_complete_ = true; - callbacks_->raiseEvent(Network::ConnectionEvent::Connected); - } - - if (raw_read_buffer_.length() > 0) { - callbacks_->setReadBufferReady(); - } - return Network::PostIoAction::KeepOpen; -} - -Network::IoResult TsiSocket::doRead(Buffer::Instance &buffer) { - Network::IoResult result = raw_buffer_socket_.doRead(raw_read_buffer_); - ENVOY_CONN_LOG(debug, "TSI: raw read result action {} bytes {} end_stream {}", - callbacks_->connection(), enumToInt(result.action_), - result.bytes_processed_, result.end_stream_read_); - if (result.action_ == Network::PostIoAction::Close && - result.bytes_processed_ == 0) { - return result; - } - - if (!handshake_complete_) { - Network::PostIoAction action = doHandshake(); - if (action == Network::PostIoAction::Close || !handshake_complete_) { - return {action, 0, false}; - } - } - - if (handshake_complete_) { - ASSERT(frame_protector_); - - uint64_t read_size = raw_read_buffer_.length(); - ENVOY_CONN_LOG(debug, "TSI: unprotecting buffer size: {}", - callbacks_->connection(), raw_read_buffer_.length()); - tsi_result status = frame_protector_->unprotect(raw_read_buffer_, buffer); - ENVOY_CONN_LOG(debug, "TSI: unprotected buffer left: {} result: {}", - callbacks_->connection(), raw_read_buffer_.length(), - tsi_result_to_string(status)); - result.bytes_processed_ = read_size - raw_read_buffer_.length(); - } - - ENVOY_CONN_LOG(debug, "TSI: do read result action {} bytes {} end_stream {}", - callbacks_->connection(), enumToInt(result.action_), - result.bytes_processed_, result.end_stream_read_); - return result; -} - -Network::IoResult TsiSocket::doWrite(Buffer::Instance &buffer, - bool end_stream) { - if (!handshake_complete_) { - Network::PostIoAction action = doHandshake(); - if (action == Network::PostIoAction::Close) { - return {action, 0, false}; - } - } - - if (handshake_complete_) { - ASSERT(frame_protector_); - ENVOY_CONN_LOG(debug, "TSI: protecting buffer size: {}", - callbacks_->connection(), buffer.length()); - tsi_result status = frame_protector_->protect(buffer, raw_write_buffer_); - ENVOY_CONN_LOG(debug, "TSI: protected buffer left: {} result: {}", - callbacks_->connection(), buffer.length(), - tsi_result_to_string(status)); - } - - ENVOY_CONN_LOG(debug, "TSI: raw_write length {} end_stream {}", - callbacks_->connection(), raw_write_buffer_.length(), - end_stream); - return raw_buffer_socket_.doWrite(raw_write_buffer_, - end_stream && (buffer.length() == 0)); -} - -void TsiSocket::closeSocket(Network::ConnectionEvent) { - handshaker_.release()->deferredDelete(); -} - -void TsiSocket::onConnected() { ASSERT(!handshake_complete_); } - -void TsiSocket::onNextDone(NextResultPtr &&result) { - handshaker_next_calling_ = false; - - Network::PostIoAction action = doHandshakeNextDone(std::move(result)); - if (action == Network::PostIoAction::Close) { - callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); - } -} - -TsiSocketFactory::TsiSocketFactory(HandshakerFactory handshaker_factory, - HandshakeValidator handshake_validator) - : handshaker_factory_(std::move(handshaker_factory)), - handshake_validator_(std::move(handshake_validator)) {} - -bool TsiSocketFactory::implementsSecureTransport() const { return true; } - -Network::TransportSocketPtr TsiSocketFactory::createTransportSocket() const { - return std::make_unique(handshaker_factory_, handshake_validator_); -} -} // namespace Security -} // namespace Envoy diff --git a/src/envoy/alts/tsi_transport_socket.h b/src/envoy/alts/tsi_transport_socket.h deleted file mode 100644 index cd26b60d3b3..00000000000 --- a/src/envoy/alts/tsi_transport_socket.h +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include "common/buffer/buffer_impl.h" -#include "common/network/raw_buffer_socket.h" -#include "envoy/network/transport_socket.h" -#include "src/envoy/alts/tsi_frame_protector.h" -#include "src/envoy/alts/tsi_handshaker.h" - -namespace Envoy { -namespace Security { - -typedef std::function HandshakerFactory; - -/** - * A function to validate the peer of the connection. - * @param peer the detail peer information of the connection. - * @param err an error message to indicate why the peer is invalid. This is an - * output param that should be populated by the function implementation. - * @return true if the peer is valid or false if the peer is invalid. - */ -typedef std::function - HandshakeValidator; - -/** - * A implementation of Network::TransportSocket based on gRPC TSI - */ -class TsiSocket : public Network::TransportSocket, - public TsiHandshakerCallbacks, - public Logger::Loggable { - public: - /** - * @param handshaker_factory a function to initiate a TsiHandshaker - * @param handshake_validator a function to validate the peer. Called right - * after the handshake completed with peer data to do the peer validation. - * The connection will be closed immediately if it returns false. - */ - TsiSocket(HandshakerFactory handshaker_factory, - HandshakeValidator handshake_validator); - virtual ~TsiSocket(); - - // Network::TransportSocket - void setTransportSocketCallbacks( - Envoy::Network::TransportSocketCallbacks& callbacks) override; - std::string protocol() const override; - bool canFlushClose() override { return handshake_complete_; } - Envoy::Ssl::Connection* ssl() override { return nullptr; } - const Envoy::Ssl::Connection* ssl() const override { return nullptr; } - Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; - void closeSocket(Network::ConnectionEvent event) override; - Network::IoResult doRead(Buffer::Instance& buffer) override; - void onConnected() override; - - // TsiHandshakerCallbacks - void onNextDone(NextResultPtr&& result) override; - - private: - /** - * Callbacks for underlying RawBufferSocket, it proxies fd() and connection() - * but not raising event or flow control since they have to be handled in - * TsiSocket. - */ - class RawBufferCallbacks : public Network::TransportSocketCallbacks { - public: - explicit RawBufferCallbacks(TsiSocket& parent) : parent_(parent) {} - - int fd() const override { return parent_.callbacks_->fd(); } - Network::Connection& connection() override { - return parent_.callbacks_->connection(); - } - bool shouldDrainReadBuffer() override { return false; } - void setReadBufferReady() override {} - void raiseEvent(Network::ConnectionEvent) override {} - - private: - TsiSocket& parent_; - }; - - Network::PostIoAction doHandshake(); - void doHandshakeNext(); - Network::PostIoAction doHandshakeNextDone(NextResultPtr&& next_result); - - HandshakerFactory handshaker_factory_; - HandshakeValidator handshake_validator_; - TsiHandshakerPtr handshaker_{}; - bool handshaker_next_calling_{}; - // TODO(lizan): wrap frame protector in a C++ class - TsiFrameProtectorPtr frame_protector_; - - Envoy::Network::TransportSocketCallbacks* callbacks_{}; - RawBufferCallbacks raw_buffer_callbacks_; - Network::RawBufferSocket raw_buffer_socket_; - - Envoy::Buffer::OwnedImpl raw_read_buffer_; - Envoy::Buffer::OwnedImpl raw_write_buffer_; - bool handshake_complete_{}; -}; - -/** - * An implementation of Network::TransportSocketFactory for TsiSocket - */ -class TsiSocketFactory : public Network::TransportSocketFactory { - public: - TsiSocketFactory(HandshakerFactory handshaker_factory, - HandshakeValidator handshake_validator); - - bool implementsSecureTransport() const override; - Network::TransportSocketPtr createTransportSocket() const override; - - private: - HandshakerFactory handshaker_factory_; - HandshakeValidator handshake_validator_; -}; -} // namespace Security -} // namespace Envoy diff --git a/src/envoy/http/authn/BUILD b/src/envoy/http/authn/BUILD deleted file mode 100644 index 6c9efa35324..00000000000 --- a/src/envoy/http/authn/BUILD +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -package(default_visibility = ["//visibility:public"]) - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_cc_test", - "envoy_cc_test_library", -) - -envoy_cc_library( - name = "authenticator", - srcs = [ - "authenticator_base.cc", - "authn_utils.cc", - "filter_context.cc", - "origin_authenticator.cc", - "peer_authenticator.cc", - ], - hdrs = [ - "authenticator_base.h", - "authn_utils.h", - "filter_context.h", - "origin_authenticator.h", - "peer_authenticator.h", - ], - repository = "@envoy", - deps = [ - "//external:authentication_policy_config_cc_proto", - "//src/envoy/http/jwt_auth:jwt_lib", - "//src/envoy/utils:utils_lib", - "//src/istio/authn:context_proto", - ], -) - -envoy_cc_library( - name = "filter_lib", - srcs = [ - "http_filter.cc", - "http_filter_factory.cc", - ], - hdrs = [ - "http_filter.h", - ], - repository = "@envoy", - deps = [ - ":authenticator", - "//external:authentication_policy_config_cc_proto", - "//src/envoy/utils:authn_lib", - "//src/envoy/utils:utils_lib", - "//src/istio/authn:context_proto", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_test_library( - name = "test_utils", - hdrs = ["test_utils.h"], - repository = "@envoy", - deps = [ - "//src/istio/authn:context_proto", - ], -) - -envoy_cc_test( - name = "filter_context_test", - srcs = ["filter_context_test.cc"], - repository = "@envoy", - deps = [ - ":authenticator", - ":test_utils", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "authenticator_base_test", - srcs = ["authenticator_base_test.cc"], - repository = "@envoy", - deps = [ - ":authenticator", - ":test_utils", - "@envoy//test/mocks/network:network_mocks", - "@envoy//test/mocks/ssl:ssl_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "authn_utils_test", - srcs = ["authn_utils_test.cc"], - repository = "@envoy", - deps = [ - ":authenticator", - ":test_utils", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "peer_authenticator_test", - srcs = ["peer_authenticator_test.cc"], - repository = "@envoy", - deps = [ - ":authenticator", - ":test_utils", - "@envoy//test/mocks/http:http_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "origin_authenticator_test", - srcs = ["origin_authenticator_test.cc"], - repository = "@envoy", - deps = [ - ":authenticator", - ":test_utils", - "@envoy//test/mocks/http:http_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "http_filter_test", - srcs = ["http_filter_test.cc"], - repository = "@envoy", - deps = [ - ":filter_lib", - ":test_utils", - "//external:authentication_policy_config_cc_proto", - "@envoy//source/common/http:header_map_lib", - "@envoy//test/mocks/http:http_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "http_filter_integration_test", - srcs = ["http_filter_integration_test.cc"], - data = glob(["testdata/*"]), - repository = "@envoy", - deps = [ - ":filter_lib", - "@envoy//test/integration:http_integration_lib", - ], -) diff --git a/src/envoy/http/authn/authenticator_base.cc b/src/envoy/http/authn/authenticator_base.cc deleted file mode 100644 index a2dba3de4cc..00000000000 --- a/src/envoy/http/authn/authenticator_base.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/authenticator_base.h" -#include "common/common/assert.h" -#include "src/envoy/http/authn/authn_utils.h" -#include "src/envoy/utils/utils.h" - -using istio::authn::Payload; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -AuthenticatorBase::AuthenticatorBase(FilterContext* filter_context) - : filter_context_(*filter_context) {} - -AuthenticatorBase::~AuthenticatorBase() {} - -bool AuthenticatorBase::validateX509(const iaapi::MutualTls& mtls, - Payload* payload) const { - const Network::Connection* connection = filter_context_.connection(); - if (connection == nullptr) { - // It's wrong if connection does not exist. - return false; - } - // Always try to get principal and set to output if available. - const bool has_user = - connection->ssl() != nullptr && - connection->ssl()->peerCertificatePresented() && - Utils::GetSourceUser(connection, payload->mutable_x509()->mutable_user()); - - // Return value depend on mode: - // - PERMISSIVE: plaintext connection is acceptable, thus return true - // regardless. - // - TLS_PERMISSIVE: tls connection is required, but certificate is optional. - // - STRICT: must be TLS with valid certificate. - switch (mtls.mode()) { - case iaapi::MutualTls::PERMISSIVE: - return true; - case iaapi::MutualTls::TLS_PERMISSIVE: - return connection->ssl() != nullptr; - case iaapi::MutualTls::STRICT: - return has_user; - default: - NOT_REACHED; - } -} - -bool AuthenticatorBase::validateJwt(const iaapi::Jwt& jwt, Payload* payload) { - Envoy::Http::HeaderMap& header = *filter_context()->headers(); - - auto iter = - filter_context()->filter_config().jwt_output_payload_locations().find( - jwt.issuer()); - if (iter == - filter_context()->filter_config().jwt_output_payload_locations().end()) { - ENVOY_LOG(warn, "No JWT payload header location is found for the issuer {}", - jwt.issuer()); - return false; - } - - LowerCaseString header_key(iter->second); - return AuthnUtils::GetJWTPayloadFromHeaders(header, header_key, - payload->mutable_jwt()); -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/authenticator_base.h b/src/envoy/http/authn/authenticator_base.h deleted file mode 100644 index ca5df7ba56a..00000000000 --- a/src/envoy/http/authn/authenticator_base.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "authentication/v1alpha1/policy.pb.h" -#include "common/common/logger.h" -#include "src/envoy/http/authn/filter_context.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// AuthenticatorBase is the base class for authenticator. It provides functions -// to perform individual authentication methods, which can be used to construct -// compound authentication flow. -class AuthenticatorBase : public Logger::Loggable { - public: - AuthenticatorBase(FilterContext* filter_context); - virtual ~AuthenticatorBase(); - - // Perform authentication. - virtual bool run(istio::authn::Payload*) PURE; - - // Validate TLS/MTLS connection and extract authenticated attributes (just - // source user identity for now). Unlike mTLS, TLS connection does not require - // a client certificate. - virtual bool validateX509( - const istio::authentication::v1alpha1::MutualTls& params, - istio::authn::Payload* payload) const; - - // Validates JWT given the jwt params. If JWT is validated, it will extract - // attributes and claims (JwtPayload), returns status SUCCESS. - // Otherwise, returns status FAILED. - virtual bool validateJwt(const istio::authentication::v1alpha1::Jwt& params, - istio::authn::Payload* payload); - - // Mutable accessor to filter context. - FilterContext* filter_context() { return &filter_context_; } - - private: - // Pointer to filter state. Do not own. - FilterContext& filter_context_; -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/authenticator_base_test.cc b/src/envoy/http/authn/authenticator_base_test.cc deleted file mode 100644 index 9c6d69c6510..00000000000 --- a/src/envoy/http/authn/authenticator_base_test.cc +++ /dev/null @@ -1,325 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/authenticator_base.h" -#include "common/common/base64.h" -#include "common/protobuf/protobuf.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "gmock/gmock.h" -#include "src/envoy/http/authn/test_utils.h" -#include "test/mocks/network/mocks.h" -#include "test/mocks/ssl/mocks.h" - -using google::protobuf::util::MessageDifferencer; -using istio::authn::Payload; -using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; -using testing::NiceMock; -using testing::Return; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -const std::string kSecIstioAuthUserInfoHeaderKey = "sec-istio-auth-userinfo"; -const std::string kSecIstioAuthUserinfoHeaderValue = - R"( - { - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "aud": "aud1", - "non-string-will-be-ignored": 1512754205, - "some-other-string-claims": "some-claims-kept" - } - )"; - -class MockAuthenticatorBase : public AuthenticatorBase { - public: - MockAuthenticatorBase(FilterContext* filter_context) - : AuthenticatorBase(filter_context) {} - MOCK_METHOD1(run, bool(Payload*)); -}; - -class ValidateX509Test : public testing::TestWithParam, - public Logger::Loggable { - public: - virtual ~ValidateX509Test() {} - - Http::TestHeaderMapImpl request_headers_{}; - NiceMock connection_{}; - NiceMock ssl_{}; - FilterConfig filter_config_{}; - FilterContext filter_context_{&request_headers_, &connection_, - istio::envoy::config::filter::http::authn:: - v2alpha1::FilterConfig::default_instance()}; - - MockAuthenticatorBase authenticator_{&filter_context_}; - - void SetUp() override { - mtls_params_.set_mode(GetParam()); - payload_ = new Payload(); - } - - void TearDown() override { delete (payload_); } - - protected: - iaapi::MutualTls mtls_params_; - iaapi::Jwt jwt_; - Payload* payload_; - Payload default_payload_; -}; - -TEST_P(ValidateX509Test, PlaintextConnection) { - // Should return false except mode is PERMISSIVE (accept plaintext) - if (GetParam() == iaapi::MutualTls::PERMISSIVE) { - EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); - } else { - EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); - } - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_P(ValidateX509Test, SslConnectionWithNoPeerCert) { - EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(&ssl_)); - EXPECT_CALL(Const(ssl_), peerCertificatePresented()) - .Times(1) - .WillOnce(Return(false)); - - // Should return false except mode is PERMISSIVE (accept plaintext) or - // TLS_PERMISSIVE - if (GetParam() == iaapi::MutualTls::PERMISSIVE || - GetParam() == iaapi::MutualTls::TLS_PERMISSIVE) { - EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); - } else { - EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); - } - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_P(ValidateX509Test, SslConnectionWithPeerCert) { - EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(&ssl_)); - EXPECT_CALL(Const(ssl_), peerCertificatePresented()) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(ssl_, uriSanPeerCertificate()).Times(1).WillOnce(Return("foo")); - EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); - // When client certificate is present on mTLS, authenticated attribute should - // be extracted. - EXPECT_EQ(payload_->x509().user(), "foo"); -} - -TEST_P(ValidateX509Test, SslConnectionWithPeerSpiffeCert) { - EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(&ssl_)); - EXPECT_CALL(Const(ssl_), peerCertificatePresented()) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(ssl_, uriSanPeerCertificate()) - .Times(1) - .WillOnce(Return("spiffe://foo")); - EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); - - // When client certificate is present on mTLS, authenticated attribute should - // be extracted. - EXPECT_EQ(payload_->x509().user(), "foo"); -} - -TEST_P(ValidateX509Test, SslConnectionWithPeerMalformedSpiffeCert) { - EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(&ssl_)); - EXPECT_CALL(Const(ssl_), peerCertificatePresented()) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(ssl_, uriSanPeerCertificate()) - .Times(1) - .WillOnce(Return("spiffe:foo")); - EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); - - // When client certificate is present on mTLS and the spiffe subject format is - // wrong - // ("spiffe:foo" instead of "spiffe://foo"), the user attribute should be - // extracted. - EXPECT_EQ(payload_->x509().user(), "spiffe:foo"); -} - -INSTANTIATE_TEST_CASE_P(ValidateX509Tests, ValidateX509Test, - testing::Values(iaapi::MutualTls::STRICT, - iaapi::MutualTls::TLS_PERMISSIVE, - iaapi::MutualTls::PERMISSIVE)); - -class ValidateJwtTest : public testing::Test, - public Logger::Loggable { - public: - virtual ~ValidateJwtTest() {} - - Http::TestHeaderMapImpl request_headers_{}; - NiceMock connection_{}; - NiceMock ssl_{}; - FilterConfig filter_config_{}; - FilterContext filter_context_{&request_headers_, &connection_, - istio::envoy::config::filter::http::authn:: - v2alpha1::FilterConfig::default_instance()}; - - MockAuthenticatorBase authenticator_{&filter_context_}; - - void SetUp() override { payload_ = new Payload(); } - - void TearDown() override { delete (payload_); } - - Http::TestHeaderMapImpl CreateTestHeaderMap(const std::string& header_key, - const std::string& header_value) { - // The base64 encoding is done through Base64::encode(). - // If the test input has special chars, may need to use the counterpart of - // Base64UrlDecode(). - std::string value_base64 = - Base64::encode(header_value.c_str(), header_value.size()); - return Http::TestHeaderMapImpl{{header_key, value_base64}}; - } - - protected: - iaapi::MutualTls mtls_params_; - iaapi::Jwt jwt_; - Payload* payload_; - Payload default_payload_; -}; - -// TODO: more tests for Jwt. -TEST_F(ValidateJwtTest, ValidateJwtWithNoIstioAuthnConfig) { - jwt_.set_issuer("issuer@foo.com"); - // authenticator_ has empty Istio authn config - // When there is empty Istio authn config, validateJwt() should return - // nullptr and failure. - EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_F(ValidateJwtTest, NoIssuer) { - // no issuer in jwt - google::protobuf::util::JsonParseOptions options; - FilterConfig filter_config; - JsonStringToMessage( - R"({ - "jwt_output_payload_locations": - { - "issuer@foo.com": "sec-istio-auth-userinfo" - } - } - )", - &filter_config, options); - Http::TestHeaderMapImpl empty_request_headers{}; - FilterContext filter_context{&empty_request_headers, &connection_, - filter_config}; - MockAuthenticatorBase authenticator{&filter_context}; - - // When there is no issuer in the JWT config, validateJwt() should return - // nullptr and failure. - EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_F(ValidateJwtTest, EmptyJwtOutputPayloadLocations) { - jwt_.set_issuer("issuer@foo.com"); - Http::TestHeaderMapImpl request_headers_with_jwt = CreateTestHeaderMap( - kSecIstioAuthUserInfoHeaderKey, kSecIstioAuthUserinfoHeaderValue); - google::protobuf::util::JsonParseOptions options; - FilterConfig filter_config; - JsonStringToMessage( - R"({ - "jwt_output_payload_locations": - { - } - } - )", - &filter_config, options); - FilterContext filter_context{&request_headers_with_jwt, &connection_, - filter_config}; - MockAuthenticatorBase authenticator{&filter_context}; - // authenticator has empty jwt_output_payload_locations in Istio authn config - // When there is no matching jwt_output_payload_locations for the issuer in - // the Istio authn config, validateJwt() should return nullptr and failure. - EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_F(ValidateJwtTest, NoJwtInHeader) { - jwt_.set_issuer("issuer@foo.com"); - google::protobuf::util::JsonParseOptions options; - FilterConfig filter_config; - JsonStringToMessage( - R"({ - "jwt_output_payload_locations": - { - "issuer@foo.com": "sec-istio-auth-jwt-output" - } - } - )", - &filter_config, options); - Http::TestHeaderMapImpl empty_request_headers{}; - FilterContext filter_context{&empty_request_headers, &connection_, - filter_config}; - MockAuthenticatorBase authenticator{&filter_context}; - // When there is no JWT in the HTTP header, validateJwt() should return - // nullptr and failure. - EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); - EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); -} - -TEST_F(ValidateJwtTest, JwtInHeader) { - jwt_.set_issuer("issuer@foo.com"); - Http::TestHeaderMapImpl request_headers_with_jwt = CreateTestHeaderMap( - "sec-istio-auth-jwt-output", kSecIstioAuthUserinfoHeaderValue); - google::protobuf::util::JsonParseOptions options; - FilterConfig filter_config; - JsonStringToMessage( - R"({ - "jwt_output_payload_locations": - { - "issuer@foo.com": "sec-istio-auth-jwt-output" - } - } - )", - &filter_config, options); - FilterContext filter_context{&request_headers_with_jwt, &connection_, - filter_config}; - MockAuthenticatorBase authenticator{&filter_context}; - Payload expected_payload; - JsonStringToMessage( - R"({ - "jwt": { - "user": "issuer@foo.com/sub@foo.com", - "audiences": ["aud1"], - "presenter": "", - "claims": { - "aud": "aud1", - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "some-other-string-claims": "some-claims-kept" - }, - raw_claims: "\n {\n \"iss\": \"issuer@foo.com\",\n \"sub\": \"sub@foo.com\",\n \"aud\": \"aud1\",\n \"non-string-will-be-ignored\": 1512754205,\n \"some-other-string-claims\": \"some-claims-kept\"\n }\n " - } - } - )", - &expected_payload, options); - - EXPECT_TRUE(authenticator.validateJwt(jwt_, payload_)); - EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, *payload_)); -} - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/authn_utils.cc b/src/envoy/http/authn/authn_utils.cc deleted file mode 100644 index 78c232e3108..00000000000 --- a/src/envoy/http/authn/authn_utils.cc +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "authn_utils.h" -#include "common/json/json_loader.h" -#include "src/envoy/http/jwt_auth/jwt.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { -// The JWT audience key name -static const std::string kJwtAudienceKey = "aud"; - -// Extract JWT audience into the JwtPayload. -// This function should to be called after the claims are extracted. -void ExtractJwtAudience( - const Envoy::Json::Object& obj, - const ::google::protobuf::Map< ::std::string, ::std::string>& claims, - istio::authn::JwtPayload* payload) { - const std::string& key = kJwtAudienceKey; - // "aud" can be either string array or string. - // First, try as string - if (claims.count(key) > 0) { - payload->add_audiences(claims.at(key)); - return; - } - // Next, try as string array - try { - std::vector aud_vector = obj.getStringArray(key); - for (const std::string aud : aud_vector) { - payload->add_audiences(aud); - } - } catch (Json::Exception& e) { - // Not convertable to string array - } -} -}; // namespace - -// Retrieve the JwtPayload from the HTTP headers with the key -bool AuthnUtils::GetJWTPayloadFromHeaders( - const HeaderMap& headers, const LowerCaseString& jwt_payload_key, - istio::authn::JwtPayload* payload) { - const HeaderEntry* entry = headers.get(jwt_payload_key); - if (!entry) { - ENVOY_LOG(debug, "No JWT payload {} in the header", jwt_payload_key.get()); - return false; - } - std::string value(entry->value().c_str(), entry->value().size()); - // JwtAuth::Base64UrlDecode() is different from Base64::decode(). - std::string payload_str = JwtAuth::Base64UrlDecode(value); - // Return an empty string if Base64 decode fails. - if (payload_str.empty()) { - ENVOY_LOG(error, "Invalid {} header, invalid base64: {}", - jwt_payload_key.get(), value); - return false; - } - *payload->mutable_raw_claims() = payload_str; - ::google::protobuf::Map< ::std::string, ::std::string>* claims = - payload->mutable_claims(); - Envoy::Json::ObjectSharedPtr json_obj; - try { - json_obj = Json::Factory::loadFromString(payload_str); - ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, - json_obj->asJsonString()); - } catch (...) { - return false; - } - - // Extract claims - json_obj->iterate( - [payload](const std::string& key, const Json::Object& obj) -> bool { - ::google::protobuf::Map< ::std::string, ::std::string>* claims = - payload->mutable_claims(); - // In current implementation, only string objects are extracted into - // claims. If call obj.asJsonString(), will get "panic: not reached" - // from json_loader.cc. - try { - // Try as string, will throw execption if object type is not string. - (*claims)[key] = obj.asString(); - } catch (Json::Exception& e) { - } - return true; - }); - // Extract audience - // ExtractJwtAudience() should be called after claims are extracted. - ExtractJwtAudience(*json_obj, payload->claims(), payload); - // Build user - if (claims->count("iss") > 0 && claims->count("sub") > 0) { - payload->set_user((*claims)["iss"] + "/" + (*claims)["sub"]); - } - // Build authorized presenter (azp) - if (claims->count("azp") > 0) { - payload->set_presenter((*claims)["azp"]); - } - return true; -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/authn_utils.h b/src/envoy/http/authn/authn_utils.h deleted file mode 100644 index e156f5eb2c3..00000000000 --- a/src/envoy/http/authn/authn_utils.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/http/header_map.h" -#include "envoy/json/json_object.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// AuthnUtils class provides utility functions used for authentication. -class AuthnUtils : public Logger::Loggable { - public: - // Retrieve the JWT payload from the HTTP header into the output payload map - // Return true if parsing the header payload key succeeds. - // Otherwise, return false. - static bool GetJWTPayloadFromHeaders(const HeaderMap& headers, - const LowerCaseString& jwt_payload_key, - istio::authn::JwtPayload* payload); -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/authn_utils_test.cc b/src/envoy/http/authn/authn_utils_test.cc deleted file mode 100644 index 3663476ab0d..00000000000 --- a/src/envoy/http/authn/authn_utils_test.cc +++ /dev/null @@ -1,179 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/envoy/http/authn/authn_utils.h" -#include "common/common/base64.h" -#include "common/common/utility.h" -#include "src/envoy/http/authn/test_utils.h" -#include "test/test_common/utility.h" - -using google::protobuf::util::MessageDifferencer; -using istio::authn::JwtPayload; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -const LowerCaseString kSecIstioAuthUserInfoHeaderKey("sec-istio-auth-userinfo"); -const std::string kSecIstioAuthUserinfoHeaderValue = - R"( - { - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "aud": "aud1", - "non-string-will-be-ignored": 1512754205, - "some-other-string-claims": "some-claims-kept" - } - )"; -const std::string kSecIstioAuthUserInfoHeaderWithNoAudValue = - R"( - { - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "non-string-will-be-ignored": 1512754205, - "some-other-string-claims": "some-claims-kept" - } - )"; -const std::string kSecIstioAuthUserInfoHeaderWithTwoAudValue = - R"( - { - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "aud": ["aud1", "aud2"], - "non-string-will-be-ignored": 1512754205, - "some-other-string-claims": "some-claims-kept" - } - )"; - -Http::TestHeaderMapImpl CreateTestHeaderMap(const LowerCaseString& header_key, - const std::string& header_value) { - // The base64 encoding is done through Base64::encode(). - // If the test input has special chars, may need to use the counterpart of - // Base64UrlDecode(). - std::string value_base64 = - Base64::encode(header_value.c_str(), header_value.size()); - return Http::TestHeaderMapImpl{{header_key.get(), value_base64}}; -} - -TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderTest) { - JwtPayload payload, expected_payload; - Http::TestHeaderMapImpl request_headers_with_jwt = CreateTestHeaderMap( - kSecIstioAuthUserInfoHeaderKey, kSecIstioAuthUserinfoHeaderValue); - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( - R"( - user: "issuer@foo.com/sub@foo.com" - audiences: ["aud1"] - claims { - key: "aud" - value: "aud1" - } - claims { - key: "iss" - value: "issuer@foo.com" - } - claims { - key: "sub" - value: "sub@foo.com" - } - claims { - key: "some-other-string-claims" - value: "some-claims-kept" - } - raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserinfoHeaderValue) + R"(")", - &expected_payload)); - // The payload returned from GetJWTPayloadFromHeaders() should be the same as - // the expected. - bool ret = AuthnUtils::GetJWTPayloadFromHeaders( - request_headers_with_jwt, kSecIstioAuthUserInfoHeaderKey, &payload); - EXPECT_TRUE(ret); - EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); -} - -TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderWithNoAudTest) { - JwtPayload payload, expected_payload; - Http::TestHeaderMapImpl request_headers_with_jwt = - CreateTestHeaderMap(kSecIstioAuthUserInfoHeaderKey, - kSecIstioAuthUserInfoHeaderWithNoAudValue); - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( - R"( - user: "issuer@foo.com/sub@foo.com" - claims { - key: "iss" - value: "issuer@foo.com" - } - claims { - key: "sub" - value: "sub@foo.com" - } - claims { - key: "some-other-string-claims" - value: "some-claims-kept" - } - raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserInfoHeaderWithNoAudValue) + - R"(")", - &expected_payload)); - // The payload returned from GetJWTPayloadFromHeaders() should be the same as - // the expected. When there is no aud, the aud is not saved in the payload - // and claims. - bool ret = AuthnUtils::GetJWTPayloadFromHeaders( - request_headers_with_jwt, kSecIstioAuthUserInfoHeaderKey, &payload); - EXPECT_TRUE(ret); - EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); -} - -TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderWithTwoAudTest) { - JwtPayload payload, expected_payload; - Http::TestHeaderMapImpl request_headers_with_jwt = - CreateTestHeaderMap(kSecIstioAuthUserInfoHeaderKey, - kSecIstioAuthUserInfoHeaderWithTwoAudValue); - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( - R"( - user: "issuer@foo.com/sub@foo.com" - audiences: "aud1" - audiences: "aud2" - claims { - key: "iss" - value: "issuer@foo.com" - } - claims { - key: "sub" - value: "sub@foo.com" - } - claims { - key: "some-other-string-claims" - value: "some-claims-kept" - } - raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserInfoHeaderWithTwoAudValue) + - R"(")", - &expected_payload)); - - // The payload returned from GetJWTPayloadFromHeaders() should be the same as - // the expected. When the aud is a string array, the aud is not saved in the - // claims. - bool ret = AuthnUtils::GetJWTPayloadFromHeaders( - request_headers_with_jwt, kSecIstioAuthUserInfoHeaderKey, &payload); - EXPECT_TRUE(ret); - EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); -} - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/filter_context.cc b/src/envoy/http/authn/filter_context.cc deleted file mode 100644 index e6192e54f6c..00000000000 --- a/src/envoy/http/authn/filter_context.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/filter_context.h" -#include "src/envoy/utils/utils.h" - -using istio::authn::Payload; -using istio::authn::Result; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -void FilterContext::setPeerResult(const Payload* payload) { - if (payload != nullptr) { - switch (payload->payload_case()) { - case Payload::kX509: - result_.set_peer_user(payload->x509().user()); - break; - case Payload::kJwt: - result_.set_peer_user(payload->jwt().user()); - break; - default: - ENVOY_LOG(warn, - "Source authentiation payload doesn't contain x509 nor jwt " - "payload."); - break; - } - } -} -void FilterContext::setOriginResult(const Payload* payload) { - // Authentication pass, look at the return payload and store to the context - // output. Set filter to continueDecoding when done. - // At the moment, only JWT can be used for origin authentication, so - // it's ok just to check jwt payload. - if (payload != nullptr && payload->has_jwt()) { - *result_.mutable_origin() = payload->jwt(); - } -} - -void FilterContext::setPrincipal(const iaapi::PrincipalBinding& binding) { - switch (binding) { - case iaapi::PrincipalBinding::USE_PEER: - result_.set_principal(result_.peer_user()); - return; - case iaapi::PrincipalBinding::USE_ORIGIN: - result_.set_principal(result_.origin().user()); - return; - default: - // Should never come here. - ENVOY_LOG(error, "Invalid binding value {}", binding); - return; - } -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/filter_context.h b/src/envoy/http/authn/filter_context.h deleted file mode 100644 index 7907479d55f..00000000000 --- a/src/envoy/http/authn/filter_context.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "authentication/v1alpha1/policy.pb.h" -#include "common/common/logger.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "envoy/http/header_map.h" -#include "envoy/network/connection.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// FilterContext holds inputs, such as request header and connection and -// result data for authentication process. -class FilterContext : public Logger::Loggable { - public: - FilterContext( - HeaderMap* headers, const Network::Connection* connection, - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config) - : headers_(headers), - connection_(connection), - filter_config_(filter_config) {} - virtual ~FilterContext() {} - - // Sets peer result based on authenticated payload. Input payload can be null, - // which basically changes nothing. - void setPeerResult(const istio::authn::Payload* payload); - - // Sets origin result based on authenticated payload. Input payload can be - // null, which basically changes nothing. - void setOriginResult(const istio::authn::Payload* payload); - - // Sets principal based on binding rule, and the existing peer and origin - // result. - void setPrincipal( - const istio::authentication::v1alpha1::PrincipalBinding& binding); - - // Returns the authentication result. - const istio::authn::Result& authenticationResult() { return result_; } - - // Accessor to headers. - HeaderMap* headers() { return headers_; } - // Accessor to connection - const Network::Connection* connection() { return connection_; } - // Accessor to the filter config - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config() const { - return filter_config_; - } - - private: - // Pointer to the headers of the request. - HeaderMap* headers_; - - // Pointer to network connection of the request. - const Network::Connection* connection_; - - // Holds authentication attribute outputs. - istio::authn::Result result_; - - // Store the Istio authn filter config. - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config_; -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/filter_context_test.cc b/src/envoy/http/authn/filter_context_test.cc deleted file mode 100644 index c00f3514ea0..00000000000 --- a/src/envoy/http/authn/filter_context_test.cc +++ /dev/null @@ -1,126 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/filter_context.h" -#include "src/envoy/http/authn/test_utils.h" -#include "test/test_common/utility.h" - -using istio::authn::Payload; -using testing::StrictMock; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -class FilterContextTest : public testing::Test { - public: - virtual ~FilterContextTest() {} - - // This test suit does not use headers nor connection, so ok to use null for - // them. - FilterContext filter_context_{nullptr, nullptr, - istio::envoy::config::filter::http::authn:: - v2alpha1::FilterConfig::default_instance()}; - - Payload x509_payload_{TestUtilities::CreateX509Payload("foo")}; - Payload jwt_payload_{TestUtilities::CreateJwtPayload("bar", "istio.io")}; -}; - -TEST_F(FilterContextTest, SetPeerResult) { - filter_context_.setPeerResult(&x509_payload_); - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString("peer_user: \"foo\""), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, SetOriginResult) { - filter_context_.setOriginResult(&jwt_payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - origin { - user: "bar" - presenter: "istio.io" - } - )"), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, SetBoth) { - filter_context_.setPeerResult(&x509_payload_); - filter_context_.setOriginResult(&jwt_payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - peer_user: "foo" - origin { - user: "bar" - presenter: "istio.io" - } - )"), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, UseOrigin) { - filter_context_.setPeerResult(&x509_payload_); - filter_context_.setOriginResult(&jwt_payload_); - filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - principal: "bar" - peer_user: "foo" - origin { - user: "bar" - presenter: "istio.io" - } - )"), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, UseOriginOnEmptyOrigin) { - filter_context_.setPeerResult(&x509_payload_); - filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - peer_user: "foo" - )"), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, PrincipalUsePeer) { - filter_context_.setPeerResult(&x509_payload_); - filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - principal: "foo" - peer_user: "foo" - )"), - filter_context_.authenticationResult())); -} - -TEST_F(FilterContextTest, PrincipalUsePeerOnEmptyPeer) { - filter_context_.setOriginResult(&jwt_payload_); - filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( - origin { - user: "bar" - presenter: "istio.io" - } - )"), - filter_context_.authenticationResult())); -} - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/http_filter.cc b/src/envoy/http/authn/http_filter.cc deleted file mode 100644 index e8fe41be1d2..00000000000 --- a/src/envoy/http/authn/http_filter.cc +++ /dev/null @@ -1,138 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/http_filter.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "common/http/utility.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "src/envoy/http/authn/origin_authenticator.h" -#include "src/envoy/http/authn/peer_authenticator.h" -#include "src/envoy/utils/authn.h" -#include "src/envoy/utils/utils.h" - -using istio::authn::Payload; -using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -AuthenticationFilter::AuthenticationFilter(const FilterConfig& filter_config) - : filter_config_(filter_config) {} - -AuthenticationFilter::~AuthenticationFilter() {} - -void AuthenticationFilter::onDestroy() { - ENVOY_LOG(debug, "Called AuthenticationFilter : {}", __func__); -} - -FilterHeadersStatus AuthenticationFilter::decodeHeaders(HeaderMap& headers, - bool) { - ENVOY_LOG(debug, "Called AuthenticationFilter : {}", __func__); - state_ = State::PROCESSING; - - filter_context_.reset(new Istio::AuthN::FilterContext( - &headers, decoder_callbacks_->connection(), filter_config_)); - - Payload payload; - - if (!filter_config_.policy().peer_is_optional() && - !createPeerAuthenticator(filter_context_.get())->run(&payload)) { - rejectRequest("Peer authentication failed."); - return FilterHeadersStatus::StopIteration; - } - - bool success = - filter_config_.policy().origin_is_optional() || - createOriginAuthenticator(filter_context_.get())->run(&payload); - - // After Istio authn, the JWT headers consumed by Istio authn should be - // removed. - // TODO: remove internal headers used to pass data between filters - // https://github.com/istio/istio/issues/4689 - for (auto const iter : filter_config_.jwt_output_payload_locations()) { - filter_context_->headers()->remove(LowerCaseString(iter.second)); - } - - if (!success) { - rejectRequest("Origin authentication failed."); - return FilterHeadersStatus::StopIteration; - } - - // Put authentication result to headers. - if (filter_context_ != nullptr) { - Utils::Authentication::SaveResultToHeader( - filter_context_->authenticationResult(), filter_context_->headers()); - } - state_ = State::COMPLETE; - return FilterHeadersStatus::Continue; -} - -FilterDataStatus AuthenticationFilter::decodeData(Buffer::Instance&, bool) { - ENVOY_LOG(debug, "Called AuthenticationFilter : {}", __func__); - ENVOY_LOG(debug, - "Called AuthenticationFilter : {} FilterDataStatus::Continue;", - __FUNCTION__); - if (state_ == State::PROCESSING) { - return FilterDataStatus::StopIterationAndWatermark; - } - return FilterDataStatus::Continue; -} - -FilterTrailersStatus AuthenticationFilter::decodeTrailers(HeaderMap&) { - ENVOY_LOG(debug, "Called AuthenticationFilter : {}", __func__); - if (state_ == State::PROCESSING) { - return FilterTrailersStatus::StopIteration; - } - return FilterTrailersStatus::Continue; -} - -void AuthenticationFilter::setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) { - ENVOY_LOG(debug, "Called AuthenticationFilter : {}", __func__); - decoder_callbacks_ = &callbacks; -} - -void AuthenticationFilter::rejectRequest(const std::string& message) { - if (state_ != State::PROCESSING) { - ENVOY_LOG(error, "State {} is not PROCESSING.", state_); - return; - } - state_ = State::REJECTED; - Utility::sendLocalReply(*decoder_callbacks_, false, Http::Code::Unauthorized, - message); -} - -std::unique_ptr -AuthenticationFilter::createPeerAuthenticator( - Istio::AuthN::FilterContext* filter_context) { - return std::make_unique( - filter_context, filter_config_.policy()); -} - -std::unique_ptr -AuthenticationFilter::createOriginAuthenticator( - Istio::AuthN::FilterContext* filter_context) { - return std::make_unique( - filter_context, filter_config_.policy()); -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/http_filter.h b/src/envoy/http/authn/http_filter.h deleted file mode 100644 index a710cfc0559..00000000000 --- a/src/envoy/http/authn/http_filter.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "envoy/http/filter.h" -#include "src/envoy/http/authn/authenticator_base.h" -#include "src/envoy/http/authn/filter_context.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// The authentication filter. -class AuthenticationFilter : public StreamDecoderFilter, - public Logger::Loggable { - public: - AuthenticationFilter( - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - config); - ~AuthenticationFilter(); - - // Http::StreamFilterBase - void onDestroy() override; - - // Http::StreamDecoderFilter - FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool) override; - FilterDataStatus decodeData(Buffer::Instance&, bool) override; - FilterTrailersStatus decodeTrailers(HeaderMap&) override; - void setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) override; - - protected: - // Convenient function to call decoder_callbacks_ only when stopped_ is true. - void continueDecoding(); - - // Convenient function to reject request. - void rejectRequest(const std::string& message); - - // Creates peer authenticator. This is made virtual function for - // testing. - virtual std::unique_ptr - - createPeerAuthenticator(Istio::AuthN::FilterContext* filter_context); - - // Creates origin authenticator. - virtual std::unique_ptr - - createOriginAuthenticator(Istio::AuthN::FilterContext* filter_context); - - private: - // Store the config. - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config_; - - StreamDecoderFilterCallbacks* decoder_callbacks_{}; - - enum State { INIT, PROCESSING, COMPLETE, REJECTED }; - // Holds the state of the filter. - State state_{State::INIT}; - - // Context for authentication process. Created in decodeHeader to start - // authentication process. - std::unique_ptr filter_context_; -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/http_filter_factory.cc b/src/envoy/http/authn/http_filter_factory.cc deleted file mode 100644 index dcf344fac78..00000000000 --- a/src/envoy/http/authn/http_filter_factory.cc +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "envoy/registry/registry.h" -#include "envoy/server/filter_config.h" -#include "google/protobuf/util/json_util.h" -#include "src/envoy/http/authn/http_filter.h" -#include "src/envoy/utils/utils.h" - -using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; - -namespace Envoy { -namespace Server { -namespace Configuration { - -namespace { -// The name for the Istio authentication filter. -const std::string kAuthnFactoryName("istio_authn"); -} // namespace - -class AuthnFilterConfig : public NamedHttpFilterConfigFactory, - public Logger::Loggable { - public: - Http::FilterFactoryCb createFilterFactory(const Json::Object& config, - const std::string&, - FactoryContext&) override { - ENVOY_LOG(debug, "Called AuthnFilterConfig : {}", __func__); - - google::protobuf::util::Status status = - Utils::ParseJsonMessage(config.asJsonString(), &filter_config_); - ENVOY_LOG(debug, "Called AuthnFilterConfig : Utils::ParseJsonMessage()"); - if (status.ok()) { - return createFilter(); - } else { - ENVOY_LOG(critical, "Utils::ParseJsonMessage() return value is: " + - status.ToString()); - throw EnvoyException( - "In createFilterFactory(), Utils::ParseJsonMessage() return value " - "is: " + - status.ToString()); - } - } - - Http::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message& proto_config, const std::string&, - FactoryContext&) override { - filter_config_ = dynamic_cast(proto_config); - return createFilter(); - } - - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - ENVOY_LOG(debug, "Called AuthnFilterConfig : {}", __func__); - return ProtobufTypes::MessagePtr{new FilterConfig}; - } - - std::string name() override { return kAuthnFactoryName; } - - private: - Http::FilterFactoryCb createFilter() { - ENVOY_LOG(debug, "Called AuthnFilterConfig : {}", __func__); - - return [&](Http::FilterChainFactoryCallbacks& callbacks) -> void { - callbacks.addStreamDecoderFilter( - std::make_shared( - filter_config_)); - }; - } - - FilterConfig filter_config_; -}; - -/** - * Static registration for the Authn filter. @see RegisterFactory. - */ -static Registry::RegisterFactory - register_; - -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/http/authn/http_filter_integration_test.cc b/src/envoy/http/authn/http_filter_integration_test.cc deleted file mode 100644 index 5690ef7766c..00000000000 --- a/src/envoy/http/authn/http_filter_integration_test.cc +++ /dev/null @@ -1,229 +0,0 @@ -#include "common/common/base64.h" -#include "src/istio/authn/context.pb.h" -#include "test/integration/http_integration.h" - -using google::protobuf::util::MessageDifferencer; -using istio::authn::Payload; -using istio::authn::Result; - -namespace Envoy { -namespace { -const std::string kSecIstioAuthUserInfoHeaderKey = "sec-istio-auth-userinfo"; -const std::string kSecIstioAuthUserinfoHeaderValue = - "eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OH" - "Z2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3" - "NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLm" - "dzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJib29rc3RvcmUtZXNwLWVjaG8uY2xv" - "dWRlbmRwb2ludHNhcGlzLmNvbSIsImlhdCI6MTUxMjc1NDIwNSwiZXhwIjo1MTEyNz" - "U0MjA1fQ=="; -const Envoy::Http::LowerCaseString kSecIstioAuthnPayloadHeaderKey( - "sec-istio-authn-payload"); - -class AuthenticationFilterIntegrationTest - : public HttpIntegrationTest, - public testing::TestWithParam { - public: - AuthenticationFilterIntegrationTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()), - default_request_headers_{ - {":method", "GET"}, {":path", "/"}, {":authority", "host"}}, - request_headers_with_jwt_{{":method", "GET"}, - {":path", "/"}, - {":authority", "host"}, - {kSecIstioAuthUserInfoHeaderKey, - kSecIstioAuthUserinfoHeaderValue}} {} - - void SetUp() override { - fake_upstreams_.emplace_back( - new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); - registerPort("upstream_0", - fake_upstreams_.back()->localAddress()->ip()->port()); - fake_upstreams_.emplace_back( - new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); - registerPort("upstream_1", - fake_upstreams_.back()->localAddress()->ip()->port()); - } - - void TearDown() override { - test_server_.reset(); - fake_upstreams_.clear(); - } - - protected: - Http::TestHeaderMapImpl default_request_headers_; - Http::TestHeaderMapImpl request_headers_with_jwt_; -}; - -INSTANTIATE_TEST_CASE_P( - IpVersions, AuthenticationFilterIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); - -TEST_P(AuthenticationFilterIntegrationTest, EmptyPolicy) { - createTestServer("src/envoy/http/authn/testdata/envoy_empty.conf", {"http"}); - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest(default_request_headers_, *response_); - // Wait for request to upstream[0] (backend) - waitForNextUpstreamRequest(0); - // Send backend response. - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, - true); - - response_->waitForEndStream(); - EXPECT_TRUE(response_->complete()); - EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); -} - -TEST_P(AuthenticationFilterIntegrationTest, SourceMTlsFail) { - createTestServer("src/envoy/http/authn/testdata/envoy_peer_mtls.conf", - {"http"}); - - // AuthN filter use MTls, but request doesn't have certificate, request - // would be rejected. - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest(default_request_headers_, *response_); - - // Request is rejected, there will be no upstream request (thus no - // waitForNextUpstreamRequest). - response_->waitForEndStream(); - EXPECT_TRUE(response_->complete()); - EXPECT_STREQ("401", response_->headers().Status()->value().c_str()); -} - -// TODO (diemtvu/lei-tang): add test for MTls success. - -TEST_P(AuthenticationFilterIntegrationTest, OriginJwtRequiredHeaderNoJwtFail) { - createTestServer( - "src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf", - {"http"}); - - // The AuthN filter requires JWT, but request doesn't have JWT, request - // would be rejected. - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest(default_request_headers_, *response_); - - // Request is rejected, there will be no upstream request (thus no - // waitForNextUpstreamRequest). - response_->waitForEndStream(); - EXPECT_TRUE(response_->complete()); - EXPECT_STREQ("401", response_->headers().Status()->value().c_str()); -} - -TEST_P(AuthenticationFilterIntegrationTest, CheckValidJwtPassAuthentication) { - createTestServer( - "src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf", - {"http"}); - - // The AuthN filter requires JWT. The http request contains validated JWT and - // the authentication should succeed. - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest(request_headers_with_jwt_, *response_); - - // Wait for request to upstream[0] (backend) - waitForNextUpstreamRequest(0); - // Send backend response. - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, - true); - - response_->waitForEndStream(); - EXPECT_TRUE(response_->complete()); - EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); -} - -TEST_P(AuthenticationFilterIntegrationTest, CheckConsumedJwtHeadersAreRemoved) { - const Envoy::Http::LowerCaseString header_location( - "location-to-read-jwt-result"); - const std::string jwt_header = - R"( - { - "iss": "issuer@foo.com", - "sub": "sub@foo.com", - "aud": "aud1", - "non-string-will-be-ignored": 1512754205, - "some-other-string-claims": "some-claims-kept" - } - )"; - std::string jwt_header_base64 = - Base64::encode(jwt_header.c_str(), jwt_header.size()); - Http::TestHeaderMapImpl request_headers_with_jwt_at_specified_location{ - {":method", "GET"}, - {":path", "/"}, - {":authority", "host"}, - {"location-to-read-jwt-result", jwt_header_base64}}; - // In this config, the JWT verification result for "issuer@foo.com" is in the - // header "location-to-read-jwt-result" - createTestServer( - "src/envoy/http/authn/testdata/" - "envoy_jwt_with_output_header_location.conf", - {"http"}); - // The AuthN filter requires JWT and the http request contains validated JWT. - // In this case, the authentication should succeed and an authn result - // should be generated. - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest( - request_headers_with_jwt_at_specified_location, *response_); - - // Wait for request to upstream[0] (backend) - waitForNextUpstreamRequest(0); - - // After Istio authn, the JWT headers consumed by Istio authn should have - // been removed. - EXPECT_TRUE(nullptr == upstream_request_->headers().get(header_location)); -} - -TEST_P(AuthenticationFilterIntegrationTest, CheckAuthnResultIsExpected) { - createTestServer( - "src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf", - {"http"}); - - // The AuthN filter requires JWT and the http request contains validated JWT. - // In this case, the authentication should succeed and an authn result - // should be generated. - codec_client_ = - makeHttpConnection(makeClientConnection((lookupPort("http")))); - codec_client_->makeHeaderOnlyRequest(request_headers_with_jwt_, *response_); - - // Wait for request to upstream[0] (backend) - waitForNextUpstreamRequest(0); - - // Authn result should be as expected - const Envoy::Http::HeaderString &header_value = - upstream_request_->headers().get(kSecIstioAuthnPayloadHeaderKey)->value(); - std::string value_base64(header_value.c_str(), header_value.size()); - const std::string value = Base64::decode(value_base64); - Result result; - google::protobuf::util::JsonParseOptions options; - Result expected_result; - - bool parse_ret = result.ParseFromString(value); - EXPECT_TRUE(parse_ret); - JsonStringToMessage( - R"( - { - "origin": { - "user": "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com/628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "audiences": [ - "bookstore-esp-echo.cloudendpointsapis.com" - ], - "presenter": "", - "claims": { - "aud": "bookstore-esp-echo.cloudendpointsapis.com", - "iss": "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "sub": "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com" - }, - raw_claims: "{\"iss\":\"628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com\",\"sub\":\"628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com\",\"aud\":\"bookstore-esp-echo.cloudendpointsapis.com\",\"iat\":1512754205,\"exp\":5112754205}" - } - } - )", - &expected_result, options); - // Note: TestUtility::protoEqual() uses SerializeAsString() and the output - // is non-deterministic. Thus, MessageDifferencer::Equals() is used. - EXPECT_TRUE(MessageDifferencer::Equals(expected_result, result)); -} - -} // namespace -} // namespace Envoy diff --git a/src/envoy/http/authn/http_filter_test.cc b/src/envoy/http/authn/http_filter_test.cc deleted file mode 100644 index 79b39d7e76a..00000000000 --- a/src/envoy/http/authn/http_filter_test.cc +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/http_filter.h" -#include "common/common/base64.h" -#include "common/http/header_map_impl.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "src/envoy/http/authn/authenticator_base.h" -#include "src/envoy/http/authn/test_utils.h" -#include "src/envoy/utils/authn.h" -#include "test/mocks/http/mocks.h" -#include "test/test_common/utility.h" - -using Envoy::Http::Istio::AuthN::AuthenticatorBase; -using Envoy::Http::Istio::AuthN::FilterContext; -using istio::authn::Payload; -using istio::authn::Result; -using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; -using testing::Invoke; -using testing::NiceMock; -using testing::StrictMock; -using testing::_; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -const char ingoreBothPolicy[] = R"( - peer_is_optional: true - origin_is_optional: true -)"; - -// Create a fake authenticator for test. This authenticator do nothing except -// making the authentication fail. -std::unique_ptr createAlwaysFailAuthenticator( - FilterContext* filter_context) { - class _local : public AuthenticatorBase { - public: - _local(FilterContext* filter_context) : AuthenticatorBase(filter_context) {} - bool run(Payload*) override { return false; } - }; - return std::make_unique<_local>(filter_context); -} - -// Create a fake authenticator for test. This authenticator do nothing except -// making the authentication successful. -std::unique_ptr createAlwaysPassAuthenticator( - FilterContext* filter_context) { - class _local : public AuthenticatorBase { - public: - _local(FilterContext* filter_context) : AuthenticatorBase(filter_context) {} - bool run(Payload*) override { - // Set some data to verify authentication result later. - auto payload = TestUtilities::CreateX509Payload("foo"); - filter_context()->setPeerResult(&payload); - return true; - } - }; - return std::make_unique<_local>(filter_context); -} - -class MockAuthenticationFilter : public AuthenticationFilter { - public: - // We'll use fake authenticator for test, so policy is not really needed. Use - // default config for simplicity. - MockAuthenticationFilter(const FilterConfig& filter_config) - : AuthenticationFilter(filter_config) {} - - ~MockAuthenticationFilter(){}; - - MOCK_METHOD1(createPeerAuthenticator, - std::unique_ptr(FilterContext*)); - MOCK_METHOD1(createOriginAuthenticator, - std::unique_ptr(FilterContext*)); -}; - -class AuthenticationFilterTest : public testing::Test { - public: - AuthenticationFilterTest() - : request_headers_{{":method", "GET"}, {":path", "/"}} {} - ~AuthenticationFilterTest() {} - - void SetUp() override { - filter_.setDecoderFilterCallbacks(decoder_callbacks_); - } - - protected: - FilterConfig filter_config_ = FilterConfig::default_instance(); - - Http::TestHeaderMapImpl request_headers_; - StrictMock filter_{filter_config_}; - NiceMock decoder_callbacks_; -}; - -TEST_F(AuthenticationFilterTest, PeerFail) { - // Peer authentication fail, request should be rejected with 401. No origin - // authentiation needed. - EXPECT_CALL(filter_, createPeerAuthenticator(_)) - .Times(1) - .WillOnce(Invoke(createAlwaysFailAuthenticator)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) - .Times(1) - .WillOnce(testing::Invoke([](Http::HeaderMap& headers, bool) { - EXPECT_STREQ("401", headers.Status()->value().c_str()); - })); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, - filter_.decodeHeaders(request_headers_, true)); - EXPECT_FALSE(Utils::Authentication::HasResultInHeader(request_headers_)); -} - -TEST_F(AuthenticationFilterTest, PeerPassOrginFail) { - // Peer pass thus origin authentication must be called. Final result should - // fail as origin authn fails. - EXPECT_CALL(filter_, createPeerAuthenticator(_)) - .Times(1) - .WillOnce(Invoke(createAlwaysPassAuthenticator)); - EXPECT_CALL(filter_, createOriginAuthenticator(_)) - .Times(1) - .WillOnce(Invoke(createAlwaysFailAuthenticator)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) - .Times(1) - .WillOnce(testing::Invoke([](Http::HeaderMap& headers, bool) { - EXPECT_STREQ("401", headers.Status()->value().c_str()); - })); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, - filter_.decodeHeaders(request_headers_, true)); - EXPECT_FALSE(Utils::Authentication::HasResultInHeader(request_headers_)); -} - -TEST_F(AuthenticationFilterTest, AllPass) { - EXPECT_CALL(filter_, createPeerAuthenticator(_)) - .Times(1) - .WillOnce(Invoke(createAlwaysPassAuthenticator)); - EXPECT_CALL(filter_, createOriginAuthenticator(_)) - .Times(1) - .WillOnce(Invoke(createAlwaysPassAuthenticator)); - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - filter_.decodeHeaders(request_headers_, true)); - Result authn; - EXPECT_TRUE( - Utils::Authentication::FetchResultFromHeader(request_headers_, &authn)); - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), authn)); -} - -TEST_F(AuthenticationFilterTest, IgnoreBothFail) { - iaapi::Policy policy_; - ASSERT_TRUE( - Protobuf::TextFormat::ParseFromString(ingoreBothPolicy, &policy_)); - *filter_config_.mutable_policy() = policy_; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, - filter_.decodeHeaders(request_headers_, true)); -} - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/origin_authenticator.cc b/src/envoy/http/authn/origin_authenticator.cc deleted file mode 100644 index dca6d48af69..00000000000 --- a/src/envoy/http/authn/origin_authenticator.cc +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/origin_authenticator.h" -#include "authentication/v1alpha1/policy.pb.h" - -using istio::authn::Payload; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -OriginAuthenticator::OriginAuthenticator(FilterContext* filter_context, - const iaapi::Policy& policy) - : AuthenticatorBase(filter_context), policy_(policy) {} - -bool OriginAuthenticator::run(Payload* payload) { - bool success = false; - - if (policy_.origins_size() == 0) { - switch (policy_.principal_binding()) { - case iaapi::PrincipalBinding::USE_ORIGIN: - // Validation should reject policy that have rule to USE_ORIGIN but - // does not provide any origin method so this code should - // never reach. However, it's ok to treat it as authentication - // fails. - ENVOY_LOG(warn, - "Principal is binded to origin, but no method specified in " - "policy {}", - policy_.DebugString()); - break; - case iaapi::PrincipalBinding::USE_PEER: - // On the other hand, it's ok to have no (origin) methods if - // rule USE_SOURCE - success = true; - break; - default: - // Should never come here. - ENVOY_LOG(error, "Invalid binding value for policy {}", - policy_.DebugString()); - break; - } - } - - for (const auto& method : policy_.origins()) { - if (validateJwt(method.jwt(), payload)) { - success = true; - break; - } - } - - if (success) { - filter_context()->setOriginResult(payload); - filter_context()->setPrincipal(policy_.principal_binding()); - } - - return success; -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/origin_authenticator.h b/src/envoy/http/authn/origin_authenticator.h deleted file mode 100644 index b935198a3c6..00000000000 --- a/src/envoy/http/authn/origin_authenticator.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn/authenticator_base.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// OriginAuthenticator performs origin authentication for given credential rule. -class OriginAuthenticator : public AuthenticatorBase { - public: - OriginAuthenticator(FilterContext* filter_context, - const istio::authentication::v1alpha1::Policy& policy); - - bool run(istio::authn::Payload*) override; - - private: - // Reference to the authentication policy that the authenticator should - // enforce. Typically, the actual object is owned by filter. - const istio::authentication::v1alpha1::Policy& policy_; -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/origin_authenticator_test.cc b/src/envoy/http/authn/origin_authenticator_test.cc deleted file mode 100644 index 8aeaa85215f..00000000000 --- a/src/envoy/http/authn/origin_authenticator_test.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/origin_authenticator.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "common/http/header_map_impl.h" -#include "common/protobuf/protobuf.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "src/envoy/http/authn/test_utils.h" -#include "test/mocks/http/mocks.h" -#include "test/test_common/utility.h" - -namespace iaapi = istio::authentication::v1alpha1; - -using istio::authn::Payload; -using istio::authn::Result; -using testing::DoAll; -using testing::MockFunction; -using testing::NiceMock; -using testing::Return; -using testing::SetArgPointee; -using testing::StrictMock; -using testing::_; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -const char kSingleOriginMethodPolicy[] = R"( - principal_binding: USE_ORIGIN - origins { - jwt { - issuer: "abc.xyz" - } - } -)"; - -const char kMultipleOriginMethodsPolicy[] = R"( - principal_binding: USE_ORIGIN - origins { - jwt { - issuer: "one" - } - } - origins { - jwt { - issuer: "two" - } - } - origins { - jwt { - issuer: "three" - } - } -)"; - -const char kPeerBinding[] = R"( - principal_binding: USE_PEER - origins { - jwt { - issuer: "abc.xyz" - } - } -)"; - -class MockOriginAuthenticator : public OriginAuthenticator { - public: - MockOriginAuthenticator(FilterContext* filter_context, - const iaapi::Policy& policy) - : OriginAuthenticator(filter_context, policy) {} - - MOCK_CONST_METHOD2(validateX509, bool(const iaapi::MutualTls&, Payload*)); - MOCK_METHOD2(validateJwt, bool(const iaapi::Jwt&, Payload*)); -}; - -class OriginAuthenticatorTest : public testing::TestWithParam { - public: - OriginAuthenticatorTest() - : request_headers_{{":method", "GET"}, {":path", "/"}} {} - virtual ~OriginAuthenticatorTest() {} - - void SetUp() override { - expected_result_when_pass_ = TestUtilities::AuthNResultFromString(R"( - principal: "foo" - origin { - user: "foo" - presenter: "istio.io" - } - )"); - set_peer_ = GetParam(); - if (set_peer_) { - auto peer_result = TestUtilities::CreateX509Payload("bar"); - filter_context_.setPeerResult(&peer_result); - expected_result_when_pass_.set_peer_user("bar"); - } - initial_result_ = filter_context_.authenticationResult(); - payload_ = new Payload(); - } - - void TearDown() override { delete (payload_); } - - void createAuthenticator() { - authenticator_.reset( - new StrictMock(&filter_context_, policy_)); - } - - protected: - std::unique_ptr> authenticator_; - Http::TestHeaderMapImpl request_headers_; - FilterContext filter_context_{&request_headers_, nullptr, - istio::envoy::config::filter::http::authn:: - v2alpha1::FilterConfig::default_instance()}; - iaapi::Policy policy_; - - Payload* payload_; - - // Mock response payload. - Payload jwt_payload_{TestUtilities::CreateJwtPayload("foo", "istio.io")}; - Payload jwt_extra_payload_{ - TestUtilities::CreateJwtPayload("bar", "istio.io")}; - - // Expected result (when authentication pass with mock payload above) - Result expected_result_when_pass_; - // Copy of authN result (from filter context) before running authentication. - // This should be the expected result if authn fail or do nothing. - Result initial_result_; - - // Indicates peer is set in the authN result before running. This is set from - // test GetParam() - bool set_peer_; -}; - -TEST_P(OriginAuthenticatorTest, Empty) { - createAuthenticator(); - authenticator_->run(payload_); - if (set_peer_) { - initial_result_.set_principal("bar"); - } - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, SingleMethodPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, - &policy_)); - - createAuthenticator(); - - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, SingleMethodFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, - &policy_)); - - createAuthenticator(); - - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, Multiple) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( - kMultipleOriginMethodsPolicy, &policy_)); - - createAuthenticator(); - - // First method fails, second success (thus third is ignored) - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(2) - .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, MultipleFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( - kMultipleOriginMethodsPolicy, &policy_)); - - createAuthenticator(); - - // All fail. - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(3) - .WillRepeatedly( - DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, PeerBindingPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); - // Expected principal is from peer_user. - expected_result_when_pass_.set_principal(initial_result_.peer_user()); - - createAuthenticator(); - - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, - filter_context_.authenticationResult())); -} - -TEST_P(OriginAuthenticatorTest, PeerBindingFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); - createAuthenticator(); - - // All fail. - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, - filter_context_.authenticationResult())); -} - -INSTANTIATE_TEST_CASE_P(OriginAuthenticatorTests, OriginAuthenticatorTest, - testing::Bool()); - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/peer_authenticator.cc b/src/envoy/http/authn/peer_authenticator.cc deleted file mode 100644 index f946d439698..00000000000 --- a/src/envoy/http/authn/peer_authenticator.cc +++ /dev/null @@ -1,71 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/peer_authenticator.h" -#include "common/http/utility.h" -#include "src/envoy/utils/utils.h" - -using istio::authn::Payload; - -namespace iaapi = istio::authentication::v1alpha1; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -PeerAuthenticator::PeerAuthenticator(FilterContext* filter_context, - const iaapi::Policy& policy) - : AuthenticatorBase(filter_context), policy_(policy) {} - -bool PeerAuthenticator::run(Payload* payload) { - bool success = false; - - if (policy_.peers_size() == 0) { - ENVOY_LOG(debug, "No method defined. Skip source authentication."); - success = true; - return success; - } - for (const auto& method : policy_.peers()) { - switch (method.params_case()) { - case iaapi::PeerAuthenticationMethod::ParamsCase::kMtls: - success = validateX509(method.mtls(), payload); - break; - case iaapi::PeerAuthenticationMethod::ParamsCase::kJwt: - success = validateJwt(method.jwt(), payload); - break; - default: - ENVOY_LOG(error, "Unknown peer authentication param {}", - method.DebugString()); - success = false; - break; - } - - if (success) { - break; - } - } - - if (success) { - filter_context()->setPeerResult(payload); - } - - return success; -} - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/peer_authenticator.h b/src/envoy/http/authn/peer_authenticator.h deleted file mode 100644 index e078421b979..00000000000 --- a/src/envoy/http/authn/peer_authenticator.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn/authenticator_base.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { - -// PeerAuthenticator performs peer authentication for given policy. -class PeerAuthenticator : public AuthenticatorBase { - public: - PeerAuthenticator(FilterContext* filter_context, - const istio::authentication::v1alpha1::Policy& policy); - - bool run(istio::authn::Payload*) override; - - private: - // Reference to the authentication policy that the authenticator should - // enforce. Typically, the actual object is owned by filter. - const istio::authentication::v1alpha1::Policy& policy_; -}; - -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/peer_authenticator_test.cc b/src/envoy/http/authn/peer_authenticator_test.cc deleted file mode 100644 index d0cc95b6f6a..00000000000 --- a/src/envoy/http/authn/peer_authenticator_test.cc +++ /dev/null @@ -1,346 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/authn/peer_authenticator.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "common/http/header_map_impl.h" -#include "common/protobuf/protobuf.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "src/envoy/http/authn/test_utils.h" -#include "test/mocks/http/mocks.h" -#include "test/test_common/utility.h" - -namespace iaapi = istio::authentication::v1alpha1; - -using istio::authn::Payload; -using testing::DoAll; -using testing::MockFunction; -using testing::NiceMock; -using testing::Return; -using testing::SetArgPointee; -using testing::StrictMock; -using testing::_; - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace { - -class MockPeerAuthenticator : public PeerAuthenticator { - public: - MockPeerAuthenticator(FilterContext* filter_context, - const istio::authentication::v1alpha1::Policy& policy) - : PeerAuthenticator(filter_context, policy) {} - - MOCK_CONST_METHOD2(validateX509, bool(const iaapi::MutualTls&, Payload*)); - MOCK_METHOD2(validateJwt, bool(const iaapi::Jwt&, Payload*)); -}; - -class PeerAuthenticatorTest : public testing::Test { - public: - PeerAuthenticatorTest() - : request_headers_{{":method", "GET"}, {":path", "/"}} {} - virtual ~PeerAuthenticatorTest() {} - - void createAuthenticator() { - authenticator_.reset( - new StrictMock(&filter_context_, policy_)); - } - - void SetUp() override { payload_ = new Payload(); } - - void TearDown() override { delete (payload_); } - - protected: - std::unique_ptr> authenticator_; - Http::TestHeaderMapImpl request_headers_; - FilterContext filter_context_{&request_headers_, nullptr, - istio::envoy::config::filter::http::authn:: - v2alpha1::FilterConfig::default_instance()}; - - iaapi::Policy policy_; - Payload* payload_; - - Payload x509_payload_{TestUtilities::CreateX509Payload("foo")}; - Payload jwt_payload_{TestUtilities::CreateJwtPayload("foo", "istio.io")}; - Payload jwt_extra_payload_{ - TestUtilities::CreateJwtPayload("bar", "istio.io")}; -}; - -TEST_F(PeerAuthenticatorTest, EmptyPolicy) { - createAuthenticator(); - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, MTlsOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, TlsOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { - allow_tls: true - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); - - authenticator_->run(payload_); - // When client certificate is present on TLS, authenticated attribute - // should be extracted. - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, MTlsOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, TlsOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { - allow_tls: true - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); - - authenticator_->run(payload_); - // When TLS authentication failse, the authenticated attribute should be - // empty. - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, JwtOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - jwt { - issuer: "abc.xyz" - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, JwtOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - jwt { - issuer: "abc.xyz" - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, Multiple) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls {} - } - peers { - jwt { - issuer: "abc.xyz" - } - } - peers { - jwt { - issuer: "another" - } - } - )", - &policy_)); - - createAuthenticator(); - - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); - - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, TlsFailAndJwtSucceed) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { allow_tls: true } - } - peers { - jwt { - issuer: "abc.xyz" - } - } - peers { - jwt { - issuer: "another" - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); - authenticator_->run(payload_); - // validateX509 fail and validateJwt succeeds, - // result should be "foo", as expected as in jwt_payload. - EXPECT_TRUE(TestUtility::protoEqual( - TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, MultipleAllFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls {} - } - peers { - jwt { - issuer: "abc.xyz" - } - } - peers { - jwt { - issuer: "another" - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(2) - .WillRepeatedly( - DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -TEST_F(PeerAuthenticatorTest, TlsFailJwtFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( - peers { - mtls { allow_tls: true } - } - peers { - jwt { - issuer: "abc.xyz" - } - } - peers { - jwt { - issuer: "another" - } - } - )", - &policy_)); - - createAuthenticator(); - EXPECT_CALL(*authenticator_, validateX509(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - EXPECT_CALL(*authenticator_, validateJwt(_, _)) - .Times(2) - .WillRepeatedly( - DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); - authenticator_->run(payload_); - // validateX509 and validateJwt fail, result should be empty. - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), - filter_context_.authenticationResult())); -} - -} // namespace -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/test_utils.h b/src/envoy/http/authn/test_utils.h deleted file mode 100644 index 40ffaacc6c5..00000000000 --- a/src/envoy/http/authn/test_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/protobuf/protobuf.h" -#include "gmock/gmock.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Http { -namespace Istio { -namespace AuthN { -namespace TestUtilities { - -istio::authn::Payload CreateX509Payload(const std::string& user) { - istio::authn::Payload payload; - payload.mutable_x509()->set_user(user); - return payload; -} - -istio::authn::Payload CreateJwtPayload(const std::string& user, - const std::string& presenter) { - istio::authn::Payload payload; - payload.mutable_jwt()->set_user(user); - if (!presenter.empty()) { - payload.mutable_jwt()->set_presenter(presenter); - } - return payload; -} - -istio::authn::Result AuthNResultFromString(const std::string& text) { - istio::authn::Result result; - EXPECT_TRUE(Protobuf::TextFormat::ParseFromString(text, &result)); - return result; -} - -} // namespace TestUtilities -} // namespace AuthN -} // namespace Istio -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/authn/testdata/envoy_empty.conf b/src/envoy/http/authn/testdata/envoy_empty.conf deleted file mode 100644 index c9f244f91ce..00000000000 --- a/src/envoy/http/authn/testdata/envoy_empty.conf +++ /dev/null @@ -1,85 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "backend_service" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "istio_authn", - "config": {} - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "backend_service", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/authn/testdata/envoy_jwt_with_output_header_location.conf b/src/envoy/http/authn/testdata/envoy_jwt_with_output_header_location.conf deleted file mode 100644 index 0a0c6e8cdd8..00000000000 --- a/src/envoy/http/authn/testdata/envoy_jwt_with_output_header_location.conf +++ /dev/null @@ -1,99 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "backend_service" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "istio_authn", - "config": { - "policy": { - "origins": [ - { - "jwt": { - "issuer": "issuer@foo.com", - "jwks_uri": "http://localhost:8081/" - } - } - ] - }, - "jwt_output_payload_locations": { - "issuer@foo.com": "location-to-read-jwt-result" - } - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "backend_service", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf b/src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf deleted file mode 100644 index 5806fec16d2..00000000000 --- a/src/envoy/http/authn/testdata/envoy_origin_jwt_authn_only.conf +++ /dev/null @@ -1,99 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "backend_service" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "istio_authn", - "config": { - "policy": { - "origins": [ - { - "jwt": { - "issuer": "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "jwks_uri": "http://localhost:8081/" - } - } - ] - }, - "jwt_output_payload_locations": { - "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com": "sec-istio-auth-userinfo" - } - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "backend_service", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/authn/testdata/envoy_peer_mtls.conf b/src/envoy/http/authn/testdata/envoy_peer_mtls.conf deleted file mode 100644 index 993de548707..00000000000 --- a/src/envoy/http/authn/testdata/envoy_peer_mtls.conf +++ /dev/null @@ -1,94 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "backend_service" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "istio_authn", - "config": { - "policy": { - "peers": [ - { - "mtls": { - } - } - ] - } - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "backend_service", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD deleted file mode 100644 index d60715426f7..00000000000 --- a/src/envoy/http/jwt_auth/BUILD +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -package(default_visibility = ["//visibility:public"]) - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_binary", - "envoy_cc_library", - "envoy_cc_test", -) - -envoy_cc_library( - name = "jwt_lib", - srcs = ["jwt.cc"], - hdrs = ["jwt.h"], - external_deps = [ - "rapidjson", - "ssl", - ], - repository = "@envoy", - deps = [ - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "jwt_authenticator_lib", - srcs = [ - "jwt_authenticator.cc", - "token_extractor.cc", - ], - hdrs = [ - "auth_store.h", - "jwt_authenticator.h", - "pubkey_cache.h", - "token_extractor.h", - ], - repository = "@envoy", - deps = [ - ":jwt_lib", - "@envoy_api//envoy/config/filter/http/jwt_authn/v2alpha:jwt_authn_cc", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "http_filter_lib", - srcs = [ - "http_filter.cc", - ], - hdrs = [ - "http_filter.h", - ], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "http_filter_factory", - srcs = ["http_filter_factory.cc"], - repository = "@envoy", - deps = [ - ":http_filter_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_test( - name = "http_filter_integration_test", - srcs = [":integration_test/http_filter_integration_test.cc"], - data = [ - "integration_test/envoy.conf.jwk", - "integration_test/envoy_allow_missing_or_failed_jwt.conf.jwk", - ], - repository = "@envoy", - deps = [ - ":http_filter_factory", - ":jwt_lib", - "@envoy//test/integration:http_integration_lib", - "@envoy//test/integration:integration_lib", - ], -) - -envoy_cc_test( - name = "jwt_test", - srcs = [ - "jwt_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "jwt_authenticator_test", - srcs = [ - "jwt_authenticator_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/mocks/upstream:upstream_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "token_extractor_test", - srcs = [ - "token_extractor_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/test_common:utility_lib", - ], -) diff --git a/src/envoy/http/jwt_auth/ImplementationNotes.md b/src/envoy/http/jwt_auth/ImplementationNotes.md deleted file mode 100644 index 5dd39507ae5..00000000000 --- a/src/envoy/http/jwt_auth/ImplementationNotes.md +++ /dev/null @@ -1,62 +0,0 @@ -# Implementation Notes - -## JWT library - -- `jwt.h`, `jwt.cc` - -- ### Status - - enum to represent failure reason. - - #### TODO: - - categorize failure reason - (e.g. JWT parse error, public key parse error, or unmatch between JWT and key) - -- ### Interfaces - - `Jwt` - - `Pubkeys` - - It holding several public keys (for e.g. JWKs = array of JWK) - - `Verifier` - -- ### Public key formats: - - PEM, JWKs - -- ### signing algorithm: - - RS256 - -- ### TODO: - - support other public key format / signing algorithm - -## HTTP filter - -This consists of some parts: - -### Loading Envoy config - - - Filter object has a `JwtAuthConfig` object keeping the config. - - `JwtAuthConfig()` in `config.cc` is loading the filter config in Envoy config file. - - It calls `IssuerInfo()` (in `config.cc`) for each issuer, which loads the config for each issuer. - - In the case URI is provided, - `IssuerInfo` keeps the URI and cluster name and the public key will be fetched later, - namely in `JwtVerificationFilter::decodeHeaders()`. - -### Fetching public key - - In `JwtVerificationFilter::decodeHeaders()`, - requests for public keys to issuers are made if needed, using `AsyncClientCallbacks` in `config.cc`. - - `JwtVerificationFilter::ReceivePubkey()` is passed to AsyncClient as a callback, - which will call `JwtVerificationFilter::CompleteVerification()` after all responses are received. - - - #### Issue: - - https://github.com/istio/proxy/issues/468 - - - #### TODO: - - add tests for config loading - -### Verifying JWT & Making response - - `JwtVerificationFilter::CompleteVerification()` calls - `JwtVerificationFilter::Verify()`, which verifies the JWT in HTTP header - and returns `OK` or failure reason. - - - `JwtVerificationFilter::CompleteVerification()` passes or declines request. - -### Other TODO: - - add Error Log - - add integration tests diff --git a/src/envoy/http/jwt_auth/README.md b/src/envoy/http/jwt_auth/README.md deleted file mode 100644 index 899e15996cc..00000000000 --- a/src/envoy/http/jwt_auth/README.md +++ /dev/null @@ -1,170 +0,0 @@ -# JWT Authentication Proxy - -## Overview - -__(TODO:figure)__ - - -### Processing flow - -Soon after the server runs: - -1. This proxy run as a sidecar of the server. -2. Configure which issuers to use, via Envoy config. - -Before an user sending request: - -1. The user should request an issuer for an access token (JWT) - - Note: JWT claims should contain `aud`, `sub`, `iss` and `exp`. - -For every request from user client: - -1. Client send an HTTP request together with JWT, which is intercepted by this proxy -2. The proxy verifies JWT: - - The signature should be valid - - JWT should not be expired - - Issuer (and audience) should be valid -3. If JWT is valid, the user is authenticated and the request will be passed to the server, together with JWT payload (user identity). \ - If JWT is not valid, the request will be discarded and the proxy will send a response with an error message. - - -## How to build it - -* Follow https://github.com/lyft/envoy/blob/master/bazel/README.md to set up environment, and build target envoy: - -``` - bazel build //src/envoy:envoy -``` - -## How to run it - -* Start Envoy proxy. Run - -``` -bazel-bin/src/envoy/envoy -c src/envoy/http/jwt_auth/sample/envoy.conf -``` - -* Start backend Echo server. - -``` -go run test/backend/echo/echo.go -``` - -* Start (fake) issuer server. - -``` -go run src/envoy/http/jwt_auth/sample/fake_issuer.go src/envoy/http/jwt_auth/sample/pubkey.jwk -``` - -* Then issue HTTP request to proxy. - -With valid JWT: -``` -token=`cat src/envoy/http/jwt_auth/sample/correct_jwt` -curl --header "Authorization: Bearer $token" http://localhost:9090/echo -d "hello world" -``` -Note: the token is generated by: -* git clone https://github.com/cloudendpoints/esp -* cd esp; bazel build //src/tools:all -* client/custom/gen-auth-token.sh -s src/nginx/t/matching-client-secret.json - -With invalid JWT: -``` -token=`cat src/envoy/http/jwt_auth/sample/invalid_jwt` -curl --header "Authorization: Bearer $token" http://localhost:9090/echo -d "hello world" -``` - -## How it works - -### How to receive JWT - -If a HTTP request contains a JWT in the HTTP Authorization header: -- `Authorization: Bearer ` -Envoy proxy will try to verify it with configured issuers. - -### Behavior after verification - -- If verification fails, the request will not be passed to the backend and the proxy will send a response with the status code 401 (Unauthorized) and the failure reason as message body. -- If verification succeeds, the request will be passed to the backend, together with an additional HTTP header: - - ``` - sec-istio-auth-userinfo: - ``` - - Here, `` is base64 encoded payload JSON. -- The authorization header with JWT token is removed. - - -## How to configure it - -### Add this filter to the filter chain - -In Envoy config, -``` -"filters": [ - { - "type": "decoder", - "name": "jwt-auth", - "config": - }, - ... -] -``` - -### Config format - -Format of `` is defined in AuthFilterConfig message in config.proto file. It can be specified in JSON format as following examples -``` -{ - "jwts": [ - { - "issuer": "issuer1_name", - "audiences": [ - "audience1", - "audience2" - ], - "jwks_uri": "http://server1/path1", - "jwks_uri_envoy_cluster": "issuer1_cluster" - }, - { - "issuer": "issuer2_name", - "audiences": [], - "jwks_uri": "server2", - "jwks_uri_envoy_cluster": "issuer2_cluster", - "public_key_cache_duration": { - "seconds": 600, - "nanos": 1000 - } - } - ] -} -``` - -### Clusters - -All public key servers should be listed in the "clusters" section of the Envoy config. The format of the "url" inside "hosts" section is "tcp://host-name:port". - -Example: -``` -"clusters": [ - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://account.example.com:8080" - } - ] - }, - ... -] -``` - diff --git a/src/envoy/http/jwt_auth/auth_store.h b/src/envoy/http/jwt_auth/auth_store.h deleted file mode 100644 index d73d34daa1b..00000000000 --- a/src/envoy/http/jwt_auth/auth_store.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/config/filter/http/jwt_authn/v2alpha/config.pb.h" -#include "envoy/server/filter_config.h" -#include "envoy/thread_local/thread_local.h" -#include "src/envoy/http/jwt_auth/pubkey_cache.h" -#include "src/envoy/http/jwt_auth/token_extractor.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -// The JWT auth store object to store config and caches. -// It only has pubkey_cache for now. In the future it will have token cache. -// It is per-thread and stored in thread local. -class JwtAuthStore : public ThreadLocal::ThreadLocalObject { - public: - // Load the config from envoy config. - JwtAuthStore(const ::envoy::config::filter::http::jwt_authn::v2alpha:: - JwtAuthentication& config) - : config_(config), pubkey_cache_(config_), token_extractor_(config_) {} - - // Get the Config. - const ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication& - config() const { - return config_; - } - - // Get the pubkey cache. - PubkeyCache& pubkey_cache() { return pubkey_cache_; } - - // Get the private token extractor. - const JwtTokenExtractor& token_extractor() const { return token_extractor_; } - - private: - // Store the config. - const ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication& - config_; - // The public key cache, indexed by issuer. - PubkeyCache pubkey_cache_; - // The object to extract token. - JwtTokenExtractor token_extractor_; -}; - -// The factory to create per-thread auth store object. -class JwtAuthStoreFactory : public Logger::Loggable { - public: - JwtAuthStoreFactory(const ::envoy::config::filter::http::jwt_authn::v2alpha:: - JwtAuthentication& config, - Server::Configuration::FactoryContext& context) - : config_(config), tls_(context.threadLocal().allocateSlot()) { - tls_->set( - [this](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(config_); - }); - ENVOY_LOG(info, "Loaded JwtAuthConfig: {}", config_.DebugString()); - } - - // Get per-thread auth store object. - JwtAuthStore& store() { return tls_->getTyped(); } - - private: - // The auth config. - ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication config_; - // Thread local slot to store per-thread auth store - ThreadLocal::SlotPtr tls_; -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/http_filter.cc b/src/envoy/http/jwt_auth/http_filter.cc deleted file mode 100644 index f964f93cda5..00000000000 --- a/src/envoy/http/jwt_auth/http_filter.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/jwt_auth/http_filter.h" - -#include "common/http/message_impl.h" -#include "common/http/utility.h" -#include "envoy/http/async_client.h" - -#include -#include - -namespace Envoy { -namespace Http { - -JwtVerificationFilter::JwtVerificationFilter(Upstream::ClusterManager& cm, - JwtAuth::JwtAuthStore& store) - : jwt_auth_(cm, store) {} - -JwtVerificationFilter::~JwtVerificationFilter() {} - -void JwtVerificationFilter::onDestroy() { - ENVOY_LOG(debug, "Called JwtVerificationFilter : {}", __func__); - jwt_auth_.onDestroy(); -} - -FilterHeadersStatus JwtVerificationFilter::decodeHeaders(HeaderMap& headers, - bool) { - ENVOY_LOG(debug, "Called JwtVerificationFilter : {}", __func__); - state_ = Calling; - stopped_ = false; - - // Sanitize the JWT verification result in the HTTP headers - // TODO (lei-tang): when the JWT verification result is in a configurable - // header, need to sanitize based on the configuration. - headers.remove(JwtAuth::JwtAuthenticator::JwtPayloadKey()); - - // Verify the JWT token, onDone() will be called when completed. - jwt_auth_.Verify(headers, this); - - if (state_ == Complete) { - return FilterHeadersStatus::Continue; - } - ENVOY_LOG(debug, "Called JwtVerificationFilter : {} Stop", __func__); - stopped_ = true; - return FilterHeadersStatus::StopIteration; -} - -void JwtVerificationFilter::onDone(const JwtAuth::Status& status) { - ENVOY_LOG(debug, "Called JwtVerificationFilter : check complete {}", - int(status)); - // This stream has been reset, abort the callback. - if (state_ == Responded) { - return; - } - if (status != JwtAuth::Status::OK) { - state_ = Responded; - // verification failed - Code code = Code(401); // Unauthorized - // return failure reason as message body - Utility::sendLocalReply(*decoder_callbacks_, false, code, - JwtAuth::StatusToString(status)); - return; - } - - state_ = Complete; - if (stopped_) { - decoder_callbacks_->continueDecoding(); - } -} - -FilterDataStatus JwtVerificationFilter::decodeData(Buffer::Instance&, bool) { - ENVOY_LOG(debug, "Called JwtVerificationFilter : {}", __func__); - if (state_ == Calling) { - return FilterDataStatus::StopIterationAndWatermark; - } - return FilterDataStatus::Continue; -} - -FilterTrailersStatus JwtVerificationFilter::decodeTrailers(HeaderMap&) { - ENVOY_LOG(debug, "Called JwtVerificationFilter : {}", __func__); - if (state_ == Calling) { - return FilterTrailersStatus::StopIteration; - } - return FilterTrailersStatus::Continue; -} - -void JwtVerificationFilter::setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) { - ENVOY_LOG(debug, "Called JwtVerificationFilter : {}", __func__); - decoder_callbacks_ = &callbacks; -} - -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/http_filter.h b/src/envoy/http/jwt_auth/http_filter.h deleted file mode 100644 index 838605faa6c..00000000000 --- a/src/envoy/http/jwt_auth/http_filter.h +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "src/envoy/http/jwt_auth/jwt_authenticator.h" - -#include "common/common/logger.h" -#include "envoy/http/filter.h" - -namespace Envoy { -namespace Http { - -// The Envoy filter to process JWT auth. -class JwtVerificationFilter : public StreamDecoderFilter, - public JwtAuth::JwtAuthenticator::Callbacks, - public Logger::Loggable { - public: - JwtVerificationFilter(Upstream::ClusterManager& cm, - JwtAuth::JwtAuthStore& store); - ~JwtVerificationFilter(); - - // Http::StreamFilterBase - void onDestroy() override; - - // Http::StreamDecoderFilter - FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool) override; - FilterDataStatus decodeData(Buffer::Instance&, bool) override; - FilterTrailersStatus decodeTrailers(HeaderMap&) override; - void setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) override; - - private: - // the function for JwtAuth::Authenticator::Callbacks interface. - // To be called when its Verify() call is completed. - void onDone(const JwtAuth::Status& status) override; - - // The callback funcion. - StreamDecoderFilterCallbacks* decoder_callbacks_; - // The auth object. - JwtAuth::JwtAuthenticator jwt_auth_; - // The state of the request - enum State { Init, Calling, Responded, Complete }; - State state_ = Init; - // Mark if request has been stopped. - bool stopped_ = false; -}; - -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/http_filter_factory.cc b/src/envoy/http/jwt_auth/http_filter_factory.cc deleted file mode 100644 index 018b8a65788..00000000000 --- a/src/envoy/http/jwt_auth/http_filter_factory.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "envoy/config/filter/http/jwt_authn/v2alpha/config.pb.validate.h" -#include "envoy/registry/registry.h" -#include "google/protobuf/util/json_util.h" -#include "src/envoy/http/jwt_auth/auth_store.h" -#include "src/envoy/http/jwt_auth/http_filter.h" - -using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; - -namespace Envoy { -namespace Server { -namespace Configuration { - -class JwtVerificationFilterConfig : public NamedHttpFilterConfigFactory { - public: - Http::FilterFactoryCb createFilterFactory(const Json::Object& config, - const std::string&, - FactoryContext& context) override { - JwtAuthentication proto_config; - MessageUtil::loadFromJson(config.asJsonString(), proto_config); - return createFilter(proto_config, context); - } - - Http::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message& proto_config, const std::string&, - FactoryContext& context) override { - return createFilter( - MessageUtil::downcastAndValidate( - proto_config), - context); - } - - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return ProtobufTypes::MessagePtr{new JwtAuthentication}; - } - - std::string name() override { return "jwt-auth"; } - - private: - Http::FilterFactoryCb createFilter(const JwtAuthentication& proto_config, - FactoryContext& context) { - auto store_factory = std::make_shared( - proto_config, context); - Upstream::ClusterManager& cm = context.clusterManager(); - return [&cm, store_factory]( - Http::FilterChainFactoryCallbacks& callbacks) -> void { - callbacks.addStreamDecoderFilter( - std::make_shared( - cm, store_factory->store())); - }; - } -}; - -/** - * Static registration for this JWT verification filter. @see RegisterFactory. - */ -static Registry::RegisterFactory - register_; - -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/integration_test/envoy.conf b/src/envoy/http/jwt_auth/integration_test/envoy.conf deleted file mode 100644 index 225c4db7023..00000000000 --- a/src/envoy/http/jwt_auth/integration_test/envoy.conf +++ /dev/null @@ -1,68 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "service1" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/stdout" - } - ], - "filters": [ - { - "type": "decoder", - "name": "jwt-auth", - "config": {} - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/stdout", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/jwt_auth/integration_test/envoy.conf.jwk b/src/envoy/http/jwt_auth/integration_test/envoy.conf.jwk deleted file mode 100644 index 21a8d136fc2..00000000000 --- a/src/envoy/http/jwt_auth/integration_test/envoy.conf.jwk +++ /dev/null @@ -1,101 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "service1" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "jwt-auth", - "config": { - "rules": [ - { - "issuer": "https://example.com", - "audiences": [ - "example_service" - ], - "remote_jwks": { - "http_uri": { - "uri": "http://example.com/foobar_cert", - "cluster": "example_issuer" - } - }, - "forward_payload_header": "sec-istio-auth-userinfo" - } - ] - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/jwt_auth/integration_test/envoy_allow_missing_or_failed_jwt.conf.jwk b/src/envoy/http/jwt_auth/integration_test/envoy_allow_missing_or_failed_jwt.conf.jwk deleted file mode 100644 index 91746d58809..00000000000 --- a/src/envoy/http/jwt_auth/integration_test/envoy_allow_missing_or_failed_jwt.conf.jwk +++ /dev/null @@ -1,101 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://{{ ip_loopback_address }}:0", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "service1" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/null" - } - ], - "filters": [ - { - "type": "decoder", - "name": "jwt-auth", - "config": { - "rules": [ - { - "issuer": "https://example.com", - "audiences": [ - "example_service" - ], - "remote_jwks": { - "http_uri": { - "uri": "http://example.com/foobar_cert", - "cluster": "example_issuer" - } - } - } - ], - "allow_missing_or_failed": true - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://{{ ip_loopback_address }}:0" - }, - "cluster_manager": { - "clusters": [ - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_0 }}" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "static", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://{{ ip_loopback_address }}:{{ upstream_1 }}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/jwt_auth/integration_test/http_filter_integration_test.cc b/src/envoy/http/jwt_auth/integration_test/http_filter_integration_test.cc deleted file mode 100644 index 5d53ba3cff3..00000000000 --- a/src/envoy/http/jwt_auth/integration_test/http_filter_integration_test.cc +++ /dev/null @@ -1,394 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "test/integration/http_integration.h" -#include "test/integration/utility.h" - -namespace Envoy { - -namespace { -// The HTTP header key for the JWT verification result -const Http::LowerCaseString kJwtVerificationResultHeaderKey( - "sec-istio-auth-userinfo"); -// {"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":2001001001} -const std::string kJwtVerificationResult = - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVz" - "dEBleGFtcGxlLmNvbSIsImF1ZCI6ImV4YW1wbGVfc2VydmljZSIs" - "ImV4cCI6MjAwMTAwMTAwMX0"; -} // namespace - -// Base class JWT filter integration tests. -class JwtVerificationFilterIntegrationTest - : public HttpIntegrationTest, - public testing::TestWithParam { - public: - JwtVerificationFilterIntegrationTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()) {} - virtual ~JwtVerificationFilterIntegrationTest() {} - /** - * Initializer for an individual integration test. - */ - void SetUp() override { - fake_upstreams_.emplace_back( - new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); - registerPort("upstream_0", - fake_upstreams_.back()->localAddress()->ip()->port()); - fake_upstreams_.emplace_back( - new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); - registerPort("upstream_1", - fake_upstreams_.back()->localAddress()->ip()->port()); - createTestServer(ConfigPath(), {"http"}); - } - - /** - * Destructor for an individual integration test. - */ - void TearDown() override { - test_server_.reset(); - fake_upstreams_.clear(); - } - - protected: - Http::TestHeaderMapImpl BaseRequestHeaders() { - return Http::TestHeaderMapImpl{ - {":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - } - - Http::TestHeaderMapImpl createHeaders(const std::string& token) { - auto headers = BaseRequestHeaders(); - headers.addCopy("Authorization", "Bearer " + token); - return headers; - } - - Http::TestHeaderMapImpl createIssuerHeaders() { - return Http::TestHeaderMapImpl{{":status", "200"}}; - } - - std::string InstanceToString(Buffer::Instance& instance) { - auto len = instance.length(); - return std::string(static_cast(instance.linearize(len)), len); - } - - std::map HeadersMapToMap( - const Http::HeaderMap& headers) { - std::map ret; - headers.iterate( - [](const Http::HeaderEntry& entry, - void* context) -> Http::HeaderMap::Iterate { - auto ret = static_cast*>(context); - Http::LowerCaseString lower_key{entry.key().c_str()}; - (*ret)[std::string(lower_key.get())] = - std::string(entry.value().c_str()); - return Http::HeaderMap::Iterate::Continue; - }, - &ret); - return ret; - }; - - void ExpectHeaderIncluded(const Http::HeaderMap& headers1, - const Http::HeaderMap& headers2) { - auto map1 = HeadersMapToMap(headers1); - auto map2 = HeadersMapToMap(headers2); - for (const auto& kv : map1) { - EXPECT_EQ(map2[kv.first], kv.second); - } - } - - void TestVerification(const Http::HeaderMap& request_headers, - const std::string& request_body, - const Http::HeaderMap& issuer_response_headers, - const std::string& issuer_response_body, - bool verification_success, - const Http::HeaderMap& expected_headers, - const std::string& expected_body) { - IntegrationCodecClientPtr codec_client; - FakeHttpConnectionPtr fake_upstream_connection_issuer; - FakeHttpConnectionPtr fake_upstream_connection_backend; - IntegrationStreamDecoderPtr response( - new IntegrationStreamDecoder(*dispatcher_)); - FakeStreamPtr request_stream_issuer; - FakeStreamPtr request_stream_backend; - - codec_client = makeHttpConnection(lookupPort("http")); - - // Send a request to Envoy. - if (!request_body.empty()) { - Http::StreamEncoder& encoder = - codec_client->startRequest(request_headers, *response); - Buffer::OwnedImpl body(request_body); - codec_client->sendData(encoder, body, true); - } else { - codec_client->makeHeaderOnlyRequest(request_headers, *response); - } - - // Empty issuer_response_body indicates issuer will not be called. - // Mock a response from an issuer server. - if (!issuer_response_body.empty()) { - fake_upstream_connection_issuer = - fake_upstreams_[1]->waitForHttpConnection(*dispatcher_); - request_stream_issuer = - fake_upstream_connection_issuer->waitForNewStream(*dispatcher_); - request_stream_issuer->waitForEndStream(*dispatcher_); - - request_stream_issuer->encodeHeaders(issuer_response_headers, false); - Buffer::OwnedImpl body(issuer_response_body); - request_stream_issuer->encodeData(body, true); - } - - // Valid JWT case. - // Check if the request sent to the backend includes the expected one. - if (verification_success) { - fake_upstream_connection_backend = - fake_upstreams_[0]->waitForHttpConnection(*dispatcher_); - request_stream_backend = - fake_upstream_connection_backend->waitForNewStream(*dispatcher_); - request_stream_backend->waitForEndStream(*dispatcher_); - - EXPECT_TRUE(request_stream_backend->complete()); - - ExpectHeaderIncluded(expected_headers, request_stream_backend->headers()); - if (!expected_body.empty()) { - EXPECT_EQ(expected_body, - InstanceToString(request_stream_backend->body())); - } - } - - response->waitForEndStream(); - - // Invalid JWT case. - // Check if the response sent to the client includes the expected one. - if (!verification_success) { - EXPECT_TRUE(response->complete()); - - ExpectHeaderIncluded(expected_headers, response->headers()); - if (!expected_body.empty()) { - EXPECT_EQ(expected_body, response->body()); - } - } - - codec_client->close(); - if (!issuer_response_body.empty()) { - fake_upstream_connection_issuer->close(); - fake_upstream_connection_issuer->waitForDisconnect(); - } - if (verification_success) { - fake_upstream_connection_backend->close(); - fake_upstream_connection_backend->waitForDisconnect(); - } - } - - private: - virtual std::string ConfigPath() = 0; -}; - -class JwtVerificationFilterIntegrationTestWithJwks - : public JwtVerificationFilterIntegrationTest { - std::string ConfigPath() override { - return "src/envoy/http/jwt_auth/integration_test/envoy.conf.jwk"; - } - - protected: - const std::string kPublicKeyRSA = - "{\"keys\": [{\"kty\": \"RSA\",\"alg\": \"RS256\",\"use\": " - "\"sig\",\"kid\": \"62a93512c9ee4c7f8067b5a216dade2763d32a47\",\"n\": " - "\"0YWnm_eplO9BFtXszMRQNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm_" - "2xNgcaVpkW0VT2l4mU3KftR-6s3Oa5Rnz5BrWEUkCTVVolR7VYksfqIB2I_" - "x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeAh" - "0-" - "bLK5zvqCmKW5QgDIXSxUTJxPjZCgfx1vmAfGqaJb-" - "nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4ja6Slr8S4EB3F1luYhATa1P" - "KU" - "SH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX_aYahIw\",\"e\": \"AQAB\"},{\"kty\": " - "\"RSA\",\"alg\": \"RS256\",\"use\": \"sig\",\"kid\": " - "\"b3319a147514df7ee5e4bcdee51350cc890cc89e\",\"n\": " - "\"qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_" - "2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE-" - "RiKM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF-" - "BTillGJt5W5RuXti9uqfMtCQdagB8EC3MNRuU_KdeLgBy3lS3oo4LOYd-" - "74kRBVZbk2wnmmb7IhP9OoLc1-7-9qU1uhpDxmE6JwBau0mDSwMnYDS4G_ML17dC-" - "ZDtLd1i24STUw39KH0pcSdfFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI_pj8M-2Mn_" - "oA8jBuI8YKwBqYkZCN1I95Q\",\"e\": \"AQAB\"}]}"; - - const std::string kPublicKeyEC = - "{\"keys\": [" - "{" - "\"kty\": \"EC\"," - "\"crv\": \"P-256\"," - "\"x\": \"EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k\"," - "\"y\": \"92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8\"," - "\"alg\": \"ES256\"," - "\"kid\": \"abc\"" - "}," - "{" - "\"kty\": \"EC\"," - "\"crv\": \"P-256\"," - "\"x\": \"EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k\"," - "\"y\": \"92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8\"," - "\"alg\": \"ES256\"," - "\"kid\": \"xyz\"" - "}" - "]}"; -}; - -INSTANTIATE_TEST_CASE_P( - IpVersions, JwtVerificationFilterIntegrationTestWithJwks, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); - -TEST_P(JwtVerificationFilterIntegrationTestWithJwks, RSASuccess1) { - const std::string kJwtNoKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0." - "n45uWZfIBZwCIPiL0K8Ca3tmm-ZlsDrC79_" - "vXCspPwk5oxdSn983tuC9GfVWKXWUMHe11DsB02b19Ow-" - "fmoEzooTFn65Ml7G34nW07amyM6lETiMhNzyiunctplOr6xKKJHmzTUhfTirvDeG-q9n24-" - "8lH7GP8GgHvDlgSM9OY7TGp81bRcnZBmxim_UzHoYO3_" - "c8OP4ZX3xG5PfihVk5G0g6wcHrO70w0_64JgkKRCrLHMJSrhIgp9NHel_" - "CNOnL0AjQKe9IGblJrMuouqYYS0zEWwmOVUWUSxQkoLpldQUVefcfjQeGjz8IlvktRa77FYe" - "xfP590ACPyXrivtsxg"; - - auto expected_headers = BaseRequestHeaders(); - expected_headers.addCopy( - "sec-istio-auth-userinfo", - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVz" - "dEBleGFtcGxlLmNvbSIsImF1ZCI6ImV4YW1wbGVfc2VydmljZSIs" - "ImV4cCI6MjAwMTAwMTAwMX0"); - - TestVerification(createHeaders(kJwtNoKid), "", createIssuerHeaders(), - kPublicKeyRSA, true, expected_headers, ""); -} - -TEST_P(JwtVerificationFilterIntegrationTestWithJwks, ES256Success1) { - // Payload: - // {"iss": "https://example.com", "sub": "test@example.com", "aud": - // "example_service", - // "exp": 2001001001} - - const std::string kJwtEC = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY" - "29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjo" - "iZXhhbXBsZV9zZXJ2aWNlIn0.1Slk-zwP_78zR8Go5COxmmMunkxdTnBeeC91CgR-p2MWM" - "T9ubWvRvNGGYOTuJ8T17Db68Qk3T8UNTK5lzfR_mw"; - - auto expected_headers = BaseRequestHeaders(); - expected_headers.addCopy("sec-istio-auth-userinfo", - "eyJpc3MiOiJo" - "dHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtc" - "GxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiZXhhbX" - "BsZV9zZXJ2aWNlIn0"); - - TestVerification(createHeaders(kJwtEC), "", createIssuerHeaders(), - kPublicKeyEC, true, expected_headers, ""); -} - -TEST_P(JwtVerificationFilterIntegrationTestWithJwks, JwtExpired) { - const std::string kJwtNoKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0.XYPg6VPrq-H1Kl-kgmAfGFomVpnmdZLIAo0g6dhJb2Be_" - "koZ2T76xg5_Lr828hsLKxUfzwNxl5-k1cdz_kAst6vei0hdnOYqRQ8EhkZS_" - "5Y2vWMrzGHw7AUPKCQvSnNqJG5HV8YdeOfpsLhQTd-" - "tG61q39FWzJ5Ra5lkxWhcrVDQFtVy7KQrbm2dxhNEHAR2v6xXP21p1T5xFBdmGZbHFiH63N9" - "dwdRgWjkvPVTUqxrZil7PSM2zg_GTBETp_" - "qS7Wwf8C0V9o2KZu0KDV0j0c9nZPWTv3IMlaGZAtQgJUeyemzRDtf4g2yG3xBZrLm3AzDUj_" - "EX_pmQAHA5ZjPVCAw"; - - // Issuer is not called by passing empty pubkey. - std::string pubkey = ""; - TestVerification(createHeaders(kJwtNoKid), "", createIssuerHeaders(), pubkey, - false, Http::TestHeaderMapImpl{{":status", "401"}}, - "JWT is expired"); -} - -TEST_P(JwtVerificationFilterIntegrationTestWithJwks, AudInvalid) { - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","aud":"invalid_service","exp":2001001001} - const std::string jwt = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImF1ZCI6ImludmFsaWRfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0." - "gEWnuqtEdVzC94lVbuClaVLoxs-w-_uKJRbAYwAKRulE-" - "ZhxG9VtKCd8i90xEuk9txB3tT8VGjdZKs5Hf5LjF4ebobV3M9ya6mZvq1MdcUHYiUtQhJe3M" - "t_2sxRmogK-QZ7HcuA9hpFO4HHVypnMDr4WHgxx2op1vhKU7NDlL-" - "38Dpf6uKEevxi0Xpids9pSST4YEQjReTXJDJECT5dhk8ZQ_lcS-pujgn7kiY99bTf6j4U-" - "ajIcWwtQtogYx4bcmHBUvEjcYOC86TRrnArZSk1mnO7OGq4KrSrqhXnvqDmc14LfldyWqEks" - "X5FkM94prXPK0iN-pPVhRjNZ4xvR-w"; - - // Issuer is not called by passing empty pubkey. - std::string pubkey = ""; - TestVerification(createHeaders(jwt), "", createIssuerHeaders(), pubkey, false, - Http::TestHeaderMapImpl{{":status", "401"}}, - "Audience doesn't match"); -} - -TEST_P(JwtVerificationFilterIntegrationTestWithJwks, Fail1) { - std::string token = "invalidToken"; - // Issuer is not called by passing empty pubkey. - std::string pubkey = ""; - TestVerification(createHeaders(token), "", createIssuerHeaders(), pubkey, - false, Http::TestHeaderMapImpl{{":status", "401"}}, - "JWT_BAD_FORMAT"); -} - -class JwtVerificationFilterIntegrationTestWithInjectedJwtResult - : public JwtVerificationFilterIntegrationTestWithJwks { - // With allow_missing_or_failed option being true, a request without JWT - // will reach the backend. This is to test the injected JWT result. - std::string ConfigPath() override { - return "src/envoy/http/jwt_auth/integration_test/" - "envoy_allow_missing_or_failed_jwt.conf.jwk"; - } -}; - -INSTANTIATE_TEST_CASE_P( - IpVersions, JwtVerificationFilterIntegrationTestWithInjectedJwtResult, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); - -TEST_P(JwtVerificationFilterIntegrationTestWithInjectedJwtResult, - InjectedJwtResultSanitized) { - // Create a request without JWT. - // With allow_missing_or_failed option being true, a request without JWT - // will reach the backend. This is to test the injected JWT result. - auto headers = BaseRequestHeaders(); - // Inject a header of JWT verification result - headers.addCopy(kJwtVerificationResultHeaderKey, kJwtVerificationResult); - - IntegrationCodecClientPtr codec_client; - FakeHttpConnectionPtr fake_upstream_connection_backend; - IntegrationStreamDecoderPtr response( - new IntegrationStreamDecoder(*dispatcher_)); - FakeStreamPtr request_stream_backend; - codec_client = makeHttpConnection(lookupPort("http")); - // Send a request to Envoy. - codec_client->makeHeaderOnlyRequest(headers, *response); - fake_upstream_connection_backend = - fake_upstreams_[0]->waitForHttpConnection(*dispatcher_); - request_stream_backend = - fake_upstream_connection_backend->waitForNewStream(*dispatcher_); - request_stream_backend->waitForEndStream(*dispatcher_); - EXPECT_TRUE(request_stream_backend->complete()); - - // With sanitization, the headers received by the backend should not - // contain the injected JWT verification header. - EXPECT_TRUE(request_stream_backend->headers().get( - kJwtVerificationResultHeaderKey) == nullptr); - - response->waitForEndStream(); - codec_client->close(); - fake_upstream_connection_backend->close(); - fake_upstream_connection_backend->waitForDisconnect(); -} - -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc deleted file mode 100644 index 3aa523594b3..00000000000 --- a/src/envoy/http/jwt_auth/jwt.cc +++ /dev/null @@ -1,593 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "jwt.h" - -#include "common/common/assert.h" -#include "common/common/base64.h" -#include "common/common/utility.h" -#include "common/json/json_loader.h" -#include "openssl/bn.h" -#include "openssl/ecdsa.h" -#include "openssl/evp.h" -#include "openssl/rsa.h" -#include "openssl/sha.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -std::string StatusToString(Status status) { - static std::map table = { - {Status::OK, "OK"}, - {Status::JWT_MISSED, "Required JWT token is missing"}, - {Status::JWT_EXPIRED, "JWT is expired"}, - {Status::JWT_BAD_FORMAT, "JWT_BAD_FORMAT"}, - {Status::JWT_HEADER_PARSE_ERROR, "JWT_HEADER_PARSE_ERROR"}, - {Status::JWT_HEADER_NO_ALG, "JWT_HEADER_NO_ALG"}, - {Status::JWT_HEADER_BAD_ALG, "JWT_HEADER_BAD_ALG"}, - {Status::JWT_SIGNATURE_PARSE_ERROR, "JWT_SIGNATURE_PARSE_ERROR"}, - {Status::JWT_INVALID_SIGNATURE, "JWT_INVALID_SIGNATURE"}, - {Status::JWT_PAYLOAD_PARSE_ERROR, "JWT_PAYLOAD_PARSE_ERROR"}, - {Status::JWT_HEADER_BAD_KID, "JWT_HEADER_BAD_KID"}, - {Status::JWT_UNKNOWN_ISSUER, "Unknown issuer"}, - {Status::JWK_PARSE_ERROR, "JWK_PARSE_ERROR"}, - {Status::JWK_NO_KEYS, "JWK_NO_KEYS"}, - {Status::JWK_BAD_KEYS, "JWK_BAD_KEYS"}, - {Status::JWK_NO_VALID_PUBKEY, "JWK_NO_VALID_PUBKEY"}, - {Status::KID_ALG_UNMATCH, "KID_ALG_UNMATCH"}, - {Status::ALG_NOT_IMPLEMENTED, "ALG_NOT_IMPLEMENTED"}, - {Status::PEM_PUBKEY_BAD_BASE64, "PEM_PUBKEY_BAD_BASE64"}, - {Status::PEM_PUBKEY_PARSE_ERROR, "PEM_PUBKEY_PARSE_ERROR"}, - {Status::JWK_RSA_PUBKEY_PARSE_ERROR, "JWK_RSA_PUBKEY_PARSE_ERROR"}, - {Status::FAILED_CREATE_EC_KEY, "FAILED_CREATE_EC_KEY"}, - {Status::JWK_EC_PUBKEY_PARSE_ERROR, "JWK_EC_PUBKEY_PARSE_ERROR"}, - {Status::FAILED_CREATE_ECDSA_SIGNATURE, "FAILED_CREATE_ECDSA_SIGNATURE"}, - {Status::AUDIENCE_NOT_ALLOWED, "Audience doesn't match"}, - {Status::FAILED_FETCH_PUBKEY, "Failed to fetch public key"}, - }; - return table[status]; -} - -namespace { - -// Conversion table is taken from -// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c -// -// and modified the position of 62 ('+' to '-') and 63 ('/' to '_') -const uint8_t kReverseLookupTableBase64Url[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, - 63, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64}; - -bool IsNotBase64UrlChar(int8_t c) { - return kReverseLookupTableBase64Url[static_cast(c)] & 64; -} - -} // namespace - -std::string Base64UrlDecode(std::string input) { - // allow at most 2 padding letters at the end of the input, only if input - // length is divisible by 4 - int len = input.length(); - if (len % 4 == 0) { - if (input[len - 1] == '=') { - input.pop_back(); - if (input[len - 2] == '=') { - input.pop_back(); - } - } - } - // if input contains non-base64url character, return empty string - // Note: padding letter must not be contained - if (std::find_if(input.begin(), input.end(), IsNotBase64UrlChar) != - input.end()) { - return ""; - } - - // base64url is using '-', '_' instead of '+', '/' in base64 string. - std::replace(input.begin(), input.end(), '-', '+'); - std::replace(input.begin(), input.end(), '_', '/'); - - // base64 string should be padded with '=' so as to the length of the string - // is divisible by 4. - switch (input.length() % 4) { - case 0: - break; - case 2: - input += "=="; - break; - case 3: - input += "="; - break; - default: - // * an invalid base64url input. return empty string. - return ""; - } - return Base64::decode(input); -} - -namespace { - -const uint8_t *CastToUChar(const std::string &str) { - return reinterpret_cast(str.c_str()); -} - -// Class to create EVP_PKEY object from string of public key, formatted in PEM -// or JWKs. -// If it failed, status_ holds the failure reason. -// -// Usage example: -// EvpPkeyGetter e; -// bssl::UniquePtr pkey = -// e.EvpPkeyFromStr(pem_formatted_public_key); -// (You can use EvpPkeyFromJwkRSA() or EcKeyFromJwkEC() for JWKs) -class EvpPkeyGetter : public WithStatus { - public: - EvpPkeyGetter() {} - - bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { - std::string pkey_der = Base64::decode(pkey_pem); - if (pkey_der == "") { - UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); - return nullptr; - } - auto rsa = bssl::UniquePtr( - RSA_public_key_from_bytes(CastToUChar(pkey_der), pkey_der.length())); - if (!rsa) { - UpdateStatus(Status::PEM_PUBKEY_PARSE_ERROR); - } - return EvpPkeyFromRsa(rsa.get()); - } - - bssl::UniquePtr EvpPkeyFromJwkRSA(const std::string &n, - const std::string &e) { - return EvpPkeyFromRsa(RsaFromJwk(n, e).get()); - } - - bssl::UniquePtr EcKeyFromJwkEC(const std::string &x, - const std::string &y) { - bssl::UniquePtr ec_key( - EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); - if (!ec_key) { - UpdateStatus(Status::FAILED_CREATE_EC_KEY); - return nullptr; - } - bssl::UniquePtr bn_x = BigNumFromBase64UrlString(x); - bssl::UniquePtr bn_y = BigNumFromBase64UrlString(y); - if (!bn_x || !bn_y) { - // EC public key field is missing or has parse error. - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - - if (EC_KEY_set_public_key_affine_coordinates(ec_key.get(), bn_x.get(), - bn_y.get()) == 0) { - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - return ec_key; - } - - private: - // In the case where rsa is nullptr, UpdateStatus() should be called - // appropriately elsewhere. - bssl::UniquePtr EvpPkeyFromRsa(RSA *rsa) { - if (!rsa) { - return nullptr; - } - bssl::UniquePtr key(EVP_PKEY_new()); - EVP_PKEY_set1_RSA(key.get(), rsa); - return key; - } - - bssl::UniquePtr BigNumFromBase64UrlString(const std::string &s) { - std::string s_decoded = Base64UrlDecode(s); - if (s_decoded == "") { - return nullptr; - } - return bssl::UniquePtr( - BN_bin2bn(CastToUChar(s_decoded), s_decoded.length(), NULL)); - }; - - bssl::UniquePtr RsaFromJwk(const std::string &n, const std::string &e) { - bssl::UniquePtr rsa(RSA_new()); - // It crash if RSA object couldn't be created. - assert(rsa); - - rsa->n = BigNumFromBase64UrlString(n).release(); - rsa->e = BigNumFromBase64UrlString(e).release(); - if (!rsa->n || !rsa->e) { - // RSA public key field is missing or has parse error. - UpdateStatus(Status::JWK_RSA_PUBKEY_PARSE_ERROR); - return nullptr; - } - return rsa; - } -}; - -} // namespace - -Jwt::Jwt(const std::string &jwt) { - // jwt must have exactly 2 dots - if (std::count(jwt.begin(), jwt.end(), '.') != 2) { - UpdateStatus(Status::JWT_BAD_FORMAT); - return; - } - auto jwt_split = StringUtil::splitToken(jwt, "."); - if (jwt_split.size() != 3) { - UpdateStatus(Status::JWT_BAD_FORMAT); - return; - } - - // Parse header json - header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); - header_str_ = Base64UrlDecode(header_str_base64url_); - try { - header_ = Json::Factory::loadFromString(header_str_); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); - return; - } - - // Header should contain "alg". - if (!header_->hasObject("alg")) { - UpdateStatus(Status::JWT_HEADER_NO_ALG); - return; - } - try { - alg_ = header_->getString("alg"); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_HEADER_BAD_ALG); - return; - } - - // Prepare EVP_MD object. - if (alg_ == "RS256") { - // may use - // EVP_sha384() if alg == "RS384" and - // EVP_sha512() if alg == "RS512" - md_ = EVP_sha256(); - } else if (alg_ != "ES256") { - UpdateStatus(Status::ALG_NOT_IMPLEMENTED); - return; - } - - // Header may contain "kid", which should be a string if exists. - try { - kid_ = header_->getString("kid", ""); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_HEADER_BAD_KID); - return; - } - - // Parse payload json - payload_str_base64url_ = - std::string(jwt_split[1].begin(), jwt_split[1].end()); - payload_str_ = Base64UrlDecode(payload_str_base64url_); - try { - payload_ = Json::Factory::loadFromString(payload_str_); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); - return; - } - - iss_ = payload_->getString("iss", ""); - sub_ = payload_->getString("sub", ""); - exp_ = payload_->getInteger("exp", 0); - - // "aud" can be either string array or string. - // Try as string array, read it as empty array if doesn't exist. - try { - aud_ = payload_->getStringArray("aud", true); - } catch (Json::Exception &e) { - // Try as string - try { - auto audience = payload_->getString("aud"); - aud_.push_back(audience); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); - return; - } - } - - // Set up signature - signature_ = - Base64UrlDecode(std::string(jwt_split[2].begin(), jwt_split[2].end())); - if (signature_ == "") { - // Signature is a bad Base64url input. - UpdateStatus(Status::JWT_SIGNATURE_PARSE_ERROR); - return; - } -} - -bool Verifier::VerifySignatureRSA(EVP_PKEY *key, const EVP_MD *md, - const uint8_t *signature, - size_t signature_len, - const uint8_t *signed_data, - size_t signed_data_len) { - bssl::UniquePtr md_ctx(EVP_MD_CTX_create()); - - EVP_DigestVerifyInit(md_ctx.get(), nullptr, md, nullptr, key); - EVP_DigestVerifyUpdate(md_ctx.get(), signed_data, signed_data_len); - return (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) == 1); -} - -bool Verifier::VerifySignatureRSA(EVP_PKEY *key, const EVP_MD *md, - const std::string &signature, - const std::string &signed_data) { - return VerifySignatureRSA(key, md, CastToUChar(signature), signature.length(), - CastToUChar(signed_data), signed_data.length()); -} - -bool Verifier::VerifySignatureEC(EC_KEY *key, const uint8_t *signature, - size_t signature_len, - const uint8_t *signed_data, - size_t signed_data_len) { - // ES256 signature should be 64 bytes. - if (signature_len != 2 * 32) { - return false; - } - - uint8_t digest[SHA256_DIGEST_LENGTH]; - SHA256(signed_data, signed_data_len, digest); - - bssl::UniquePtr ecdsa_sig(ECDSA_SIG_new()); - if (!ecdsa_sig) { - UpdateStatus(Status::FAILED_CREATE_ECDSA_SIGNATURE); - return false; - } - - BN_bin2bn(signature, 32, ecdsa_sig->r); - BN_bin2bn(signature + 32, 32, ecdsa_sig->s); - return (ECDSA_do_verify(digest, SHA256_DIGEST_LENGTH, ecdsa_sig.get(), key) == - 1); -} - -bool Verifier::VerifySignatureEC(EC_KEY *key, const std::string &signature, - const std::string &signed_data) { - return VerifySignatureEC(key, CastToUChar(signature), signature.length(), - CastToUChar(signed_data), signed_data.length()); -} - -bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) { - // If JWT status is not OK, inherits its status and return false. - if (jwt.GetStatus() != Status::OK) { - UpdateStatus(jwt.GetStatus()); - return false; - } - - // If pubkeys status is not OK, inherits its status and return false. - if (pubkeys.GetStatus() != Status::OK) { - UpdateStatus(pubkeys.GetStatus()); - return false; - } - - std::string signed_data = - jwt.header_str_base64url_ + '.' + jwt.payload_str_base64url_; - bool kid_alg_matched = false; - for (auto &pubkey : pubkeys.keys_) { - // If kid is specified in JWT, JWK with the same kid is used for - // verification. - // If kid is not specified in JWT, try all JWK. - if (jwt.kid_ != "" && pubkey->kid_specified_ && pubkey->kid_ != jwt.kid_) { - continue; - } - - // The same alg must be used. - if (pubkey->alg_specified_ && pubkey->alg_ != jwt.alg_) { - continue; - } - kid_alg_matched = true; - - if (pubkey->kty_ == "EC" && - VerifySignatureEC(pubkey->ec_key_.get(), jwt.signature_, signed_data)) { - // Verification succeeded. - return true; - } else if ((pubkey->pem_format_ || pubkey->kty_ == "RSA") && - VerifySignatureRSA(pubkey->evp_pkey_.get(), jwt.md_, - jwt.signature_, signed_data)) { - // Verification succeeded. - return true; - } - } - - // Verification failed. - if (kid_alg_matched) { - UpdateStatus(Status::JWT_INVALID_SIGNATURE); - } else { - UpdateStatus(Status::KID_ALG_UNMATCH); - } - return false; -} - -// Returns the parsed header. -Json::ObjectSharedPtr Jwt::Header() { return header_; } - -const std::string &Jwt::HeaderStr() { return header_str_; } -const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; } -const std::string &Jwt::Alg() { return alg_; } -const std::string &Jwt::Kid() { return kid_; } - -// Returns payload JSON. -Json::ObjectSharedPtr Jwt::Payload() { return payload_; } - -const std::string &Jwt::PayloadStr() { return payload_str_; } -const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; } -const std::string &Jwt::Iss() { return iss_; } -const std::vector &Jwt::Aud() { return aud_; } -const std::string &Jwt::Sub() { return sub_; } -int64_t Jwt::Exp() { return exp_; } - -void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { - keys_.clear(); - std::unique_ptr key_ptr(new Pubkey()); - EvpPkeyGetter e; - key_ptr->evp_pkey_ = e.EvpPkeyFromStr(pkey_pem); - key_ptr->pem_format_ = true; - UpdateStatus(e.GetStatus()); - if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(key_ptr)); - } -} - -void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { - keys_.clear(); - - Json::ObjectSharedPtr jwks_json; - try { - jwks_json = Json::Factory::loadFromString(pkey_jwks); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWK_PARSE_ERROR); - return; - } - std::vector keys; - if (!jwks_json->hasObject("keys")) { - UpdateStatus(Status::JWK_NO_KEYS); - return; - } - try { - keys = jwks_json->getObjectArray("keys", true); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWK_BAD_KEYS); - return; - } - - for (auto jwk_json : keys) { - try { - ExtractPubkeyFromJwk(jwk_json); - } catch (Json::Exception &e) { - continue; - } - } - - if (keys_.size() == 0) { - UpdateStatus(Status::JWK_NO_VALID_PUBKEY); - } -} - -void Pubkeys::ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json) { - // Check "kty" parameter, it should exist. - // https://tools.ietf.org/html/rfc7517#section-4.1 - // If "kty" is missing, getString throws an exception. - std::string kty = jwk_json->getString("kty"); - - // Extract public key according to "kty" value. - // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty == "EC") { - ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty == "RSA") { - ExtractPubkeyFromJwkRSA(jwk_json); - } -} - -void Pubkeys::ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json) { - std::unique_ptr pubkey(new Pubkey()); - std::string n_str, e_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; - } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_.compare(0, 2, "RS") != 0) { - return; - } - pubkey->alg_specified_ = true; - } - pubkey->kty_ = jwk_json->getString("kty"); - n_str = jwk_json->getString("n"); - e_str = jwk_json->getString("e"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; - } - - EvpPkeyGetter e; - pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); - keys_.push_back(std::move(pubkey)); -} - -void Pubkeys::ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json) { - std::unique_ptr pubkey(new Pubkey()); - std::string x_str, y_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; - } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_ != "ES256") { - return; - } - pubkey->alg_specified_ = true; - } - pubkey->kty_ = jwk_json->getString("kty"); - x_str = jwk_json->getString("x"); - y_str = jwk_json->getString("y"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; - } - - EvpPkeyGetter e; - pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); - keys_.push_back(std::move(pubkey)); -} - -std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, - Type type) { - std::unique_ptr keys(new Pubkeys()); - switch (type) { - case Type::JWKS: - keys->CreateFromJwksCore(pkey); - break; - case Type::PEM: - keys->CreateFromPemCore(pkey); - break; - default: - PANIC("can not reach here"); - } - return keys; -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h deleted file mode 100644 index ebe9f6934ad..00000000000 --- a/src/envoy/http/jwt_auth/jwt.h +++ /dev/null @@ -1,301 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/json/json_object.h" -#include "openssl/ec.h" -#include "openssl/evp.h" - -#include -#include -#include - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -enum class Status { - OK = 0, - - // JWT token is required. - JWT_MISSED = 1, - - // Token expired. - JWT_EXPIRED = 2, - - // Given JWT is not in the form of Header.Payload.Signature - JWT_BAD_FORMAT = 3, - - // Header is an invalid Base64url input or an invalid JSON. - JWT_HEADER_PARSE_ERROR = 4, - - // Header does not have "alg". - JWT_HEADER_NO_ALG = 5, - - // "alg" in the header is not a string. - JWT_HEADER_BAD_ALG = 6, - - // Signature is an invalid Base64url input. - JWT_SIGNATURE_PARSE_ERROR = 7, - - // Signature Verification failed (= Failed in DigestVerifyFinal()) - JWT_INVALID_SIGNATURE = 8, - - // Signature is valid but payload is an invalid Base64url input or an invalid - // JSON. - JWT_PAYLOAD_PARSE_ERROR = 9, - - // "kid" in the JWT header is not a string. - JWT_HEADER_BAD_KID = 10, - - // Issuer is not configured. - JWT_UNKNOWN_ISSUER = 11, - - // JWK is an invalid JSON. - JWK_PARSE_ERROR = 12, - - // JWK does not have "keys". - JWK_NO_KEYS = 13, - - // "keys" in JWK is not an array. - JWK_BAD_KEYS = 14, - - // There are no valid public key in given JWKs. - JWK_NO_VALID_PUBKEY = 15, - - // There is no key the kid and the alg of which match those of the given JWT. - KID_ALG_UNMATCH = 16, - - // Value of "alg" in the header is invalid. - ALG_NOT_IMPLEMENTED = 17, - - // Given PEM formatted public key is an invalid Base64 input. - PEM_PUBKEY_BAD_BASE64 = 18, - - // A parse error on PEM formatted public key happened. - PEM_PUBKEY_PARSE_ERROR = 19, - - // "n" or "e" field of a JWK has a parse error or is missing. - JWK_RSA_PUBKEY_PARSE_ERROR = 20, - - // Failed to create a EC_KEY object. - FAILED_CREATE_EC_KEY = 21, - - // "x" or "y" field of a JWK has a parse error or is missing. - JWK_EC_PUBKEY_PARSE_ERROR = 22, - - // Failed to create ECDSA_SIG object. - FAILED_CREATE_ECDSA_SIGNATURE = 23, - - // Audience is not allowed. - AUDIENCE_NOT_ALLOWED = 24, - - // Failed to fetch public key - FAILED_FETCH_PUBKEY = 25, -}; - -std::string StatusToString(Status status); - -std::string Base64UrlDecode(std::string input); - -// Base class to keep the status that represents "OK" or the first failure -// reason -class WithStatus { - public: - WithStatus() : status_(Status::OK) {} - Status GetStatus() const { return status_; } - - protected: - void UpdateStatus(Status status) { - // Not overwrite failure status to keep the reason of the first failure - if (status_ == Status::OK) { - status_ = status; - } - } - - private: - Status status_; -}; - -class Pubkeys; -class Jwt; - -// JWT Verifier class. -// -// Usage example: -// Verifier v; -// Jwt jwt(jwt_string); -// std::unique_ptr pubkey = ... -// if (v.Verify(jwt, *pubkey)) { -// auto payload = jwt.Payload(); -// ... -// } else { -// Status s = v.GetStatus(); -// ... -// } -class Verifier : public WithStatus { - public: - // This function verifies JWT signature. - // If verification failed, GetStatus() returns the failture reason. - // When the given JWT has a format error, this verification always fails and - // the JWT's status is handed over to Verifier. - // When pubkeys.GetStatus() is not equal to Status::OK, this verification - // always fails and the public key's status is handed over to Verifier. - bool Verify(const Jwt& jwt, const Pubkeys& pubkeys); - - private: - // Functions to verify with single public key. - // (Note: Pubkeys object passed to Verify() may contains multiple public keys) - // When verification fails, UpdateStatus() is NOT called. - bool VerifySignatureRSA(EVP_PKEY* key, const EVP_MD* md, - const uint8_t* signature, size_t signature_len, - const uint8_t* signed_data, size_t signed_data_len); - bool VerifySignatureRSA(EVP_PKEY* key, const EVP_MD* md, - const std::string& signature, - const std::string& signed_data); - bool VerifySignatureEC(EC_KEY* key, const std::string& signature, - const std::string& signed_data); - bool VerifySignatureEC(EC_KEY* key, const uint8_t* signature, - size_t signature_len, const uint8_t* signed_data, - size_t signed_data_len); -}; - -// Class to parse and a hold a JWT. -// It also holds the failure reason if parse failed. -// -// Usage example: -// Jwt jwt(jwt_string); -// if(jwt.GetStatus() == Status::OK) { ... } -class Jwt : public WithStatus { - public: - // This constructor parses the given JWT and prepares for verification. - // You can check if the setup was successfully done by seeing if GetStatus() - // == Status::OK. When the given JWT has a format error, GetStatus() returns - // the error detail. - Jwt(const std::string& jwt); - - // It returns a pointer to a JSON object of the header of the given JWT. - // When the given JWT has a format error, it returns nullptr. - // It returns the header JSON even if the signature is invalid. - Json::ObjectSharedPtr Header(); - - // They return a string (or base64url-encoded string) of the header JSON of - // the given JWT. - const std::string& HeaderStr(); - const std::string& HeaderStrBase64Url(); - - // They return the "alg" (or "kid") value of the header of the given JWT. - const std::string& Alg(); - - // It returns the "kid" value of the header of the given JWT, or an empty - // string if "kid" does not exist in the header. - const std::string& Kid(); - - // It returns a pointer to a JSON object of the payload of the given JWT. - // When the given jWT has a format error, it returns nullptr. - // It returns the payload JSON even if the signature is invalid. - Json::ObjectSharedPtr Payload(); - - // They return a string (or base64url-encoded string) of the payload JSON of - // the given JWT. - const std::string& PayloadStr(); - const std::string& PayloadStrBase64Url(); - - // It returns the "iss" claim value of the given JWT, or an empty string if - // "iss" claim does not exist. - const std::string& Iss(); - - // It returns the "aud" claim value of the given JWT, or an empty string if - // "aud" claim does not exist. - const std::vector& Aud(); - - // It returns the "sub" claim value of the given JWT, or an empty string if - // "sub" claim does not exist. - const std::string& Sub(); - - // It returns the "exp" claim value of the given JWT, or 0 if "exp" claim does - // not exist. - int64_t Exp(); - - private: - const EVP_MD* md_; - - Json::ObjectSharedPtr header_; - std::string header_str_; - std::string header_str_base64url_; - Json::ObjectSharedPtr payload_; - std::string payload_str_; - std::string payload_str_base64url_; - std::string signature_; - std::string alg_; - std::string kid_; - std::string iss_; - std::vector aud_; - std::string sub_; - int64_t exp_; - - /* - * TODO: try not to use friend function - */ - friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys); -}; - -// Class to parse and a hold public key(s). -// It also holds the failure reason if parse failed. -// -// Usage example: -// std::unique_ptr keys = Pubkeys::ParseFromJwks(jwks_string); -// if(keys->GetStatus() == Status::OK) { ... } -class Pubkeys : public WithStatus { - public: - // Format of public key. - enum Type { PEM, JWKS }; - - Pubkeys(){}; - static std::unique_ptr CreateFrom(const std::string& pkey, - Type type); - - private: - void CreateFromPemCore(const std::string& pkey_pem); - void CreateFromJwksCore(const std::string& pkey_jwks); - // Extracts the public key from a jwk key (jkey) and sets it to keys_; - void ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json); - - class Pubkey { - public: - Pubkey(){}; - bssl::UniquePtr evp_pkey_; - bssl::UniquePtr ec_key_; - std::string kid_; - std::string kty_; - bool alg_specified_ = false; - bool kid_specified_ = false; - bool pem_format_ = false; - std::string alg_; - }; - std::vector > keys_; - - /* - * TODO: try not to use friend function - */ - friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys); -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt_authenticator.cc b/src/envoy/http/jwt_auth/jwt_authenticator.cc deleted file mode 100644 index b535d31f9a3..00000000000 --- a/src/envoy/http/jwt_auth/jwt_authenticator.cc +++ /dev/null @@ -1,247 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/jwt_auth/jwt_authenticator.h" -#include "common/http/message_impl.h" -#include "common/http/utility.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { -namespace { - -// The HTTP header to pass verified token payload. -const LowerCaseString kJwtPayloadKey("sec-istio-auth-userinfo"); - -// Extract host and path from a URI -void ExtractUriHostPath(const std::string& uri, std::string* host, - std::string* path) { - // Example: - // uri = "https://example.com/certs" - // pos : ^ - // pos1 : ^ - // host = "example.com" - // path = "/certs" - auto pos = uri.find("://"); - pos = pos == std::string::npos ? 0 : pos + 3; // Start position of host - auto pos1 = uri.find("/", pos); - if (pos1 == std::string::npos) { - // If uri doesn't have "/", the whole string is treated as host. - *host = uri.substr(pos); - *path = "/"; - } else { - *host = uri.substr(pos, pos1 - pos); - *path = "/" + uri.substr(pos1 + 1); - } -} - -} // namespace - -JwtAuthenticator::JwtAuthenticator(Upstream::ClusterManager& cm, - JwtAuthStore& store) - : cm_(cm), store_(store) {} - -// Verify a JWT token. -void JwtAuthenticator::Verify(HeaderMap& headers, - JwtAuthenticator::Callbacks* callback) { - headers_ = &headers; - callback_ = callback; - - ENVOY_LOG(debug, "Jwt authentication starts"); - std::vector> tokens; - store_.token_extractor().Extract(headers, &tokens); - if (tokens.size() == 0) { - if (OkToBypass()) { - DoneWithStatus(Status::OK); - } else { - DoneWithStatus(Status::JWT_MISSED); - } - return; - } - - // Only take the first one now. - token_.swap(tokens[0]); - - jwt_.reset(new Jwt(token_->token())); - if (jwt_->GetStatus() != Status::OK) { - DoneWithStatus(jwt_->GetStatus()); - return; - } - - // Check "exp" claim. - const auto unix_timestamp = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - if (jwt_->Exp() < unix_timestamp) { - DoneWithStatus(Status::JWT_EXPIRED); - return; - } - - // Check if token is extracted from the location specified by the issuer. - if (!token_->IsIssuerAllowed(jwt_->Iss())) { - ENVOY_LOG(debug, "Token for issuer {} did not specify extract location", - jwt_->Iss()); - DoneWithStatus(Status::JWT_UNKNOWN_ISSUER); - return; - } - - // Check the issuer is configured or not. - auto issuer = store_.pubkey_cache().LookupByIssuer(jwt_->Iss()); - if (!issuer) { - DoneWithStatus(Status::JWT_UNKNOWN_ISSUER); - return; - } - - // Check if audience is allowed - if (!issuer->IsAudienceAllowed(jwt_->Aud())) { - DoneWithStatus(Status::AUDIENCE_NOT_ALLOWED); - return; - } - - if (issuer->pubkey() && !issuer->Expired()) { - VerifyKey(*issuer); - return; - } - - FetchPubkey(issuer); -} - -void JwtAuthenticator::FetchPubkey(PubkeyCacheItem* issuer) { - uri_ = issuer->jwt_config().remote_jwks().http_uri().uri(); - std::string host, path; - ExtractUriHostPath(uri_, &host, &path); - - MessagePtr message(new RequestMessageImpl()); - message->headers().insertMethod().value().setReference( - Http::Headers::get().MethodValues.Get); - message->headers().insertPath().value(path); - message->headers().insertHost().value(host); - - const auto& cluster = issuer->jwt_config().remote_jwks().http_uri().cluster(); - if (cm_.get(cluster) == nullptr) { - DoneWithStatus(Status::FAILED_FETCH_PUBKEY); - return; - } - - ENVOY_LOG(debug, "fetch pubkey from [uri = {}]: start", uri_); - request_ = cm_.httpAsyncClientForCluster(cluster).send( - std::move(message), *this, absl::optional()); -} - -void JwtAuthenticator::onSuccess(MessagePtr&& response) { - request_ = nullptr; - uint64_t status_code = Http::Utility::getResponseStatus(response->headers()); - if (status_code == 200) { - ENVOY_LOG(debug, "fetch pubkey [uri = {}]: success", uri_); - std::string body; - if (response->body()) { - auto len = response->body()->length(); - body = std::string(static_cast(response->body()->linearize(len)), - len); - } else { - ENVOY_LOG(debug, "fetch pubkey [uri = {}]: body is empty", uri_); - } - OnFetchPubkeyDone(body); - } else { - ENVOY_LOG(debug, "fetch pubkey [uri = {}]: response status code {}", uri_, - status_code); - DoneWithStatus(Status::FAILED_FETCH_PUBKEY); - } -} - -void JwtAuthenticator::onFailure(AsyncClient::FailureReason) { - request_ = nullptr; - ENVOY_LOG(debug, "fetch pubkey [uri = {}]: failed", uri_); - DoneWithStatus(Status::FAILED_FETCH_PUBKEY); -} - -void JwtAuthenticator::onDestroy() { - if (request_) { - request_->cancel(); - request_ = nullptr; - ENVOY_LOG(debug, "fetch pubkey [uri = {}]: canceled", uri_); - } -} - -// Handle the public key fetch done event. -void JwtAuthenticator::OnFetchPubkeyDone(const std::string& pubkey) { - auto issuer = store_.pubkey_cache().LookupByIssuer(jwt_->Iss()); - Status status = issuer->SetRemoteJwks(pubkey); - if (status != Status::OK) { - DoneWithStatus(status); - } else { - VerifyKey(*issuer); - } -} - -// Verify with a specific public key. -void JwtAuthenticator::VerifyKey(const PubkeyCacheItem& issuer_item) { - JwtAuth::Verifier v; - if (!v.Verify(*jwt_, *issuer_item.pubkey())) { - DoneWithStatus(v.GetStatus()); - return; - } - - // TODO(lei-tang): remove this backward compatibility. - // Tracking issue: https://github.com/istio/istio/issues/4744 - headers_->addReferenceKey(kJwtPayloadKey, jwt_->PayloadStrBase64Url()); - - if (!issuer_item.jwt_config().forward_payload_header().empty()) { - const LowerCaseString key( - issuer_item.jwt_config().forward_payload_header()); - if (key.get() != kJwtPayloadKey.get()) { - headers_->addCopy(key, jwt_->PayloadStrBase64Url()); - } - } - - if (!issuer_item.jwt_config().forward()) { - // Remove JWT from headers. - token_->Remove(headers_); - } - - DoneWithStatus(Status::OK); -} - -bool JwtAuthenticator::OkToBypass() { - if (store_.config().allow_missing_or_failed()) { - return true; - } - - // TODO: use bypass field - return false; -} - -void JwtAuthenticator::DoneWithStatus(const Status& status) { - ENVOY_LOG(debug, "Jwt authentication completed with: {}", - JwtAuth::StatusToString(status)); - ENVOY_LOG(debug, - "The value of allow_missing_or_failed in AuthFilterConfig is: {}", - store_.config().allow_missing_or_failed()); - if (store_.config().allow_missing_or_failed()) { - callback_->onDone(JwtAuth::Status::OK); - } else { - callback_->onDone(status); - } - callback_ = nullptr; -} - -const LowerCaseString& JwtAuthenticator::JwtPayloadKey() { - return kJwtPayloadKey; -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt_authenticator.h b/src/envoy/http/jwt_auth/jwt_authenticator.h deleted file mode 100644 index 748b5752799..00000000000 --- a/src/envoy/http/jwt_auth/jwt_authenticator.h +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/http/async_client.h" - -#include "src/envoy/http/jwt_auth/auth_store.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -// A per-request JWT authenticator to handle all JWT authentication: -// * fetch remote public keys and cache them. -class JwtAuthenticator : public Logger::Loggable, - public AsyncClient::Callbacks { - public: - JwtAuthenticator(Upstream::ClusterManager& cm, JwtAuthStore& store); - - // The callback interface to notify the completion event. - class Callbacks { - public: - virtual ~Callbacks() {} - virtual void onDone(const Status& status) PURE; - }; - void Verify(HeaderMap& headers, Callbacks* callback); - - // Called when the object is about to be destroyed. - void onDestroy(); - - // The HTTP header key to carry the verified JWT payload. - static const LowerCaseString& JwtPayloadKey(); - - private: - // Fetch a remote public key. - void FetchPubkey(PubkeyCacheItem* issuer); - // Following two functions are for AyncClient::Callbacks - void onSuccess(MessagePtr&& response); - void onFailure(AsyncClient::FailureReason); - - // Verify with a specific public key. - void VerifyKey(const PubkeyCacheItem& issuer); - - // Handle the public key fetch done event. - void OnFetchPubkeyDone(const std::string& pubkey); - - // Calls the callback with status. - void DoneWithStatus(const Status& status); - - // Return true if it is OK to forward this request without JWT. - bool OkToBypass(); - - // The cluster manager object to make HTTP call. - Upstream::ClusterManager& cm_; - // The cache object. - JwtAuthStore& store_; - // The JWT object. - std::unique_ptr jwt_; - // The token data - std::unique_ptr token_; - - // The HTTP request headers - HeaderMap* headers_{}; - // The on_done function. - Callbacks* callback_{}; - - // The pending uri_, only used for logging. - std::string uri_; - // The pending remote request so it can be canceled. - AsyncClient::Request* request_{}; -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc b/src/envoy/http/jwt_auth/jwt_authenticator_test.cc deleted file mode 100644 index 1d5cdcc44d0..00000000000 --- a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc +++ /dev/null @@ -1,803 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/jwt_auth/jwt_authenticator.h" -#include "common/http/message_impl.h" -#include "common/json/json_loader.h" -#include "gtest/gtest.h" -#include "test/mocks/upstream/mocks.h" -#include "test/test_common/utility.h" - -using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; -using ::testing::Invoke; -using ::testing::NiceMock; -using ::testing::_; - -namespace Envoy { -namespace Http { -namespace JwtAuth { -namespace { - -// RS256 private key -//-----BEGIN PRIVATE KEY----- -// MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC6n3u6qsX0xY49 -// o+TBJoF64A8s6v0UpxpYZ1UQbNDh/dmrlYpVmjDH1MIHGYiY0nWqZSLXekHyi3Az -// +XmV9jUAUEzFVtAJRee0ui+ENqJK9injAYOMXNCJgD6lSryHoxRkGeGV5iuRTteU -// IHA1XI3yo0ySksDsoVljP7jzoadXY0gknH/gEZrcd0rBAbGLa2O5CxC9qjlbjGZJ -// VpoRaikHAzLZCaWFIVC49SlNrLBOpRxSr/pJ8AeFnggNr8XER3ZzbPyAUa1+y31x -// jeVFh/5z9l1uhjeao31K7f6PfPmvZIdaWEH8s0CPJaUEay9sY+VOoPOJhDBk3hoa -// ypUpBv1XAgMBAAECggEAc5HaJJIm/trsqD17pyV6X6arnyxyx7xn80Eii4ZnoNv8 -// VWbJARP4i3e1JIJqdgE3PutctUYP2u0A8h7XbcfHsMcJk9ecA3IX+HKohF71CCkD -// bYH9fgnoVo5lvSTYNcMHGKpyacrdRiImHKQt+M21VgJMpCRfdurAmVbX6YA9Sj6w -// SBFrZbWkBHiHg7w++xKr+VeTHW/8fXI5bvSPAm/XB6dDKAcSXYiJJJhIoaVR9cHn -// 1ePRDLpEwfDpBHeepd/S3qR37mIbHmo8SVytDY2xTUaIoaRfXRWGMYSyxl0y4RsZ -// Vo6Tp9Tj2fyohvB/S+lE34zhxnsHToK2JZvPeoyHCQKBgQDyEcjaUZiPdx7K63CT -// d57QNYC6DTjtKWnfO2q/vAVyAPwS30NcVuXj3/1yc0L+eExpctn8tcLfvDi1xZPY -// dW2L3SZKgRJXL+JHTCEkP8To/qNLhBqitcKYwp0gtpoZbUjZdZwn18QJx7Mw/nFC -// lJhSYRl+FjVolY3qBaS6eD7imwKBgQDFXNmeAV5FFF0FqGRsLYl0hhXTR6Hi/hKQ -// OyRALBW9LUKbsazwWEFGRlqbEWd1OcOF5SSV4d3u7wLQRTDeNELXUFvivok12GR3 -// gNl9nDJ5KKYGFmqxM0pzfbT5m3Lsrr2FTIq8gM9GBpQAOmzQIkEu62yELtt2rRf0 -// 1pTh+UbN9QKBgF88kAEUySjofLzpFElwbpML+bE5MoRcHsMs5Tq6BopryMDEBgR2 -// S8vzfAtjPaBQQ//Yp9q8yAauTsF1Ek2/JXI5d68oSMb0l9nlIcTZMedZB3XWa4RI -// bl8bciZEsSv/ywGDPASQ5xfR8bX85SKEw8jlWto4cprK/CJuRfj3BgaxAoGAAmQf -// ltR5aejXP6xMmyrqEWlWdlrV0UQ2wVyWEdj24nXb6rr6V2caU1mi22IYmMj8X3Dp -// Qo+b+rsWk6Ni9i436RfmJRcd3nMitHfxKp5r1h/x8vzuifsPGdsaCDQj7k4nqafF -// vobo+/Y0cNREYTkpBQKBLBDNQ+DQ+3xmDV7RxskCgYBCo6u2b/DZWFLoq3VpAm8u -// 1ZgL8qxY/bbyA02IKF84QPFczDM5wiLjDGbGnOcIYYMvTHf1LJU4FozzYkB0GicX -// Y0tBQIHaaLWbPk1RZdPfR9kAp16iwk8H+V4UVjLfsTP7ocEfNCzZztmds83h8mTL -// DSwE5aY76Cs8XLcF/GNJRQ== -//-----END PRIVATE KEY----- - -// A good public key -const std::string kPublicKey = - "{\"keys\": [{" - " \"kty\": \"RSA\"," - " \"n\": " - "\"up97uqrF9MWOPaPkwSaBeuAPLOr9FKcaWGdVEGzQ4f3Zq5WKVZowx9TCBxmImNJ1q" - "mUi13pB8otwM_l5lfY1AFBMxVbQCUXntLovhDaiSvYp4wGDjFzQiYA-pUq8h6MUZBnhleYrk" - "U7XlCBwNVyN8qNMkpLA7KFZYz-486GnV2NIJJx_4BGa3HdKwQGxi2tjuQsQvao5W4xmSVaaE" - "WopBwMy2QmlhSFQuPUpTaywTqUcUq_6SfAHhZ4IDa_FxEd2c2z8gFGtfst9cY3lRYf-c_Zdb" - "oY3mqN9Su3-j3z5r2SHWlhB_LNAjyWlBGsvbGPlTqDziYQwZN4aGsqVKQb9Vw\"," - " \"e\": \"AQAB\"," - " \"alg\": \"RS256\"," - " \"kid\": \"62a93512c9ee4c7f8067b5a216dade2763d32a47\"" - "}," - "{" - " \"kty\": \"RSA\"," - " \"n\": " - "\"up97uqrF9MWOPaPkwSaBeuAPLOr9FKcaWGdVEGzQ4f3Zq5WKVZowx9TCBxmImNJ1q" - "mUi13pB8otwM_l5lfY1AFBMxVbQCUXntLovhDaiSvYp4wGDjFzQiYA-pUq8h6MUZBnhleYrk" - "U7XlCBwNVyN8qNMkpLA7KFZYz-486GnV2NIJJx_4BGa3HdKwQGxi2tjuQsQvao5W4xmSVaaE" - "WopBwMy2QmlhSFQuPUpTaywTqUcUq_6SfAHhZ4IDa_FxEd2c2z8gFGtfst9cY3lRYf-c_Zdb" - "oY3mqN9Su3-j3z5r2SHWlhB_LNAjyWlBGsvbGPlTqDziYQwZN4aGsqVKQb9Vw\"," - " \"e\": \"AQAB\"," - " \"alg\": \"RS256\"," - " \"kid\": \"b3319a147514df7ee5e4bcdee51350cc890cc89e\"" - "}]}"; - -// A good JSON config. -const char kExampleConfig[] = R"( -{ - "rules": [ - { - "issuer": "https://example.com", - "audiences": [ - "example_service", - "http://example_service1", - "https://example_service2/" - ], - "remote_jwks": { - "http_uri": { - "uri": "https://pubkey_server/pubkey_path", - "cluster": "pubkey_cluster" - }, - "cache_duration": { - "seconds": 600 - } - }, - "forward_payload_header": "sec-istio-auth-userinfo" - } - ] -} -)"; - -// A JSON config without forward_payload_header configured. -const char kExampleConfigWithoutForwardPayloadHeader[] = R"( -{ - "rules": [ - { - "issuer": "https://example.com", - "audiences": [ - "example_service", - "http://example_service1", - "https://example_service2/" - ], - "remote_jwks": { - "http_uri": { - "uri": "https://pubkey_server/pubkey_path", - "cluster": "pubkey_cluster" - }, - "cache_duration": { - "seconds": 600 - } - }, - } - ] -} -)"; - -// An example JSON config with a good JWT config and allow_missing_or_failed -// option enabled -const char kExampleConfigWithJwtAndAllowMissingOrFailed[] = R"( -{ - "rules": [ - { - "issuer": "https://example.com", - "audiences": [ - "example_service", - "http://example_service1", - "https://example_service2/" - ], - "remote_jwks": { - "http_uri": { - "uri": "https://pubkey_server/pubkey_path", - "cluster": "pubkey_cluster" - }, - "cache_duration": { - "seconds": 600 - } - } - } - ], - "allow_missing_or_failed": true -} -)"; - -// A JSON config for "other_issuer" -const char kOtherIssuerConfig[] = R"( -{ - "rules": [ - { - "issuer": "other_issuer" - } - ] -} -)"; - -// A config with bypass -const char kBypassConfig[] = R"( -{ - "bypass": [ - ] -} -)"; - -// expired token -// {"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":1205005587} -const std::string kExpiredToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTIwNTAwNTU4NywiY" - "XVkIjoiZXhhbXBsZV9zZXJ2aWNlIn0.izDa6aHNgbsbeRzucE0baXIP7SXOrgopYQ" - "ALLFAsKq_N0GvOyqpAZA9nwCAhqCkeKWcL-9gbQe3XJa0KN3FPa2NbW4ChenIjmf2" - "QYXOuOQaDu9QRTdHEY2Y4mRy6DiTZAsBHWGA71_cLX-rzTSO_8aC8eIqdHo898oJw" - "3E8ISKdryYjayb9X3wtF6KLgNomoD9_nqtOkliuLElD8grO0qHKI1xQurGZNaoeyi" - "V1AdwgX_5n3SmQTacVN0WcSgk6YJRZG6VE8PjxZP9bEameBmbSB0810giKRpdTU1-" - "RJtjq6aCSTD4CYXtW38T5uko4V-S4zifK3BXeituUTebkgoA"; - -// A token with aud as invalid_service -// Payload: -// {"iss":"https://example.com","sub":"test@example.com","aud":"invalid_service","exp":2001001001} -const std::string kInvalidAudToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiY" - "XVkIjoiaW52YWxpZF9zZXJ2aWNlIn0.B9HuVXpRDVYIvApfNQmE_l5fEMPEiPdi-s" - "dKbTione8I_UsnYHccKZVegaF6f2uyWhAvaTPgaMosyDlJD6skadEcmZD0V4TzsYK" - "v7eP5FQga26hZ1Kra7n9hAq4oFfH0J8aZLOvDV3tAgCNRXlh9h7QiBPeDNQlwztqE" - "csyp1lHI3jdUhsn3InIn-vathdx4PWQWLVb-74vwsP-END-MGlOfu_TY5OZUeY-GB" - "E4Wr06aOSU2XQjuNr6y2WJGMYFsKKWfF01kHSuyc9hjnq5UI19WrOM8s7LFP4w2iK" - "WFIPUGmPy3aM0TiF2oFOuuMxdPR3HNdSG7EWWRwoXv7n__jA"; - -// Payload: -// {"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":2001001001} -const std::string kGoodToken = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiY" - "XVkIjoiZXhhbXBsZV9zZXJ2aWNlIn0.cuui_Syud76B0tqvjESE8IZbX7vzG6xA-M" - "Daof1qEFNIoCFT_YQPkseLSUSR2Od3TJcNKk-dKjvUEL1JW3kGnyC1dBx4f3-Xxro" - "yL23UbR2eS8TuxO9ZcNCGkjfvH5O4mDb6cVkFHRDEolGhA7XwNiuVgkGJ5Wkrvshi" - "h6nqKXcPNaRx9lOaRWg2PkE6ySNoyju7rNfunXYtVxPuUIkl0KMq3WXWRb_cb8a_Z" - "EprqSZUzi_ZzzYzqBNVhIJujcNWij7JRra2sXXiSAfKjtxHQoxrX8n4V1ySWJ3_1T" - "H_cJcdfS_RKP7YgXRWC0L16PNF5K7iqRqmjKALNe83ZFnFIw"; - -// Payload: -// {"iss":"https://example.com","sub":"test@example.com","aud":"http://example_service/","exp":2001001001} -const std::string kGoodTokenAudHasProtocolScheme = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiY" - "XVkIjoiaHR0cDovL2V4YW1wbGVfc2VydmljZS8ifQ.gHqO8m3hUZZ8m7EajMQy8vB" - "RL5o3njwU5Pg2NxU4z3AwUP6P_7MoB_ChiByjg_LQ92GjHXbHn1gAQHVOn0hERVwm" - "VYGmNsZHm4k5pmD6orPcYV1i3DdLqqxEVyw2R1XD8bC9zK7Tc8mKTRIJYC4T1QSo8" - "mKTzZ8M-EwAuDYa0CsWGhIfA4o3xChXKPLM2hxA4uM1A6s4AQ4ipNQ5FTgLDabgsC" - "EpfDR3lAXSaug1NE22zX_tm0d9JnC5ZrIk3kwmPJPrnAS2_9RKTQW2e2skpAT8dUV" - "T5aSpQxJmWIkyp4PKWmH6h4H2INS7hWyASZdX4oW-R0PMy3FAd8D6Y8740A"; - -// Payload: -// {"iss":"https://example.com","sub":"test@example.com","aud":"https://example_service1/","exp":2001001001} -const std::string kGoodTokenAudService1 = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiY" - "XVkIjoiaHR0cHM6Ly9leGFtcGxlX3NlcnZpY2UxLyJ9.JJq_-fzbNWykI2npW13hJ" - "F_2_IK9JAlodt_T_kO_kSCb7ngAJvmbDhnIUKp7PX-UCEx_6sehNnLZzZeazGeDgw" - "xcjI4zM7E1bzus_sY_Kl7MSYBx7UyW0rgbEvjJOg681Uwn8MkQh9wfQ-SuzPfe07Y" - "O4bFMuNBiZsxS0j3_agJrbmpEPycNBSIZ0ez3aQpnDyUgZ1ZGBoVOgzXUJDXptb71" - "nzvwse8DINafa5kOhBmQcrIADiOyTVC1IqcOvaftVcS4MTkTeCyzfsqcNQ-VeNPKY" - "3e6wTe9brxbii-IPZFNY-1osQNnfCtYpEDjfvMjwHTielF-b55xq_tUwuqaaQ"; - -// Payload: -// {"iss":"https://example.com","sub":"test@example.com","aud":"http://example_service2","exp":2001001001} -const std::string kGoodTokenAudService2 = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUu" - "Y29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiY" - "XVkIjoiaHR0cDovL2V4YW1wbGVfc2VydmljZTIifQ.XFPQHdA5A2rpoQgMMcCBRcW" - "t8QrwVJAhdTgNqBjga_ebnoWZdzj9C6t-8mYYoCQ6t7bulLFbPzO8iJREo7zxN7Rn" - "F0-15ur16LV7AYeDnH0istAiti9uy3POW3telcN374hbBVdA6sBafGqzeQ8cDpb4o" - "0T_BIy6-kaz3ne4-UEdl8kLrR7UaA_LYrdXGomYKqwH3Q4q4mnV7mpE0YUm98AyI6" - "Thwt7f3DTmHOMBeO_3xrLOOZgNtuXipqupkp9sb-DcCRdSokoFpGSTibvV_8RwkQo" - "W2fdqw_ZD7WOe4sTcK27Uma9exclisHVxzJJbQOW82WdPQGicYaR_EajYzA"; - -} // namespace - -class MockJwtAuthenticatorCallbacks : public JwtAuthenticator::Callbacks { - public: - MOCK_METHOD1(onDone, void(const Status &status)); -}; - -class JwtAuthenticatorTest : public ::testing::Test { - public: - void SetUp() { SetupConfig(kExampleConfig); } - - void SetupConfig(const std::string &json_str) { - google::protobuf::util::Status status = - ::google::protobuf::util::JsonStringToMessage(json_str, &config_); - ASSERT_TRUE(status.ok()); - store_.reset(new JwtAuthStore(config_)); - auth_.reset(new JwtAuthenticator(mock_cm_, *store_)); - } - - JwtAuthentication config_; - std::unique_ptr store_; - std::unique_ptr auth_; - NiceMock mock_cm_; - MockJwtAuthenticatorCallbacks mock_cb_; -}; - -// A mock HTTP upstream with response body. -class MockUpstream { - public: - MockUpstream(Upstream::MockClusterManager &mock_cm, - const std::string &response_body) - : request_(&mock_cm.async_client_), response_body_(response_body) { - ON_CALL(mock_cm.async_client_, send_(_, _, _)) - .WillByDefault( - Invoke([this](MessagePtr &, AsyncClient::Callbacks &cb, - const absl::optional &) - -> AsyncClient::Request * { - Http::MessagePtr response_message(new ResponseMessageImpl( - HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}}})); - response_message->body().reset( - new Buffer::OwnedImpl(response_body_)); - cb.onSuccess(std::move(response_message)); - called_count_++; - return &request_; - })); - } - - int called_count() const { return called_count_; } - - private: - MockAsyncClientRequest request_; - std::string response_body_; - int called_count_{}; -}; - -TEST_F(JwtAuthenticatorTest, TestOkJWTandCache) { - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - - // Test OK pubkey and its cache - for (int i = 0; i < 10; i++) { - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcG" - "xlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiZXhhbXBsZV9zZXJ2" - "aWNlIn0"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - } - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -TEST_F(JwtAuthenticatorTest, TestOkJWTPubkeyNoAlg) { - // Test OK pubkey with no "alg" claim. - std::string alg_claim = " \"alg\": \"RS256\","; - std::string pubkey_no_alg = kPublicKey; - std::size_t alg_pos = pubkey_no_alg.find(alg_claim); - while (alg_pos != std::string::npos) { - pubkey_no_alg.erase(alg_pos, alg_claim.length()); - alg_pos = pubkey_no_alg.find(alg_claim); - } - MockUpstream mock_pubkey(mock_cm_, pubkey_no_alg); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcG" - "xlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiZXhhbXBsZV9zZXJ2" - "aWNlIn0"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -TEST_F(JwtAuthenticatorTest, TestOkJWTPubkeyNoKid) { - // Test OK pubkey with no "kid" claim. - std::string kid_claim1 = - ", \"kid\": \"62a93512c9ee4c7f8067b5a216dade2763d32a47\""; - std::string kid_claim2 = - ", \"kid\": \"b3319a147514df7ee5e4bcdee51350cc890cc89e\""; - std::string pubkey_no_kid = kPublicKey; - std::size_t kid_pos = pubkey_no_kid.find(kid_claim1); - pubkey_no_kid.erase(kid_pos, kid_claim1.length()); - kid_pos = pubkey_no_kid.find(kid_claim2); - pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - - MockUpstream mock_pubkey(mock_cm_, pubkey_no_kid); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcG" - "xlLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiZXhhbXBsZV9zZXJ2" - "aWNlIn0"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -// Verifies that a JWT with aud: http://example_service/ is matched to -// example_service in config. -TEST_F(JwtAuthenticatorTest, TestOkJWTAudService) { - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - - // Test OK pubkey and its cache - auto headers = TestHeaderMapImpl{ - {"Authorization", "Bearer " + kGoodTokenAudHasProtocolScheme}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGx" - "lLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiaHR0cDovL2V4YW1wbG" - "Vfc2VydmljZS8ifQ"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -// Verifies that a JWT with aud: https://example_service1/ is matched to -// a JWT with aud: http://example_service1 in config. -TEST_F(JwtAuthenticatorTest, TestOkJWTAudService1) { - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - - // Test OK pubkey and its cache - auto headers = - TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodTokenAudService1}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGx" - "lLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiaHR0cHM6Ly9leGFtcG" - "xlX3NlcnZpY2UxLyJ9"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -// Verifies that a JWT with aud: http://example_service2 is matched to -// a JWT with aud: https://example_service2/ in config. -TEST_F(JwtAuthenticatorTest, TestOkJWTAudService2) { - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - - // Test OK pubkey and its cache - auto headers = - TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodTokenAudService2}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - EXPECT_EQ(headers.get_("sec-istio-auth-userinfo"), - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGx" - "lLmNvbSIsImV4cCI6MjAwMTAwMTAwMSwiYXVkIjoiaHR0cDovL2V4YW1wbG" - "Vfc2VydmljZTIifQ"); - // Verify the token is removed. - EXPECT_FALSE(headers.Authorization()); - - EXPECT_EQ(mock_pubkey.called_count(), 1); -} - -TEST_F(JwtAuthenticatorTest, TestForwardJwt) { - // Confit forward_jwt flag - config_.mutable_rules(0)->set_forward(true); - // Re-create store and auth objects. - store_.reset(new JwtAuthStore(config_)); - auth_.reset(new JwtAuthenticator(mock_cm_, *store_)); - - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - - // Test OK pubkey and its cache - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - - // Verify the token is NOT removed. - EXPECT_TRUE(headers.Authorization()); -} - -TEST_F(JwtAuthenticatorTest, TestMissedJWT) { - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWT_MISSED); - })); - - // Empty headers. - auto headers = TestHeaderMapImpl{}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestMissingJwtWhenAllowMissingOrFailedIsTrue) { - // In this test, when JWT is missing, the status should still be OK - // because allow_missing_or_failed is true. - SetupConfig(kExampleConfigWithJwtAndAllowMissingOrFailed); - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - // Empty headers. - auto headers = TestHeaderMapImpl{}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestInValidJwtWhenAllowMissingOrFailedIsTrue) { - // In this test, when JWT is invalid, the status should still be OK - // because allow_missing_or_failed is true. - SetupConfig(kExampleConfigWithJwtAndAllowMissingOrFailed); - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - std::string token = "invalidToken"; - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + token}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestBypassJWT) { - SetupConfig(kBypassConfig); - - // TODO: enable Bypass test - return; - - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)) - .WillOnce(Invoke( - // Empty header, rejected. - [](const Status &status) { ASSERT_EQ(status, Status::JWT_MISSED); })) - .WillOnce(Invoke( - // CORS header, OK - [](const Status &status) { ASSERT_EQ(status, Status::OK); })) - .WillOnce(Invoke( - // healthz header, OK - [](const Status &status) { ASSERT_EQ(status, Status::OK); })); - - // Empty headers. - auto empty_headers = TestHeaderMapImpl{}; - auth_->Verify(empty_headers, &mock_cb_); - - // CORS headers - auto cors_headers = - TestHeaderMapImpl{{":method", "OPTIONS"}, {":path", "/any/path"}}; - auth_->Verify(cors_headers, &mock_cb_); - - // healthz headers - auto healthz_headers = - TestHeaderMapImpl{{":method", "GET"}, {":path", "/healthz"}}; - auth_->Verify(healthz_headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestInvalidJWT) { - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWT_BAD_FORMAT); - })); - - std::string token = "invalidToken"; - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + token}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestInvalidPrefix) { - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWT_MISSED); - })); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer-invalid"}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestExpiredJWT) { - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWT_EXPIRED); - })); - - auto headers = - TestHeaderMapImpl{{"Authorization", "Bearer " + kExpiredToken}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestNonMatchAudJWT) { - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::AUDIENCE_NOT_ALLOWED); - })); - - auto headers = - TestHeaderMapImpl{{"Authorization", "Bearer " + kInvalidAudToken}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestWrongCluster) { - // Get returns nullptr - EXPECT_CALL(mock_cm_, get(_)) - .WillOnce(Invoke( - [](const std::string &cluster) -> Upstream::ThreadLocalCluster * { - EXPECT_EQ(cluster, "pubkey_cluster"); - return nullptr; - })); - - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::FAILED_FETCH_PUBKEY); - })); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestIssuerNotFound) { - // Create a config with an other issuer. - SetupConfig(kOtherIssuerConfig); - - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)).Times(0); - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWT_UNKNOWN_ISSUER); - })); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - auth_->Verify(headers, &mock_cb_); -} - -TEST_F(JwtAuthenticatorTest, TestPubkeyFetchFail) { - NiceMock async_client; - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)) - .WillOnce(Invoke([&](const std::string &cluster) -> Http::AsyncClient & { - EXPECT_EQ(cluster, "pubkey_cluster"); - return async_client; - })); - - MockAsyncClientRequest request(&async_client); - AsyncClient::Callbacks *callbacks; - EXPECT_CALL(async_client, send_(_, _, _)) - .WillOnce(Invoke([&](MessagePtr &message, AsyncClient::Callbacks &cb, - const absl::optional &) - -> AsyncClient::Request * { - EXPECT_EQ((TestHeaderMapImpl{ - {":method", "GET"}, - {":path", "/pubkey_path"}, - {":authority", "pubkey_server"}, - }), - message->headers()); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::FAILED_FETCH_PUBKEY); - })); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - auth_->Verify(headers, &mock_cb_); - - Http::MessagePtr response_message(new ResponseMessageImpl( - HeaderMapPtr{new TestHeaderMapImpl{{":status", "401"}}})); - callbacks->onSuccess(std::move(response_message)); -} - -TEST_F(JwtAuthenticatorTest, TestInvalidPubkey) { - NiceMock async_client; - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)) - .WillOnce(Invoke([&](const std::string &cluster) -> Http::AsyncClient & { - EXPECT_EQ(cluster, "pubkey_cluster"); - return async_client; - })); - - MockAsyncClientRequest request(&async_client); - AsyncClient::Callbacks *callbacks; - EXPECT_CALL(async_client, send_(_, _, _)) - .WillOnce(Invoke([&](MessagePtr &message, AsyncClient::Callbacks &cb, - const absl::optional &) - -> AsyncClient::Request * { - EXPECT_EQ((TestHeaderMapImpl{ - {":method", "GET"}, - {":path", "/pubkey_path"}, - {":authority", "pubkey_server"}, - }), - message->headers()); - callbacks = &cb; - return &request; - })); - - EXPECT_CALL(mock_cb_, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::JWK_PARSE_ERROR); - })); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - auth_->Verify(headers, &mock_cb_); - - Http::MessagePtr response_message(new ResponseMessageImpl( - HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}}})); - response_message->body().reset(new Buffer::OwnedImpl("invalid publik key")); - callbacks->onSuccess(std::move(response_message)); -} - -TEST_F(JwtAuthenticatorTest, TestOnDestroy) { - NiceMock async_client; - EXPECT_CALL(mock_cm_, httpAsyncClientForCluster(_)) - .WillOnce(Invoke([&](const std::string &cluster) -> Http::AsyncClient & { - EXPECT_EQ(cluster, "pubkey_cluster"); - return async_client; - })); - - MockAsyncClientRequest request(&async_client); - AsyncClient::Callbacks *callbacks; - EXPECT_CALL(async_client, send_(_, _, _)) - .WillOnce(Invoke([&](MessagePtr &message, AsyncClient::Callbacks &cb, - const absl::optional &) - -> AsyncClient::Request * { - EXPECT_EQ((TestHeaderMapImpl{ - {":method", "GET"}, - {":path", "/pubkey_path"}, - {":authority", "pubkey_server"}, - }), - message->headers()); - callbacks = &cb; - return &request; - })); - - // Cancel is called once. - EXPECT_CALL(request, cancel()).Times(1); - - // onDone() should not be called. - EXPECT_CALL(mock_cb_, onDone(_)).Times(0); - - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - auth_->Verify(headers, &mock_cb_); - - // Destroy the authenticating process. - auth_->onDestroy(); -} - -TEST_F(JwtAuthenticatorTest, TestNoForwardPayloadHeader) { - // In this config, there is no forward_payload_header - SetupConfig(kExampleConfigWithoutForwardPayloadHeader); - MockUpstream mock_pubkey(mock_cm_, kPublicKey); - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - auth_->Verify(headers, &mock_cb); - - // Test when forward_payload_header is not set, the output should still - // contain the sec-istio-auth-userinfo header for backward compatibility. - EXPECT_TRUE(headers.has("sec-istio-auth-userinfo")); - // In addition, the sec-istio-auth-userinfo header should be the only header - EXPECT_EQ(headers.size(), 1); -} - -TEST_F(JwtAuthenticatorTest, TestInlineJwks) { - // Change the config to use local_jwks.inline_string - auto rule0 = config_.mutable_rules(0); - rule0->clear_remote_jwks(); - auto local_jwks = rule0->mutable_local_jwks(); - local_jwks->set_inline_string(kPublicKey); - - // recreate store and auth with modified config. - store_.reset(new JwtAuthStore(config_)); - auth_.reset(new JwtAuthenticator(mock_cm_, *store_)); - - MockUpstream mock_pubkey(mock_cm_, ""); - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + kGoodToken}}; - - MockJwtAuthenticatorCallbacks mock_cb; - EXPECT_CALL(mock_cb, onDone(_)).WillOnce(Invoke([](const Status &status) { - ASSERT_EQ(status, Status::OK); - })); - - auth_->Verify(headers, &mock_cb); - EXPECT_EQ(mock_pubkey.called_count(), 0); -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc deleted file mode 100644 index 48bf73f9edf..00000000000 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ /dev/null @@ -1,699 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "jwt.h" - -#include "common/common/utility.h" -#include "common/json/json_loader.h" -#include "test/test_common/utility.h" - -#include - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -class DatasetPem { - public: - // JWT with - // Header: {"alg":"RS256","typ":"JWT"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwt = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0.FxT92eaBr9thDpeWaQh0YFhblVggn86DBpnTa_" - "DVO4mNoGEkdpuhYq3epHPAs9EluuxdSkDJ3fCoI758ggGDw8GbqyJAcOsH10fBOrQbB7EFRB" - "CI1xz6-6GEUac5PxyDnwy3liwC_" - "gK6p4yqOD13EuEY5aoYkeM382tDFiz5Jkh8kKbqKT7h0bhIimniXLDz6iABeNBFouczdPf04" - "N09hdvlCtAF87Fu1qqfwEQ93A-J7m08bZJoyIPcNmTcYGHwfMR4-lcI5cC_93C_" - "5BGE1FHPLOHpNghLuM6-rhOtgwZc9ywupn_bBK3QzuAoDnYwpqQhgQL_CdUD_bSHcmWFkw"; - - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058, - // aud: [aud1, aud2] } - // signature part is invalid. - const std::string kJwtMultiSub = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFmMDZjMTlmOGU1YjMzMTUyMT" - "ZkZjAxMGZkMmI5YTkzYmFjMTM1YzgifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tI" - "iwiaWF0IjoxNTE3ODc1MDU5LCJhdWQiOlsiYXVkMSIsImF1ZDIiXSwiZXhwIjoxNTE3ODc" - "4NjU5LCJzdWIiOiJodHRwczovL2V4YW1wbGUuY29tIn0.fzzlfQG2wZpPRRAPa6Yu"; - - const std::string kJwtSub = "test@example.com"; - const std::string kJwtHeaderEncoded = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"; - const std::string kJwtPayloadEncoded = - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0"; - const std::string kJwtSignatureEncoded = - "FxT92eaBr9thDpeWaQh0YFhblVggn86DBpnTa_" - "DVO4mNoGEkdpuhYq3epHPAs9EluuxdSkDJ3fCoI758ggGDw8GbqyJAcOsH10fBOrQbB7EFRB" - "CI1xz6-6GEUac5PxyDnwy3liwC_" - "gK6p4yqOD13EuEY5aoYkeM382tDFiz5Jkh8kKbqKT7h0bhIimniXLDz6iABeNBFouczdPf04" - "N09hdvlCtAF87Fu1qqfwEQ93A-J7m08bZJoyIPcNmTcYGHwfMR4-lcI5cC_93C_" - "5BGE1FHPLOHpNghLuM6-rhOtgwZc9ywupn_bBK3QzuAoDnYwpqQhgQL_CdUD_bSHcmWFkw"; - const std::string kJwtPayload = - R"EOF({"iss":"https://example.com","sub":"test@example.com","exp":1501281058})EOF"; - - const std::string kPublicKey = - "MIIBCgKCAQEAtw7MNxUTxmzWROCD5BqJxmzT7xqc9KsnAjbXCoqEEHDx4WBlfcwk" - "XHt9e/2+Uwi3Arz3FOMNKwGGlbr7clBY3utsjUs8BTF0kO/poAmSTdSuGeh2mSbc" - "VHvmQ7X/kichWwx5Qj0Xj4REU3Gixu1gQIr3GATPAIULo5lj/ebOGAa+l0wIG80N" - "zz1pBtTIUx68xs5ZGe7cIJ7E8n4pMX10eeuh36h+aossePeuHulYmjr4N0/1jG7a" - "+hHYL6nqwOR3ej0VqCTLS0OloC0LuCpLV7CnSpwbp2Qg/c+MDzQ0TH8g8drIzR5h" - "Fe9a3NlNRMXgUU5RqbLnR9zfXr7b9oEszQIDAQAB"; - - // private key: - // "-----BEGIN RSA PRIVATE KEY-----" - // "MIIEowIBAAKCAQEAtw7MNxUTxmzWROCD5BqJxmzT7xqc9KsnAjbXCoqEEHDx4WBl" - // "fcwkXHt9e/2+Uwi3Arz3FOMNKwGGlbr7clBY3utsjUs8BTF0kO/poAmSTdSuGeh2" - // "mSbcVHvmQ7X/kichWwx5Qj0Xj4REU3Gixu1gQIr3GATPAIULo5lj/ebOGAa+l0wI" - // "G80Nzz1pBtTIUx68xs5ZGe7cIJ7E8n4pMX10eeuh36h+aossePeuHulYmjr4N0/1" - // "jG7a+hHYL6nqwOR3ej0VqCTLS0OloC0LuCpLV7CnSpwbp2Qg/c+MDzQ0TH8g8drI" - // "zR5hFe9a3NlNRMXgUU5RqbLnR9zfXr7b9oEszQIDAQABAoIBAQCgQQ8cRZJrSkqG" - // "P7qWzXjBwfIDR1wSgWcD9DhrXPniXs4RzM7swvMuF1myW1/r1xxIBF+V5HNZq9tD" - // "Z07LM3WpqZX9V9iyfyoZ3D29QcPX6RGFUtHIn5GRUGoz6rdTHnh/+bqJ92uR02vx" - // "VPD4j0SNHFrWpxcE0HRxA07bLtxLgNbzXRNmzAB1eKMcrTu/W9Q1zI1opbsQbHbA" - // "CjbPEdt8INi9ij7d+XRO6xsnM20KgeuKx1lFebYN9TKGEEx8BCGINOEyWx1lLhsm" - // "V6S0XGVwWYdo2ulMWO9M0lNYPzX3AnluDVb3e1Yq2aZ1r7t/GrnGDILA1N2KrAEb" - // "AAKHmYNNAoGBAPAv9qJqf4CP3tVDdto9273DA4Mp4Kjd6lio5CaF8jd/4552T3UK" - // "N0Q7N6xaWbRYi6xsCZymC4/6DhmLG/vzZOOhHkTsvLshP81IYpWwjm4rF6BfCSl7" - // "ip+1z8qonrElxes68+vc1mNhor6GGsxyGe0C18+KzpQ0fEB5J4p0OHGnAoGBAMMb" - // "/fpr6FxXcjUgZzRlxHx1HriN6r8Jkzc+wAcQXWyPUOD8OFLcRuvikQ16sa+SlN4E" - // "HfhbFn17ABsikUAIVh0pPkHqMsrGFxDn9JrORXUpNhLdBHa6ZH+we8yUe4G0X4Mc" - // "R7c8OT26p2zMg5uqz7bQ1nJ/YWlP4nLqIytehnRrAoGAT6Rn0JUlsBiEmAylxVoL" - // "mhGnAYAKWZQ0F6/w7wEtPs/uRuYOFM4NY1eLb2AKLK3LqqGsUkAQx23v7PJelh2v" - // "z3bmVY52SkqNIGGnJuGDaO5rCCdbH2EypyCfRSDCdhUDWquSpBv3Dr8aOri2/CG9" - // "jQSLUOtC8ouww6Qow1UkPjMCgYB8kTicU5ysqCAAj0mVCIxkMZqFlgYUJhbZpLSR" - // "Tf93uiCXJDEJph2ZqLOXeYhMYjetb896qx02y/sLWAyIZ0ojoBthlhcLo2FCp/Vh" - // "iOSLot4lOPsKmoJji9fei8Y2z2RTnxCiik65fJw8OG6mSm4HeFoSDAWzaQ9Y8ue1" - // "XspVNQKBgAiHh4QfiFbgyFOlKdfcq7Scq98MA3mlmFeTx4Epe0A9xxhjbLrn362+" - // "ZSCUhkdYkVkly4QVYHJ6Idzk47uUfEC6WlLEAnjKf9LD8vMmZ14yWR2CingYTIY1" - // "LL2jMkSYEJx102t2088meCuJzEsF3BzEWOP8RfbFlciT7FFVeiM4" - // "-----END RSA PRIVATE KEY-----"; - - /* - * jwt with header replaced by - * "{"alg":"RS256","typ":"JWT", this is a invalid json}" - */ - const std::string kJwtWithBadJsonHeader = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIHRoaXMgaXMgYSBpbnZhbGlkIGpzb259." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0." - "ERgdOJdVCrUAaAIMaAG6rgAR7M6ZJUjvKxIMgb9jrfsEVzsetb4UlPsrO-FBA4LUT_" - "xIshL4Bzd0_3w63v7xol2-iAQgW_7PQeeEEqqMcyfkuXEhHu_lXawAlpqKhCmFuyIeYBiSs-" - "RRIqHCutIJSBfcIGLMRcVzpMODfwMMlzjw6SlfMuy68h54TpBt1glvwEg71lVVO7IE3Fxwgl" - "EDR_2MrVwjmyes9TmDgsj_zBHHn_d09kVqV_adYXtVec9fyo7meODQXB_" - "eWm065WsSRFksQn8fidWtrAfdcSzYe2wN0doP-QYzJeWKll15XVRKS67NeENz40Wd_Tr_" - "tyHBHw"; - - /* - * jwt with payload replaced by - * "this is not a json" - */ - const std::string kJwtWithBadJsonPayload = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.dGhpcyBpcyBub3QgYSBqc29u." - "NhQjffMuBkYA9MXq3Fi3h2RRR6vNsYHOjF22GRHRcAEsTHJGYpWsU0MpkWnSJ04Ktx6PFp8f" - "jRUI0bLtLC2R2Nv3VQNfvcZy0cJmlEWGZbRjEA2AwOaMpiKX-6z5BtMic9hG5Aw1IDxf_" - "ZvqiE5nRxPBnMXxsINgJ1veTd0zBhOsr0Y3Onl2O3UJSqrQn4kSqjpTENODjSJcJcfiy15sU" - "MX7wCiP_FSjLAW-" - "mcaa8RdV49LegwB185eK9UmTJ98yTqEN7w9wcKkZFe8vpojkJX8an0EjGOTJ_5IsU1A_" - "Xv1Z1ZQYGTOEsMH8j9zWslYTRq15bDIyALHRD46UHqjDSQ"; - - /* - * jwt with header replaced by - * "{"typ":"JWT"}" - */ - const std::string kJwtWithAlgAbsent = - "eyJ0eXAiOiJKV1QifQ." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0" - "." - "MGJmMDU2YjViZmJhMzE5MGI3MTRiMmE4NDhiMmIzNzI2Mzk3MGUwOGVmZTAwMzc0YzY4MWFj" - "NzgzMDZjZWRlYgoyZWY3Mzk2NWE2MjYxZWI2M2FhMGFjM2E1NDQ1MjNmMjZmNjU2Y2MxYWIz" - "YTczNGFlYTg4ZDIyN2YyZmM4YTI5CjM5OTQwNjI2ZjI3ZDlmZTM4M2JjY2NhZjIxMmJlY2U5" - "Y2Q3NGY5YmY2YWY2ZDI2ZTEyNDllMjU4NGVhZTllMGQKMzg0YzVlZmUzY2ZkMWE1NzM4YTIw" - "MzBmYTQ0OWY0NDQ1MTNhOTQ4NTRkMzgxMzdkMTljMWQ3ZmYxYjNlMzJkMQoxMGMyN2VjZDQ5" - "MTMzNjZiZmE4Zjg3ZTEyNWQzMGEwYjhhYjUyYWE5YzZmZTcwM2FmZDliMjkzODY3OWYxNWQy" - "CjZiNWIzZjgzYTk0Zjg1MjFkMDhiNmYyNzY1MTM1N2MyYWI0MzBkM2FlYjg5MTFmNjM5ZGNj" - "MGM2NTcxNThmOWUKOWQ1ZDM2NWFkNGVjOTgwYmNkY2RiMDUzM2MzYjY2MjJmYWJiMDViNjNk" - "NjIxMDJiZDkyZDE3ZjZkZDhiMTBkOQo1YjBlMDRiZWU2MDBjNjRhNzM0ZGE1ZGY2YjljMGY5" - "ZDM1Mzk3MjcyNDcyN2RjMTViYjk1MjMwZjdmYmU5MzYx"; - - /* - * jwt with header replaced by - * "{"alg":256,"typ":"JWT"}" - */ - const std::string kJwtWithAlgIsNotString = - "eyJhbGciOjI1NiwidHlwIjoiSldUIn0." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0." - "ODYxMDhhZjY5MjEyMGQ4ZjE5YzMzYmQzZDY3MmE1NjFjNDM1NzdhYmNhNDM0Njg2MWMwNGI4" - "ZDNhZDExNjUxZgphZTU0ZjMzZWVmMWMzYmQyOTEwNGIxNTA3ZDllZTI0ZmY0OWFkZTYwNGUz" - "MGUzMWIxN2MwMTIzNTY0NDYzNjBlCjEyZDk3ZGRiMDAwZDgwMDFmYjcwOTIzZDYzY2VhMzE1" - "MjcyNzdlY2RhYzZkMWU5MThmOThjOTFkNTZiM2NhYWIKNjA0ZThiNWI4N2MwNWM4M2RkODE4" - "NWYwNDBiYjY4Yjk3MmY5MDc2YmYzYTk3ZjM0OWVhYjA1ZTdjYzdhOGEzZApjMGU4Y2Y0MzJl" - "NzY2MDAwYTQ0ZDg1ZmE5MjgzM2ExNjNjOGM3OTllMTEyNTM5OWMzYzY3MThiMzY2ZjU5YTVl" - "CjVjYTdjZTBmNDdlMjZhYjU3M2Y2NDI4ZmRmYzQ2N2NjZjQ4OWFjNTA1OTRhM2NlYTlhNTE1" - "ODJhMDE1ODA2YzkKZmRhNTFmODliNTk3NjA4Njg2NzNiMDUwMzdiY2IzOTQzMzViYzU2YmFk" - "ODUyOWIwZWJmMjc1OTkxMTkzZjdjMwo0MDFjYWRlZDI4NjA2MmNlZTFhOGU3YWFiMDJiNjcy" - "NGVhYmVmMjA3MGQyYzFlMmY3NDRiM2IyNjU0MGQzZmUx"; - - /* - * jwt with header replaced by - * "{"alg":"InvalidAlg","typ":"JWT"}" - */ - const std::string kJwtWithInvalidAlg = - "eyJhbGciOiJJbnZhbGlkQWxnIiwidHlwIjoiSldUIn0." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0." - "MjQ3ZThmMTQ1YWYwZDIyZjVlZDlhZTJhOWIzYWI2OGY5ZTcyZWU1ZmJlNzA1ODE2NjkxZDU3" - "OGY0MmU0OTlhNgpiMmY0NmM2OTI3Yjc1Mjk3NDdhYTQyZTY3Mjk2NGY0MzkzMzgwMjhlNjE2" - "ZDk2YWM4NDQwZTQ1MGRiYTM5NjJmCjNlODU0YjljOTNjOTg4YTZmNjVkNDhiYmViNTBkZTg5" - "NWZjOGNmM2NmY2I0MGY1MmU0YjQwMWFjYWZlMjU0M2EKMzc3MjU2YzgyNmZlODIxYTgxNDZm" - "ZDZkODhkZjg3Yzg1MjJkYTM4MWI4MmZiNTMwOGYxODAzMGZjZGNjMjAxYgpmYmM2NzRiZGQ5" - "YWMyNzYwZDliYzBlMTMwMDA2OTE3MTBmM2U5YmZlN2Y4OGYwM2JjMWFhNTAwZTY2ZmVhMDk5" - "CjlhYjVlOTFiZGVkNGMxZTBmMzBiNTdiOGM0MDY0MGViNjMyNTE2Zjc5YTczNzM0YTViM2M2" - "YjAxMGQ4MjYyYmUKM2U1MjMyMTE4MzUxY2U5M2VkNmY1NWJhYTFmNmU5M2NmMzVlZjJiNjRi" - "MDYxNzU4YWJmYzdkNzUzYzAxMWVhNgo3NTg1N2MwMGY3YTE3Y2E3YWI2NGJlMWIyYjdkNzZl" - "NWJlMThhZWFmZWY5NDU5MjAxY2RkY2NkZGZiZjczMjQ2"; -}; - -class DatasetJwk { - public: - // The following public key jwk and token are taken from - // https://github.com/cloudendpoints/esp/blob/master/src/api_manager/auth/lib/auth_jwt_validator_test.cc - const std::string kPublicKeyRSA = - "{\"keys\": [{\"kty\": \"RSA\",\"alg\": \"RS256\",\"use\": " - "\"sig\",\"kid\": \"62a93512c9ee4c7f8067b5a216dade2763d32a47\",\"n\": " - "\"0YWnm_eplO9BFtXszMRQNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm_" - "2xNgcaVpkW0VT2l4mU3KftR-6s3Oa5Rnz5BrWEUkCTVVolR7VYksfqIB2I_" - "x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeAh" - "0-" - "bLK5zvqCmKW5QgDIXSxUTJxPjZCgfx1vmAfGqaJb-" - "nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4ja6Slr8S4EB3F1luYhATa1P" - "KU" - "SH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX_aYahIw\",\"e\": \"AQAB\"},{\"kty\": " - "\"RSA\",\"alg\": \"RS256\",\"use\": \"sig\",\"kid\": " - "\"b3319a147514df7ee5e4bcdee51350cc890cc89e\",\"n\": " - "\"qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_" - "2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE-" - "RiKM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF-" - "BTillGJt5W5RuXti9uqfMtCQdagB8EC3MNRuU_KdeLgBy3lS3oo4LOYd-" - "74kRBVZbk2wnmmb7IhP9OoLc1-7-9qU1uhpDxmE6JwBau0mDSwMnYDS4G_ML17dC-" - "ZDtLd1i24STUw39KH0pcSdfFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI_pj8M-2Mn_" - "oA8jBuI8YKwBqYkZCN1I95Q\",\"e\": \"AQAB\"}]}"; - - // private key: - // "-----BEGIN PRIVATE KEY-----\n" - // "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoOLtPHgOE289C\n" - // "yXWh/HFzZ49AVyz4vSZdijpMZLrgJj/ZaY629iVws1mOG511lVXZfzybQx/BpIDX\n" - // "rAT5GIoz2GqjkRjwE9ePnsIyJgDKIe5A+nXJrKMyCgTU/aO+nh6oX4FOKWUYm3lb\n" - // "lG5e2L26p8y0JB1qAHwQLcw1G5T8p14uAHLeVLeijgs5h37viREFVluTbCeaZvsi\n" - // "E/06gtzX7v72pTW6GkPGYTonAFq7SYNLAydgNLgb8wvXt0L5kO0t3WLbhJNTDf0o\n" - // "fSlxJ18VsvY20Rl015qbUMN2TSJS0lI9mWJQckEj+mPwz7Yyf+gDyMG4jxgrAGpi\n" - // "RkI3Uj3lAgMBAAECggEAOuaaVyp4KvXYDVeC07QTeUgCdZHQkkuQemIi5YrDkCZ0\n" - // "Zsi6CsAG/f4eVk6/BGPEioItk2OeY+wYnOuDVkDMazjUpe7xH2ajLIt3DZ4W2q+k\n" - // "v6WyxmmnPqcZaAZjZiPxMh02pkqCNmqBxJolRxp23DtSxqR6lBoVVojinpnIwem6\n" - // "xyUl65u0mvlluMLCbKeGW/K9bGxT+qd3qWtYFLo5C3qQscXH4L0m96AjGgHUYW6M\n" - // "Ffs94ETNfHjqICbyvXOklabSVYenXVRL24TOKIHWkywhi1wW+Q6zHDADSdDVYw5l\n" - // "DaXz7nMzJ2X7cuRP9zrPpxByCYUZeJDqej0Pi7h7ZQKBgQDdI7Yb3xFXpbuPd1VS\n" - // "tNMltMKzEp5uQ7FXyDNI6C8+9TrjNMduTQ3REGqEcfdWA79FTJq95IM7RjXX9Aae\n" - // "p6cLekyH8MDH/SI744vCedkD2bjpA6MNQrzNkaubzGJgzNiZhjIAqnDAD3ljHI61\n" - // "NbADc32SQMejb6zlEh8hssSsXwKBgQDCvXhTIO/EuE/y5Kyb/4RGMtVaQ2cpPCoB\n" - // "GPASbEAHcsRk+4E7RtaoDQC1cBRy+zmiHUA9iI9XZyqD2xwwM89fzqMj5Yhgukvo\n" - // "XMxvMh8NrTneK9q3/M3mV1AVg71FJQ2oBr8KOXSEbnF25V6/ara2+EpH2C2GDMAo\n" - // "pgEnZ0/8OwKBgFB58IoQEdWdwLYjLW/d0oGEWN6mRfXGuMFDYDaGGLuGrxmEWZdw\n" - // "fzi4CquMdgBdeLwVdrLoeEGX+XxPmCEgzg/FQBiwqtec7VpyIqhxg2J9V2elJS9s\n" - // "PB1rh9I4/QxRP/oO9h9753BdsUU6XUzg7t8ypl4VKRH3UCpFAANZdW1tAoGAK4ad\n" - // "tjbOYHGxrOBflB5wOiByf1JBZH4GBWjFf9iiFwgXzVpJcC5NHBKL7gG3EFwGba2M\n" - // "BjTXlPmCDyaSDlQGLavJ2uQar0P0Y2MabmANgMkO/hFfOXBPtQQe6jAfxayaeMvJ\n" - // "N0fQOylUQvbRTodTf2HPeG9g/W0sJem0qFH3FrECgYEAnwixjpd1Zm/diJuP0+Lb\n" - // "YUzDP+Afy78IP3mXlbaQ/RVd7fJzMx6HOc8s4rQo1m0Y84Ztot0vwm9+S54mxVSo\n" - // "6tvh9q0D7VLDgf+2NpnrDW7eMB3n0SrLJ83Mjc5rZ+wv7m033EPaWSr/TFtc/MaF\n" - // "aOI20MEe3be96HHuWD3lTK0=\n" - // "-----END PRIVATE KEY-----"; - - // JWT payload JSON - const std::string kJwtPayload = - R"EOF({"iss":"https://example.com","sub":"test@example.com","exp":1501281058})EOF"; - - // JWT without kid - // Header: {"alg":"RS256","typ":"JWT"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwtNoKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0.XYPg6VPrq-H1Kl-kgmAfGFomVpnmdZLIAo0g6dhJb2Be_" - "koZ2T76xg5_Lr828hsLKxUfzwNxl5-k1cdz_kAst6vei0hdnOYqRQ8EhkZS_" - "5Y2vWMrzGHw7AUPKCQvSnNqJG5HV8YdeOfpsLhQTd-" - "tG61q39FWzJ5Ra5lkxWhcrVDQFtVy7KQrbm2dxhNEHAR2v6xXP21p1T5xFBdmGZbHFiH63N9" - "dwdRgWjkvPVTUqxrZil7PSM2zg_GTBETp_" - "qS7Wwf8C0V9o2KZu0KDV0j0c9nZPWTv3IMlaGZAtQgJUeyemzRDtf4g2yG3xBZrLm3AzDUj_" - "EX_pmQAHA5ZjPVCAw"; - - // JWT payload JSON with long exp - const std::string kJwtPayloadLongExp = - R"EOF({"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":2001001001})EOF"; - - // JWT without kid with long exp - // Header: {"alg":"RS256","typ":"JWT"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":2001001001} - const std::string kJwtNoKidLongExp = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0." - "n45uWZfIBZwCIPiL0K8Ca3tmm-ZlsDrC79_" - "vXCspPwk5oxdSn983tuC9GfVWKXWUMHe11DsB02b19Ow-" - "fmoEzooTFn65Ml7G34nW07amyM6lETiMhNzyiunctplOr6xKKJHmzTUhfTirvDeG-q9n24-" - "8lH7GP8GgHvDlgSM9OY7TGp81bRcnZBmxim_UzHoYO3_" - "c8OP4ZX3xG5PfihVk5G0g6wcHrO70w0_64JgkKRCrLHMJSrhIgp9NHel_" - "CNOnL0AjQKe9IGblJrMuouqYYS0zEWwmOVUWUSxQkoLpldQUVefcfjQeGjz8IlvktRa77FYe" - "xfP590ACPyXrivtsxg"; - // JWT with correct kid - // Header: - // {"alg":"RS256","typ":"JWT","kid":"b3319a147514df7ee5e4bcdee51350cc890cc89e"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwtWithCorrectKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImIzMzE5YTE0NzUxNGRmN2VlNWU0" - "YmNkZWU1MTM1MGNjODkwY2M4OWUifQ." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0.QYWtQR2JNhLBJXtpJfFisF0WSyzLbD-9dynqwZt_" - "KlQZAIoZpr65BRNEyRzpt0jYrk7RA7hUR2cS9kB3AIKuWA8kVZubrVhSv_fiX6phjf_" - "bZYj92kDtMiPJf7RCuGyMgKXwwf4b1Sr67zamcTmQXf26DT415rnrUHVqTlOIW50TjNa1bbO" - "fNyKZC3LFnKGEzkfaIeXYdGiSERVOTtOFF5cUtZA2OVyeAT3mE1NuBWxz0v7xJ4zdIwHwxFU" - "wd_5tB57j_" - "zCEC9NwnwTiZ8wcaSyMWc4GJUn4bJs22BTNlRt5ElWl6RuBohxZA7nXwWig5CoLZmCpYpb8L" - "fBxyCpqJQ"; - - // JWT with existing but incorrect kid - // Header: - // {"alg":"RS256","typ":"JWT","kid":"62a93512c9ee4c7f8067b5a216dade2763d32a47"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwtWithIncorrectKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjYyYTkzNTEyYzllZTRjN2Y4MDY3" - "YjVhMjE2ZGFkZTI3NjNkMzJhNDcifQ." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0." - "adrKqsjKh4zdOuw9rMZr0Kn2LLYG1OUfDuvnO6tk75NKCHpKX6oI8moNYhgcCQU4AoCKXZ_" - "u-oMl54QTx9lX9xZ2VUWKTxcJEOnpoJb-DVv_FgIG9ETe5wcCS8Y9pQ2-hxtO1_LWYok1-" - "A01Q4929u6WNw_Og4rFXR6VSpZxXHOQrEwW44D2-Lngu1PtPjWIz3rO6cOiYaTGCS6-" - "TVeLFnB32KQg823WhFhWzzHjhYRO7NOrl-IjfGn3zYD_" - "DfSoMY3A6LeOFCPp0JX1gcKcs2mxaF6e3LfVoBiOBZGvgG_" - "jx3y85hF2BZiANbSf1nlLQFdjk_CWbLPhTWeSfLXMOg"; - - // JWT with nonexist kid - // Header: {"alg":"RS256","typ":"JWT","kid":"blahblahblah"} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwtWithNonExistKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJsYWhibGFoYmxhaCJ9." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0.digk0Fr_IdcWgJNVyeVDw2dC1cQG6LsHwg5pIN93L4_" - "xhEDI3ZFoZ8aE44kvQHWLicnHDlhELqtF-" - "TqxrhfnitpLE7jiyknSu6NVXxtRBcZ3dOTKryVJDvDXcYXOaaP8infnh82loHfhikgg1xmk9" - "rcH50jtc3BkxWNbpNgPyaAAE2tEisIInaxeX0gqkwiNVrLGe1hfwdtdlWFL1WENGlyniQBvB" - "Mwi8DgG_F0eyFKTSRWoaNQQXQruEK0YIcwDj9tkYOXq8cLAnRK9zSYc5-" - "15Hlzfb8eE77pID0HZN-Axeui4IY22I_kYftd0OEqlwXJv_v5p6kNaHsQ9QbtAkw"; - - // JWT with bad-formatted kid - // Header: {"alg":"RS256","typ":"JWT","kid":1} - // Payload: - // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} - const std::string kJwtWithBadFormatKid = - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6MX0." - "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" - "ImV4cCI6MTUwMTI4MTA1OH0." - "oYq0UkokShprH2YO5b84CI5fEu0sKWmEJimyJQ9YZbvaGtf6zaLbdVJBTbh6plBno-" - "miUhjqXZtDdmBexQzp5HPHoIUwQxlGggCuJRdEnmw65Ul9WFWtS7M9g8DqVKaCo9MO-" - "apCsylPZsRSzzZuaTPorZktELt6XcUIxeXOKOSZJ78sHsRrDeLhlELd9Q0b6hzAdDEYCvYE6" - "woc3DiRHk19nsEgdg5O1RWKjTAcdd3oD9ecznzvVmAZT8gXrGXPd49tn1qHkVr1G621Ypi9V" - "37BD2KXH3jN9_EBocxwcxhkPwSLtP3dgkfls_f5GoWCgmp-c5ycIskCDcIjxRnPjg"; - - // JWT payload JSON with ES256 - const std::string kJwtPayloadEC = - R"EOF({"iss":"628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "sub":"628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "aud":"http://myservice.com/myapi"})EOF"; - - // Please see jwt_generator.py and jwk_generator.py under /tools/. - // for ES256-signed jwt token and public jwk generation, respectively. - // jwt_generator.py uses ES256 private key file to generate JWT token. - // ES256 private key file can be generated by: - // $ openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem - // jwk_generator.py uses ES256 public key file to generate JWK. ES256 - // public key file can be generated by: - // $ openssl ec -in private_key.pem -pubout -out public_key.pem. - - // ES256 private key: - // "-----BEGIN EC PRIVATE KEY-----" - // "MHcCAQEEIOyf96eKdFeSFYeHiM09vGAylz+/auaXKEr+fBZssFsJoAoGCCqGSM49" - // "AwEHoUQDQgAEEB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5n3ZsIFO8wV" - // "DyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw==" - // "-----END EC PRIVATE KEY-----" - - // ES256 public key: - // "-----BEGIN PUBLIC KEY-----" - // "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEB54wykhS7YJFD6RYJNnwbWEz3cI" - // "7CF5bCDTXlrwI5n3ZsIFO8wVDyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw==" - // "-----END PUBLIC KEY-----" - - const std::string kPublicKeyJwkEC = - "{\"keys\": [" - "{" - "\"kty\": \"EC\"," - "\"crv\": \"P-256\"," - "\"x\": \"EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k\"," - "\"y\": \"92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8\"," - "\"alg\": \"ES256\"," - "\"kid\": \"abc\"" - "}," - "{" - "\"kty\": \"EC\"," - "\"crv\": \"P-256\"," - "\"x\": \"EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k\"," - "\"y\": \"92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8\"," - "\"alg\": \"ES256\"," - "\"kid\": \"xyz\"" - "}" - "]}"; - - // "{"kid":"abc"}" - const std::string kTokenEC = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mj" - "g2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvc" - "GVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1" - "MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3V" - "udC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.T2KAwChqg" - "o2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9J" - "Gp9lq3cbEw"; - - // "{"kid":"abcdef"}" - const std::string kJwtWithNonExistKidEC = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZiJ9.eyJpc3MiOi" - "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" - "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" - "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" - "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" - "9teWFwaSJ9.rWSoOV5j7HxHc4yVgZEZYUSgY7AUarG3HxdfPON1mw6II_pNUsc8" - "_sVf7Yv2-jeVhmf8BtR99wnOwEDhVYrVpQ"; - - const std::string kTokenECNoKid = - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm" - "9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2a" - "WNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4" - "bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5" - "jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.zlFcET8Fi" - "OYcKe30A7qOD4TIBvtb9zIVhDcM8pievKs1Te-UOBcklQxhwXMnRSSEBY4P0pfZ" - "qWJT_V5IVrKrdQ"; -}; - -namespace { - -bool EqJson(Json::ObjectSharedPtr p1, Json::ObjectSharedPtr p2) { - return p1->asJsonString() == p2->asJsonString(); -} -} // namespace - -class JwtTest : public testing::Test { - protected: - void DoTest(std::string jwt_str, std::string pkey, std::string pkey_type, - bool verified, Status status, Json::ObjectSharedPtr payload) { - Jwt jwt(jwt_str); - Verifier v; - std::unique_ptr key; - if (pkey_type == "pem") { - key = Pubkeys::CreateFrom(pkey, Pubkeys::Type::PEM); - } else if (pkey_type == "jwks") { - key = Pubkeys::CreateFrom(pkey, Pubkeys::Type::JWKS); - } else { - ASSERT_TRUE(0); - } - EXPECT_EQ(verified, v.Verify(jwt, *key)); - EXPECT_EQ(status, v.GetStatus()); - if (verified) { - ASSERT_TRUE(jwt.Payload()); - EXPECT_TRUE(EqJson(payload, jwt.Payload())); - } - } -}; - -// Test cases w/ PEM-formatted public key - -class JwtTestPem : public JwtTest { - protected: - DatasetPem ds; -}; - -TEST_F(JwtTestPem, OK) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, payload); -} - -TEST_F(JwtTestPem, MultiAudiences) { - Jwt jwt(ds.kJwtMultiSub); - ASSERT_EQ(jwt.Aud(), std::vector({"aud1", "aud2"})); -} - -TEST_F(JwtTestPem, InvalidSignature) { - auto invalid_jwt = ds.kJwt; - invalid_jwt[ds.kJwt.length() - 2] = - ds.kJwt[ds.kJwt.length() - 2] != 'a' ? 'a' : 'b'; - DoTest(invalid_jwt, ds.kPublicKey, "pem", false, - Status::JWT_INVALID_SIGNATURE, nullptr); -} - -TEST_F(JwtTestPem, InvalidPublicKey) { - auto invalid_pubkey = ds.kPublicKey; - invalid_pubkey[0] = ds.kPublicKey[0] != 'a' ? 'a' : 'b'; - DoTest(ds.kJwt, invalid_pubkey, "pem", false, Status::PEM_PUBKEY_PARSE_ERROR, - nullptr); -} - -TEST_F(JwtTestPem, PublicKeyInvalidBase64) { - auto invalid_pubkey = "a"; - DoTest(ds.kJwt, invalid_pubkey, "pem", false, Status::PEM_PUBKEY_BAD_BASE64, - nullptr); -} - -TEST_F(JwtTestPem, Base64urlBadInputHeader) { - auto invalid_header = ds.kJwtHeaderEncoded + "a"; - auto invalid_jwt = StringUtil::join( - std::vector{invalid_header, ds.kJwtPayloadEncoded, - ds.kJwtSignatureEncoded}, - "."); - DoTest(invalid_jwt, ds.kPublicKey, "pem", false, - Status::JWT_HEADER_PARSE_ERROR, nullptr); -} - -TEST_F(JwtTestPem, Base64urlBadInputPayload) { - auto invalid_payload = ds.kJwtPayloadEncoded + "a"; - auto invalid_jwt = StringUtil::join( - std::vector{ds.kJwtHeaderEncoded, invalid_payload, - ds.kJwtSignatureEncoded}, - "."); - DoTest(invalid_jwt, ds.kPublicKey, "pem", false, - Status::JWT_PAYLOAD_PARSE_ERROR, nullptr); -} - -TEST_F(JwtTestPem, Base64urlBadinputSignature) { - auto invalid_signature = "a"; - auto invalid_jwt = StringUtil::join( - std::vector{ds.kJwtHeaderEncoded, ds.kJwtPayloadEncoded, - invalid_signature}, - "."); - DoTest(invalid_jwt, ds.kPublicKey, "pem", false, - Status::JWT_SIGNATURE_PARSE_ERROR, nullptr); -} - -TEST_F(JwtTestPem, JwtInvalidNumberOfDots) { - auto invalid_jwt = ds.kJwt + '.'; - DoTest(invalid_jwt, ds.kPublicKey, "pem", false, Status::JWT_BAD_FORMAT, - nullptr); -} - -TEST_F(JwtTestPem, JsonBadInputHeader) { - DoTest(ds.kJwtWithBadJsonHeader, ds.kPublicKey, "pem", false, - Status::JWT_HEADER_PARSE_ERROR, nullptr); -} - -TEST_F(JwtTestPem, JsonBadInputPayload) { - DoTest(ds.kJwtWithBadJsonPayload, ds.kPublicKey, "pem", false, - Status::JWT_PAYLOAD_PARSE_ERROR, nullptr); -} - -TEST_F(JwtTestPem, AlgAbsentInHeader) { - DoTest(ds.kJwtWithAlgAbsent, ds.kPublicKey, "pem", false, - Status::JWT_HEADER_NO_ALG, nullptr); -} - -TEST_F(JwtTestPem, AlgIsNotString) { - DoTest(ds.kJwtWithAlgIsNotString, ds.kPublicKey, "pem", false, - Status::JWT_HEADER_BAD_ALG, nullptr); -} - -TEST_F(JwtTestPem, InvalidAlg) { - DoTest(ds.kJwtWithInvalidAlg, ds.kPublicKey, "pem", false, - Status::ALG_NOT_IMPLEMENTED, nullptr); -} - -TEST(JwtSubExtractionTest, NonEmptyJwtSubShouldEqual) { - DatasetPem ds; - Jwt jwt(ds.kJwt); - EXPECT_EQ(jwt.Sub(), ds.kJwtSub); -} - -TEST(JwtSubExtractionTest, EmptyJwtSubShouldEqual) { - Jwt jwt(""); - EXPECT_EQ(jwt.Sub(), ""); -} - -// Test cases w/ JWKs-formatted public key - -class JwtTestJwks : public JwtTest { - protected: - DatasetJwk ds; -}; - -TEST_F(JwtTestJwks, OkNoKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, payload); -} - -TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - // Remove "alg" claim from public key. - std::string alg_claim = "\"alg\": \"RS256\","; - std::string pubkey_no_alg = ds.kPublicKeyRSA; - std::size_t alg_pos = pubkey_no_alg.find(alg_claim); - while (alg_pos != std::string::npos) { - pubkey_no_alg.erase(alg_pos, alg_claim.length()); - alg_pos = pubkey_no_alg.find(alg_claim); - } - DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, payload); - - // Remove "kid" claim from public key. - std::string kid_claim1 = - ",\"kid\": \"62a93512c9ee4c7f8067b5a216dade2763d32a47\""; - std::string kid_claim2 = - ",\"kid\": \"b3319a147514df7ee5e4bcdee51350cc890cc89e\""; - std::string pubkey_no_kid = ds.kPublicKeyRSA; - std::size_t kid_pos = pubkey_no_kid.find(kid_claim1); - pubkey_no_kid.erase(kid_pos, kid_claim1.length()); - kid_pos = pubkey_no_kid.find(kid_claim2); - pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, payload); -} - -TEST_F(JwtTestJwks, OkNoKidLogExp) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadLongExp); - DoTest(ds.kJwtNoKidLongExp, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); -} - -TEST_F(JwtTestJwks, OkCorrectKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtWithCorrectKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); -} - -TEST_F(JwtTestJwks, IncorrectKid) { - DoTest(ds.kJwtWithIncorrectKid, ds.kPublicKeyRSA, "jwks", false, - Status::JWT_INVALID_SIGNATURE, nullptr); -} - -TEST_F(JwtTestJwks, NonExistKid) { - DoTest(ds.kJwtWithNonExistKid, ds.kPublicKeyRSA, "jwks", false, - Status::KID_ALG_UNMATCH, nullptr); -} - -TEST_F(JwtTestJwks, BadFormatKid) { - DoTest(ds.kJwtWithBadFormatKid, ds.kPublicKeyRSA, "jwks", false, - Status::JWT_HEADER_BAD_KID, nullptr); -} - -TEST_F(JwtTestJwks, JwkBadJson) { - std::string invalid_pubkey = "foobar"; - DoTest(ds.kJwtNoKid, invalid_pubkey, "jwks", false, Status::JWK_PARSE_ERROR, - nullptr); -} - -TEST_F(JwtTestJwks, JwkNoKeys) { - std::string invalid_pubkey = R"EOF({"foo":"bar"})EOF"; - DoTest(ds.kJwtNoKid, invalid_pubkey, "jwks", false, Status::JWK_NO_KEYS, - nullptr); -} - -TEST_F(JwtTestJwks, JwkBadKeys) { - std::string invalid_pubkey = R"EOF({"keys":"foobar"})EOF"; - DoTest(ds.kJwtNoKid, invalid_pubkey, "jwks", false, Status::JWK_BAD_KEYS, - nullptr); -} - -TEST_F(JwtTestJwks, JwkBadPublicKey) { - std::string invalid_pubkey = R"EOF({"keys":[]})EOF"; - DoTest(ds.kJwtNoKid, invalid_pubkey, "jwks", false, - Status::JWK_NO_VALID_PUBKEY, nullptr); -} - -TEST_F(JwtTestJwks, OkTokenJwkEC) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); - // ES256-signed token with kid specified. - DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, payload); - // ES256-signed token without kid specified. - DoTest(ds.kTokenECNoKid, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - payload); -} - -TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); - // Remove "alg" claim from public key. - std::string alg_claim = "\"alg\": \"ES256\","; - std::string pubkey_no_alg = ds.kPublicKeyJwkEC; - std::size_t alg_pos = pubkey_no_alg.find(alg_claim); - while (alg_pos != std::string::npos) { - pubkey_no_alg.erase(alg_pos, alg_claim.length()); - alg_pos = pubkey_no_alg.find(alg_claim); - } - DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, payload); - - // Remove "kid" claim from public key. - std::string kid_claim1 = ",\"kid\": \"abc\""; - std::string kid_claim2 = ",\"kid\": \"xyz\""; - std::string pubkey_no_kid = ds.kPublicKeyJwkEC; - std::size_t kid_pos = pubkey_no_kid.find(kid_claim1); - pubkey_no_kid.erase(kid_pos, kid_claim1.length()); - kid_pos = pubkey_no_kid.find(kid_claim2); - pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, payload); -} - -TEST_F(JwtTestJwks, NonExistKidEC) { - DoTest(ds.kJwtWithNonExistKidEC, ds.kPublicKeyJwkEC, "jwks", false, - Status::KID_ALG_UNMATCH, nullptr); -} - -TEST_F(JwtTestJwks, InvalidPublicKeyEC) { - auto invalid_pubkey = ds.kPublicKeyJwkEC; - invalid_pubkey.replace(12, 9, "kty\":\"RSA"); - DoTest(ds.kTokenEC, invalid_pubkey, "jwks", false, Status::KID_ALG_UNMATCH, - nullptr); -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/pubkey_cache.h b/src/envoy/http/jwt_auth/pubkey_cache.h deleted file mode 100644 index 830f4d2dc3b..00000000000 --- a/src/envoy/http/jwt_auth/pubkey_cache.h +++ /dev/null @@ -1,185 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "common/common/logger.h" -#include "common/config/datasource.h" -#include "envoy/config/filter/http/jwt_authn/v2alpha/config.pb.h" -#include "src/envoy/http/jwt_auth/jwt.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { -namespace { -// Default cache expiration time in 5 minutes. -const int kPubkeyCacheExpirationSec = 600; - -// HTTP Protocol scheme prefix in JWT aud claim. -const std::string kHTTPSchemePrefix("http://"); - -// HTTPS Protocol scheme prefix in JWT aud claim. -const std::string kHTTPSSchemePrefix("https://"); -} // namespace - -// Struct to hold an issuer cache item. -class PubkeyCacheItem : public Logger::Loggable { - public: - PubkeyCacheItem( - const ::envoy::config::filter::http::jwt_authn::v2alpha::JwtRule& - jwt_config) - : jwt_config_(jwt_config) { - // Convert proto repeated fields to std::set. - for (const auto& aud : jwt_config_.audiences()) { - audiences_.insert(SanitizeAudience(aud)); - } - - auto inline_jwks = Config::DataSource::read(jwt_config_.local_jwks(), true); - if (!inline_jwks.empty()) { - Status status = SetKey(inline_jwks, - // inline jwks never expires. - std::chrono::steady_clock::time_point::max()); - if (status != Status::OK) { - ENVOY_LOG(warn, "Invalid inline jwks for issuer: {}, jwks: {}", - jwt_config_.issuer(), inline_jwks); - } - } - } - - // Return true if cached pubkey is expired. - bool Expired() const { - return std::chrono::steady_clock::now() >= expiration_time_; - } - - // Get the JWT config. - const ::envoy::config::filter::http::jwt_authn::v2alpha::JwtRule& jwt_config() - const { - return jwt_config_; - } - - // Get the pubkey object. - const Pubkeys* pubkey() const { return pubkey_.get(); } - - // Check if an audience is allowed. - bool IsAudienceAllowed(const std::vector& jwt_audiences) { - if (audiences_.empty()) { - return true; - } - for (const auto& aud : jwt_audiences) { - if (audiences_.find(SanitizeAudience(aud)) != audiences_.end()) { - return true; - } - } - return false; - } - - Status SetRemoteJwks(const std::string& pubkey_str) { - return SetKey(pubkey_str, GetRemoteJwksExpirationTime()); - } - - private: - // Get the expiration time for remote JWKS - std::chrono::steady_clock::time_point GetRemoteJwksExpirationTime() const { - auto expire = std::chrono::steady_clock::now(); - if (jwt_config_.has_remote_jwks() && - jwt_config_.remote_jwks().has_cache_duration()) { - const auto& duration = jwt_config_.remote_jwks().cache_duration(); - expire += std::chrono::seconds(duration.seconds()) + - std::chrono::nanoseconds(duration.nanos()); - } else { - expire += std::chrono::seconds(kPubkeyCacheExpirationSec); - } - return expire; - } - - // Set a pubkey as string. - Status SetKey(const std::string& pubkey_str, - std::chrono::steady_clock::time_point expire) { - auto pubkey = Pubkeys::CreateFrom(pubkey_str, Pubkeys::JWKS); - if (pubkey->GetStatus() != Status::OK) { - return pubkey->GetStatus(); - } - pubkey_ = std::move(pubkey); - expiration_time_ = expire; - return Status::OK; - } - - // Searches protocol scheme prefix and trailing slash from aud, and - // returns aud without these prefix and suffix. - std::string SanitizeAudience(const std::string& aud) { - int beg = 0; - int end = aud.length() - 1; - bool sanitize_aud = false; - // Point beg to first character after protocol scheme prefix in audience. - if (aud.compare(0, kHTTPSchemePrefix.length(), kHTTPSchemePrefix) == 0) { - beg = kHTTPSchemePrefix.length(); - sanitize_aud = true; - } else if (aud.compare(0, kHTTPSSchemePrefix.length(), - kHTTPSSchemePrefix) == 0) { - beg = kHTTPSSchemePrefix.length(); - sanitize_aud = true; - } - // Point end to trailing slash in aud. - if (end >= 0 && aud[end] == '/') { - --end; - sanitize_aud = true; - } - if (sanitize_aud) { - return aud.substr(beg, end - beg + 1); - } - return aud; - } - - // The issuer config - const ::envoy::config::filter::http::jwt_authn::v2alpha::JwtRule& jwt_config_; - // Use set for fast lookup - std::set audiences_; - // The generated pubkey object. - std::unique_ptr pubkey_; - // The pubkey expiration time. - std::chrono::steady_clock::time_point expiration_time_; -}; - -// Pubkey cache -class PubkeyCache { - public: - // Load the config from envoy config. - PubkeyCache(const ::envoy::config::filter::http::jwt_authn::v2alpha:: - JwtAuthentication& config) { - for (const auto& jwt : config.rules()) { - pubkey_cache_map_.emplace(jwt.issuer(), jwt); - } - } - - // Lookup issuer cache map. - PubkeyCacheItem* LookupByIssuer(const std::string& name) { - auto it = pubkey_cache_map_.find(name); - if (it == pubkey_cache_map_.end()) { - return nullptr; - } - return &it->second; - } - - private: - // The public key cache map indexed by issuer. - std::unordered_map pubkey_cache_map_; -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/sample/correct_jwt b/src/envoy/http/jwt_auth/sample/correct_jwt deleted file mode 100644 index 74b058e597f..00000000000 --- a/src/envoy/http/jwt_auth/sample/correct_jwt +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImIzMzE5YTE0NzUxNGRmN2VlNWU0YmNkZWU1MTM1MGNjODkwY2M4OWUifQ==.eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJib29rc3RvcmUtZXNwLWVjaG8uY2xvdWRlbmRwb2ludHNhcGlzLmNvbSIsImlhdCI6MTUxMjc1NDIwNSwiZXhwIjo1MTEyNzU0MjA1fQ==.HKWpc8zLw7NAzlgPphHpQ6fWh7k1cJ0XM7B_9YqcOQYLe8UA9KvOC_4D6cNw7HCaEv8UQufA4d8ErDn5PI3mPxn6m8pciJbcqblXmNN8jCJUSH2OHZsWDdzipHPrt5kxz9onx39m9Zdb_xXAffHREVDXO6eMzNte8ZihZwmZauIT9fbL8BbD74_D5tQvswdjUNAQuTdK6-pBXOH1Qf7fE3V92ESVqUmqM05FkTBfDZw6CGKj47W8ecs0QiLyERth8opCTLsRi5QN1xEPggTpfH_YBZTtsuIybVjiw9UAizWE-ziFWx2qlt9JPEArjvroMfNmJz4gTenbKNuXBMJOQg== diff --git a/src/envoy/http/jwt_auth/sample/envoy.conf b/src/envoy/http/jwt_auth/sample/envoy.conf deleted file mode 100644 index 2b6bf28381b..00000000000 --- a/src/envoy/http/jwt_auth/sample/envoy.conf +++ /dev/null @@ -1,97 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://0.0.0.0:9090", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "service1" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/stdout" - } - ], - "filters": [ - { - "type": "decoder", - "name": "jwt-auth", - "config": { - "rules": [ - { - "issuer": "628645741881-noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com", - "remote_jwks": { - "http_uri":{ - "uri": "http://localhost:8081/", - "cluster": "example_issuer" - } - } - } - ] - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/stdout", - "address": "tcp://0.0.0.0:9001" - }, - "cluster_manager": { - "clusters": [ - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://0.0.0.0:8080" - } - ] - }, - { - "name": "example_issuer", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8081" - } - ] - } - ] - } -} diff --git a/src/envoy/http/jwt_auth/sample/fake_issuer.go b/src/envoy/http/jwt_auth/sample/fake_issuer.go deleted file mode 100644 index afa2dc3c4de..00000000000 --- a/src/envoy/http/jwt_auth/sample/fake_issuer.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2016 Istio Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// An example implementation of Echo backend in go. - -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "net/http" - "strconv" - "os" -) - -var ( - port = flag.Int("port", 8081, "default http port") - pubkey = "" -) - -func handler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%v", pubkey) -} - -func main() { - b, err := ioutil.ReadFile(os.Args[1]) // just pass the file name - if err != nil { - fmt.Print(err) - } - pubkey = string(b) // convert content to a 'string' - - flag.Parse() - fmt.Printf("Listening on port %v\n", *port) - - http.HandleFunc("/", handler) - http.ListenAndServe(":"+strconv.Itoa(*port), nil) -} diff --git a/src/envoy/http/jwt_auth/sample/invalid_jwt b/src/envoy/http/jwt_auth/sample/invalid_jwt deleted file mode 100644 index 418e12b41b8..00000000000 --- a/src/envoy/http/jwt_auth/sample/invalid_jwt +++ /dev/null @@ -1 +0,0 @@ -invalidToken \ No newline at end of file diff --git a/src/envoy/http/jwt_auth/sample/pubkey.jwk b/src/envoy/http/jwt_auth/sample/pubkey.jwk deleted file mode 100644 index 987d52920dc..00000000000 --- a/src/envoy/http/jwt_auth/sample/pubkey.jwk +++ /dev/null @@ -1,20 +0,0 @@ -{ - "keys": [ - { - "alg": "RS256", - "e": "AQAB", - "kid": "62a93512c9ee4c7f8067b5a216dade2763d32a47", - "kty": "RSA", - "n": "0YWnm_eplO9BFtXszMRQNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm_2xNgcaVpkW0VT2l4mU3KftR-6s3Oa5Rnz5BrWEUkCTVVolR7VYksfqIB2I_x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeAh0-bLK5zvqCmKW5QgDIXSxUTJxPjZCgfx1vmAfGqaJb-nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4ja6Slr8S4EB3F1luYhATa1PKUSH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX_aYahIw", - "use": "sig" - }, - { - "alg": "RS256", - "e": "AQAB", - "kid": "b3319a147514df7ee5e4bcdee51350cc890cc89e", - "kty": "RSA", - "n": "qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE-RiKM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF-BTillGJt5W5RuXti9uqfMtCQdagB8EC3MNRuU_KdeLgBy3lS3oo4LOYd-74kRBVZbk2wnmmb7IhP9OoLc1-7-9qU1uhpDxmE6JwBau0mDSwMnYDS4G_ML17dC-ZDtLd1i24STUw39KH0pcSdfFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI_pj8M-2Mn_oA8jBuI8YKwBqYkZCN1I95Q", - "use": "sig" - } - ] -} diff --git a/src/envoy/http/jwt_auth/token_extractor.cc b/src/envoy/http/jwt_auth/token_extractor.cc deleted file mode 100644 index 1d62bd10dc7..00000000000 --- a/src/envoy/http/jwt_auth/token_extractor.cc +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/jwt_auth/token_extractor.h" -#include "common/common/utility.h" -#include "common/http/utility.h" - -using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; - -namespace Envoy { -namespace Http { -namespace JwtAuth { -namespace { - -// The autorization bearer prefix. -const std::string kBearerPrefix = "Bearer "; - -// The query parameter name to get JWT token. -const std::string kParamAccessToken = "access_token"; - -} // namespace - -JwtTokenExtractor::JwtTokenExtractor(const JwtAuthentication& config) { - for (const auto& jwt : config.rules()) { - bool use_default = true; - if (jwt.from_headers_size() > 0) { - use_default = false; - for (const auto& header : jwt.from_headers()) { - auto& issuers = header_maps_[LowerCaseString(header.name())]; - issuers.insert(jwt.issuer()); - } - } - if (jwt.from_params_size() > 0) { - use_default = false; - for (const std::string& param : jwt.from_params()) { - auto& issuers = param_maps_[param]; - issuers.insert(jwt.issuer()); - } - } - - // If not specified, use default - if (use_default) { - authorization_issuers_.insert(jwt.issuer()); - - auto& param_issuers = param_maps_[kParamAccessToken]; - param_issuers.insert(jwt.issuer()); - } - } -} - -void JwtTokenExtractor::Extract( - const HeaderMap& headers, - std::vector>* tokens) const { - if (!authorization_issuers_.empty()) { - const HeaderEntry* entry = headers.Authorization(); - if (entry) { - // Extract token from header. - const HeaderString& value = entry->value(); - if (StringUtil::startsWith(value.c_str(), kBearerPrefix, true)) { - tokens->emplace_back(new Token(value.c_str() + kBearerPrefix.length(), - authorization_issuers_, true, nullptr)); - // Only take the first one. - return; - } - } - } - - // Check header first - for (const auto& header_it : header_maps_) { - const HeaderEntry* entry = headers.get(header_it.first); - if (entry) { - tokens->emplace_back( - new Token(std::string(entry->value().c_str(), entry->value().size()), - header_it.second, false, &header_it.first)); - // Only take the first one. - return; - } - } - - if (param_maps_.empty() || headers.Path() == nullptr) { - return; - } - - const auto& params = Utility::parseQueryString(std::string( - headers.Path()->value().c_str(), headers.Path()->value().size())); - for (const auto& param_it : param_maps_) { - const auto& it = params.find(param_it.first); - if (it != params.end()) { - tokens->emplace_back( - new Token(it->second, param_it.second, false, nullptr)); - // Only take the first one. - return; - } - } -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/token_extractor.h b/src/envoy/http/jwt_auth/token_extractor.h deleted file mode 100644 index 4efd808afa4..00000000000 --- a/src/envoy/http/jwt_auth/token_extractor.h +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/config/filter/http/jwt_authn/v2alpha/config.pb.h" -#include "envoy/http/header_map.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -// Extracts JWT token from locations specified in the config. -// -// The rules of token extraction: -// * Each issuer can specify its token locations either at headers or -// query parameters. -// * If an issuer doesn't specify any location, following default locations -// are used: -// header: Authorization: Bear -// query parameter: ?access_token= -// * A token must be extracted from the location specified by its issuer. -// -class JwtTokenExtractor : public Logger::Loggable { - public: - JwtTokenExtractor(const ::envoy::config::filter::http::jwt_authn::v2alpha:: - JwtAuthentication& config); - - // The object to store extracted token. - // Based on the location the token is extracted from, it also - // has the allowed issuers that have specified the location. - class Token { - public: - Token(const std::string& token, const std::set& issuers, - bool from_authorization, const LowerCaseString* header_name) - : token_(token), - allowed_issuers_(issuers), - from_authorization_(from_authorization), - header_name_(header_name) {} - - const std::string& token() const { return token_; } - - bool IsIssuerAllowed(const std::string& issuer) const { - return allowed_issuers_.find(issuer) != allowed_issuers_.end(); - } - - // TODO: to remove token from query parameter. - void Remove(HeaderMap* headers) { - if (from_authorization_) { - headers->removeAuthorization(); - } else if (header_name_ != nullptr) { - headers->remove(*header_name_); - } - } - - private: - // Extracted token. - std::string token_; - // Allowed issuers specified the location the token is extacted from. - const std::set& allowed_issuers_; - // True if token is extracted from default Authorization header - bool from_authorization_; - // Not nullptr if token is extracted from custom header. - const LowerCaseString* header_name_; - }; - - // Return the extracted JWT tokens. - // Only extract one token for now. - void Extract(const HeaderMap& headers, - std::vector>* tokens) const; - - private: - struct LowerCaseStringCmp { - bool operator()(const LowerCaseString& lhs, - const LowerCaseString& rhs) const { - return lhs.get() < rhs.get(); - } - }; - // The map of header to set of issuers - std::map, LowerCaseStringCmp> - header_maps_; - // The map of parameters to set of issuers. - std::map> param_maps_; - // Special handling of Authorization header. - std::set authorization_issuers_; -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/token_extractor_test.cc b/src/envoy/http/jwt_auth/token_extractor_test.cc deleted file mode 100644 index d13d98dc00d..00000000000 --- a/src/envoy/http/jwt_auth/token_extractor_test.cc +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/jwt_auth/token_extractor.h" -#include "gtest/gtest.h" -#include "test/test_common/utility.h" - -using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; -using ::testing::Invoke; -using ::testing::NiceMock; -using ::testing::_; - -namespace Envoy { -namespace Http { -namespace JwtAuth { -namespace { - -const char kExampleConfig[] = R"( -{ - "rules": [ - { - "issuer": "issuer1" - }, - { - "issuer": "issuer2", - "from_headers": [ - { - "name": "token-header" - } - ] - }, - { - "issuer": "issuer3", - "from_params": [ - "token_param" - ] - }, - { - "issuer": "issuer4", - "from_headers": [ - { - "name": "token-header" - } - ], - "from_params": [ - "token_param" - ] - } - ] -} -)"; - -} // namespace - -class JwtTokenExtractorTest : public ::testing::Test { - public: - void SetUp() { SetupConfig(kExampleConfig); } - - void SetupConfig(const std::string& json_str) { - google::protobuf::util::Status status = - ::google::protobuf::util::JsonStringToMessage(json_str, &config_); - ASSERT_TRUE(status.ok()); - extractor_.reset(new JwtTokenExtractor(config_)); - } - - JwtAuthentication config_; - std::unique_ptr extractor_; -}; - -TEST_F(JwtTokenExtractorTest, TestNoToken) { - auto headers = TestHeaderMapImpl{}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 0); -} - -TEST_F(JwtTokenExtractorTest, TestWrongHeaderToken) { - auto headers = TestHeaderMapImpl{{"wrong-token-header", "jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 0); -} - -TEST_F(JwtTokenExtractorTest, TestWrongParamToken) { - auto headers = TestHeaderMapImpl{{":path", "/path?wrong_token=jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 0); -} - -TEST_F(JwtTokenExtractorTest, TestDefaultHeaderLocation) { - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 1); - EXPECT_EQ(tokens[0]->token(), "jwt_token"); - - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer1")); - - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer2")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer3")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer4")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("unknown_issuer")); - - // Test token remove - tokens[0]->Remove(&headers); - EXPECT_FALSE(headers.Authorization()); -} - -TEST_F(JwtTokenExtractorTest, TestDefaultParamLocation) { - auto headers = TestHeaderMapImpl{{":path", "/path?access_token=jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 1); - EXPECT_EQ(tokens[0]->token(), "jwt_token"); - - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer1")); - - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer2")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer3")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer4")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("unknown_issuer")); -} - -TEST_F(JwtTokenExtractorTest, TestCustomHeaderToken) { - auto headers = TestHeaderMapImpl{{"token-header", "jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 1); - - EXPECT_EQ(tokens[0]->token(), "jwt_token"); - - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer1")); - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer2")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer3")); - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer4")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("unknown_issuer")); - - // Test token remove - tokens[0]->Remove(&headers); - EXPECT_FALSE(headers.get(LowerCaseString("token-header"))); -} - -TEST_F(JwtTokenExtractorTest, TestCustomParamToken) { - auto headers = TestHeaderMapImpl{{":path", "/path?token_param=jwt_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 1); - - EXPECT_EQ(tokens[0]->token(), "jwt_token"); - - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer1")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("issuer2")); - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer3")); - EXPECT_TRUE(tokens[0]->IsIssuerAllowed("issuer4")); - EXPECT_FALSE(tokens[0]->IsIssuerAllowed("unknown_issuer")); -} - -TEST_F(JwtTokenExtractorTest, TestMultipleTokens) { - auto headers = TestHeaderMapImpl{{":path", "/path?token_param=param_token"}, - {"token-header", "header_token"}}; - std::vector> tokens; - extractor_->Extract(headers, &tokens); - EXPECT_EQ(tokens.size(), 1); - - // Header token first. - EXPECT_EQ(tokens[0]->token(), "header_token"); -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/tools/jwk_generator.py b/src/envoy/http/jwt_auth/tools/jwk_generator.py deleted file mode 100755 index 27bb83dce31..00000000000 --- a/src/envoy/http/jwt_auth/tools/jwk_generator.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import sys -import chilkat # Chilkat v9.5.0.66 or later. - -""" This script is used to generate ES256/RS256 public jwk key.""" - -"""commands to generate public_key_file (Note that private key needs to be generated first): - ES256: $ openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem - $ openssl ec -in private_key.pem -pubout -out public_key.pem - RS256: $ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 - $ openssl rsa -pubout -in private_key.pem -out public_key.pem -""" - -def main(args): - # Load public key file into memory. - sbPem = chilkat.CkStringBuilder() - success = sbPem.LoadFile(args.public_key_file, "utf-8") - if (success != True): - print("Failed to load public key.") - sys.exit() - - # Load the key file into a public key object. - pubKey = chilkat.CkPublicKey() - success = pubKey.LoadFromString(sbPem.getAsString()) - if (success != True): - print(pubKey.lastErrorText()) - sys.exit() - - # Get the public key in JWK format: - jwk = pubKey.getJwk() - # Convert it to json format. - json = chilkat.CkJsonObject() - json.Load(jwk) - # This line is used to set output format. - cpt = True - if (not args.compact) or (args.compact and args.compact == "no"): - cpt = False - json.put_EmitCompact(cpt) - # Additional information can be added like this. change to fit needs. - if args.alg: - json.AppendString("alg", args.alg) - if args.kid: - json.AppendString("kid", args.kid) - # Print. - print("Generated " + args.alg + " public jwk:") - print(json.emit()) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - - # positional arguments - parser.add_argument( - "alg", - help="Signing algorithm, e.g., ES256/RS256.") - parser.add_argument( - "public_key_file", - help="The path to the generated ES256/RS256 public key file, e.g., /path/to/public_key.pem.") - - #optional arguments - parser.add_argument("-c", "--compact", help="If making json output compact, say 'yes' or 'no'.") - parser.add_argument("-k", "--kid", help="Key id, same as the kid in private key if any.") - main(parser.parse_args()) \ No newline at end of file diff --git a/src/envoy/http/jwt_auth/tools/jwt_generator.py b/src/envoy/http/jwt_auth/tools/jwt_generator.py deleted file mode 100755 index 6d72d9e4261..00000000000 --- a/src/envoy/http/jwt_auth/tools/jwt_generator.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2018 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import sys -import jwt # pip install PyJWT and pip install cryptography. - -""" This script is used to generate ES256/RS256-signed jwt token.""" - -"""commands to generate private_key_file: - ES256: $ openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem - RS256: $ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 -""" - -def main(args): - # JWT token generation. - with open(args.private_key_file, 'r') as f: - try: - secret = f.read() - except: - print("Failed to load private key.") - sys.exit() - - # Token headers - hdrs = {'alg': args.alg, - 'typ': 'JWT'} - if args.kid: - hdrs['kid'] = args.kid - - # Token claims - claims = {'iss': args.iss, - 'sub': args.sub, - 'aud': args.aud} - if args.email: - claims['email'] = args.email - if args.azp: - claims['azp'] = args.azp - if args.exp: - claims['exp'] = args.exp - - # Change claim and headers field to fit needs. - jwt_token = jwt.encode(claims, - secret, - algorithm=args.alg, - headers=hdrs) - - print(args.alg + "-signed jwt:") - print(jwt_token) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - - # positional arguments - parser.add_argument( - "alg", - help="Signing algorithm, i.e., ES256/RS256.") - parser.add_argument( - "iss", - help="Token issuer, which is also used for sub claim.") - parser.add_argument( - "aud", - help="Audience. This must match 'audience' in the security configuration" - " in the swagger spec.") - parser.add_argument( - "private_key_file", - help="The path to the generated ES256/RS256 private key file, e.g., /path/to/private_key.pem.") - - #optional arguments - parser.add_argument("-e", "--email", help="Preferred e-mail address.") - parser.add_argument("-a", "--azp", help="Authorized party - the party to which the ID Token was issued.") - parser.add_argument("-x", "--exp", type=int, help="Token expiration claim.") - parser.add_argument("-k", "--kid", help="Key id.") - parser.add_argument("-s", "--sub", help="Token subject claim.") - main(parser.parse_args()) diff --git a/src/envoy/http/mixer/BUILD b/src/envoy/http/mixer/BUILD deleted file mode 100644 index 9e79e72373a..00000000000 --- a/src/envoy/http/mixer/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2016 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", -) - -envoy_cc_library( - name = "filter_lib", - srcs = [ - "check_data.cc", - "check_data.h", - "config.cc", - "config.h", - "control.cc", - "control.h", - "control_factory.h", - "filter.cc", - "filter.h", - "filter_factory.cc", - "header_update.h", - "report_data.h", - ], - repository = "@envoy", - visibility = ["//visibility:public"], - deps = [ - "//src/envoy/http/jwt_auth:http_filter_lib", - "//src/envoy/utils:authn_lib", - "//src/envoy/utils:utils_lib", - "//src/istio/control/http:control_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) diff --git a/src/envoy/http/mixer/README.md b/src/envoy/http/mixer/README.md deleted file mode 100644 index 72ab7d18893..00000000000 --- a/src/envoy/http/mixer/README.md +++ /dev/null @@ -1,236 +0,0 @@ - -This Proxy will use Envoy and talk to Mixer server. - -### Notice: -[envoy.conf.template](https://github.com/istio/proxy/blob/master/src/envoy/http/mixer/envoy.conf.template) -and [start_envoy](https://github.com/istio/proxy/blob/master/src/envoy/http/mixer/start_envoy) -have duplicates (with sightly modification) under -[https://github.com/istio/istio/tree/master/tests/integration/component/proxy](https://github.com/istio/istio/tree/master/tests/integration/component/proxy). -Please make sure to modify both the original one and the copy together if necessary. - - -## Build Mixer server - -* Follow https://github.com/istio/istio/blob/master/mixer/doc/running-local-mixer.md to run a Mixer server locally. -Follow https://github.com/istio/istio/blob/master/DEV-GUIDE.md to build Istio, which includes building Mixer server. - -## Build Envoy proxy - -* Follow https://github.com/lyft/envoy/blob/master/bazel/README.md to set up environment, and build target envoy: - -``` - bazel build //src/envoy:envoy -``` - -## How to run it - -* Start mixer server. In mixer folder run: - -``` - bazel-bin/cmd/server/mixs server \ - --configStoreURL=fs://$(pwd)/testdata/configroot \ - --alsologtostderr -``` - - The server will run at port 9091. - In order to run Mixer locally, you also need to edit `testdata/configroot/scopes/global/subjects/global/rules.yml` as described in its comments. - -* Start backend Echo server. - -``` - cd test/backend/echo - go run echo.go -``` - -* Start Envoy proxy, run - -``` - src/envoy/mixer/start_envoy -``` - -* Then issue HTTP request to proxy. - -``` - # request to server-side proxy - curl http://localhost:9090/echo -d "hello world" - # request to client-side proxy that gets sent to server-side proxy - curl http://localhost:7070/echo -d "hello world" -``` - -## How to configurate Mixer server - -In Envoy config, Mixer server has to be one of "clusters" under "cluster_manager". -For examples: -``` - "cluster_manager": { - "clusters": [ - ..., - { - "name": "mixer_server", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "features": "http2", - "hosts": [ - { - "url": "tcp://${MIXER_SERVER}" - } - ] - } -``` -Its name has to be "mixer_server". - -## How to configurate HTTP Mixer filters - -This filter will intercept all HTTP requests and call Mixer. Here is its config: - -``` - "filters": [ - "type": "decoder", - "name": "mixer", - "config": { - "mixer_attributes" : { - "attribute_name1": "attribute_value1", - "attribute_name2": "attribute_value2" - }, - "forward_attributes" : { - "attribute_name1": "attribute_value1", - "attribute_name2": "attribute_value2" - }, - "quota_name": "RequestCount", - "quota_amount": "1" - } -``` - -Notes: -* mixer_attributes: these attributes will be sent to the mixer in both Check and Report calls. -* forward_attributes: these attributes will be forwarded to the upstream istio/proxy. It will send them to mixer in Check and Report calls. -* quota_name, quota_amount are used for making quota call. quota_amount defaults to 1. - -## HTTP Route opaque config -By default, the mixer filter only forwards attributes and does not call mixer server. This behavior can be changed per HTTP route by supplying an opaque config: - -``` - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "service1", - "opaque_config": { - "mixer_control": "off", - "mixer_check": "on", - "mixer_report": "off", - "mixer_forward": "off" - } - } -``` -Notes: -* mixer_forward: turn on/off attribute forwarding feature. If on, it will forward attributes specified in "forward_attributes" in mixer filter config and "mixer_forward_attributes." prefixed attributes in route opaque data. Default is on. -* mixer_check: if on, call Mixer Check. Default is off. -* mixer_report: if on, call Mixer Report. Default is off. -* mixer_control: if on, call both Mixer Check and Report. Default is off. If "mixer_check" or "mixer_report" is specified, its value will override this. - -Above route opaque config reverts the behavior by sending requests to mixer server but not forwarding any attributes. - -Mixer attributes and forward attributes can be set per-route in the route opaque config. - -``` - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "service1", - "opaque_config": { - "mixer_attributes.key1": "value1", - "mixer_forward_attributes.key2": "value2" - } - } -``` -Attribute key1 = value1 will be sent to the mixer if mixer is on. Attribute key1 = value2 will be forwarded to next proxy if mixer_forward is on. - - -## How to enable quota (rate limiting) - -Quota (rate limiting) is enforced by the mixer. Mixer needs to be configured with Quota in its global config and service config. Its quota config will have -"quota name", its limit within a window. If "Quota" is added but param is missing, the default config is: quota name is "RequestCount", the limit is 10 with 1 second window. Essentially, it is imposing 10 qps rate limiting. - -Mixer client can be configured to make Quota call for all requests. If "quota_name" is specified in the mixer filter config, mixer client will call Quota with the specified quota name. If "quota_amount" is specified, it will call with that amount, otherwise the used amount is 1. - -Following config will enable rate limiting with Mixer: - -``` - "quota_name": "RequestCount", - -``` - - -## How to pass some attributes from client proxy to mixer. - -Usually client proxy is not configured to call mixer (it can be enabled in the route opaque_config). Client proxy can pass some attributes to mixer by using "forward_attributes" field. Its attributes will be sent to the upstream proxy (the server proxy). If the server proxy is calling mixer, these attributes will be sent to the mixer. - - -## How to disable cache for Check calls - -Check calls can be cached. By default, it is enabled. Mixer server controls which attributes to use for cache keys. It also controls the cache expiration. - -Check cache can be disabled with following config: -``` - "disable_check_cache": true, -``` - -## How to disable cache for Quota calls - -Quota cache is tied to Check cache. It is enabled automatically if Check cache is enabled. It can be disabled with following config: -``` - "disable_quota_cache": true, -``` - -## How to disable batch for Report calls - -Reports are batched up to 1 second or up to 1000 records. It is enabled by default. It can be disabled with following config: -``` - "disable_report_batch": true, -``` - -## How to change network failure policy - -When there is any network problems between the proxy and the mixer server, what should the proxy do for its Check calls? There are two policy: fail open or fail close. By default, it is using fail open policy. It can be changed by adding this mixer filter config "network_fail_policy". Its value can be "open" or "close". For example, following config will change the policy to fail close. - -``` - "network_fail_policy": "close", - -``` -The default value is "open". - -## How to configurate TCP Mixer filters - -Here is its sample config: - -``` - "filters": [ - { - "type": "both", - "name": "mixer", - "config": { - "mixer_attributes": { - "destination.uid": "POD222", - "destination.service": "foo.svc.cluster.local" - }, - "quota_name": "RequestCount" - } - } - ] -``` - -This filter will intercept a tcp connection: -* Call Check at connection creation and call Report at connection close. -* All mixer settings described above can be used here. -* disable_tcp_check_calls is a tcp filter specific config to disable check for tcp connection. - - diff --git a/src/envoy/http/mixer/check_data.cc b/src/envoy/http/mixer/check_data.cc deleted file mode 100644 index 071061a547f..00000000000 --- a/src/envoy/http/mixer/check_data.cc +++ /dev/null @@ -1,201 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/mixer/check_data.h" -#include "common/common/base64.h" -#include "src/envoy/http/jwt_auth/jwt.h" -#include "src/envoy/http/jwt_auth/jwt_authenticator.h" -#include "src/envoy/utils/authn.h" -#include "src/envoy/utils/utils.h" - -using HttpCheckData = ::istio::control::http::CheckData; - -namespace Envoy { -namespace Http { -namespace Mixer { -namespace { -// The HTTP header to forward Istio attributes. -const LowerCaseString kIstioAttributeHeader("x-istio-attributes"); - -// Referer header -const LowerCaseString kRefererHeaderKey("referer"); - -// Set of headers excluded from request.headers attribute. -const std::set RequestHeaderExclusives = { - kIstioAttributeHeader.get(), -}; - -} // namespace - -CheckData::CheckData(const HeaderMap& headers, - const Network::Connection* connection) - : headers_(headers), connection_(connection) { - if (headers_.Path()) { - query_params_ = Utility::parseQueryString(std::string( - headers_.Path()->value().c_str(), headers_.Path()->value().size())); - } -} - -const LowerCaseString& CheckData::IstioAttributeHeader() { - return kIstioAttributeHeader; -} - -bool CheckData::ExtractIstioAttributes(std::string* data) const { - // Extract attributes from x-istio-attributes header - const HeaderEntry* entry = headers_.get(kIstioAttributeHeader); - if (entry) { - *data = Base64::decode( - std::string(entry->value().c_str(), entry->value().size())); - return true; - } - return false; -} - -bool CheckData::GetSourceIpPort(std::string* ip, int* port) const { - if (connection_) { - return Utils::GetIpPort(connection_->remoteAddress()->ip(), ip, port); - } - return false; -} - -bool CheckData::GetSourceUser(std::string* user) const { - return Utils::GetSourceUser(connection_, user); -} - -std::map CheckData::GetRequestHeaders() const { - return Utils::ExtractHeaders(headers_, RequestHeaderExclusives); -} - -bool CheckData::IsMutualTLS() const { return Utils::IsMutualTLS(connection_); } - -bool CheckData::FindHeaderByType(HttpCheckData::HeaderType header_type, - std::string* value) const { - switch (header_type) { - case HttpCheckData::HEADER_PATH: - if (headers_.Path()) { - *value = std::string(headers_.Path()->value().c_str(), - headers_.Path()->value().size()); - return true; - } - break; - case HttpCheckData::HEADER_HOST: - if (headers_.Host()) { - *value = std::string(headers_.Host()->value().c_str(), - headers_.Host()->value().size()); - return true; - } - break; - case HttpCheckData::HEADER_SCHEME: - if (headers_.Scheme()) { - *value = std::string(headers_.Scheme()->value().c_str(), - headers_.Scheme()->value().size()); - return true; - } - break; - case HttpCheckData::HEADER_USER_AGENT: - if (headers_.UserAgent()) { - *value = std::string(headers_.UserAgent()->value().c_str(), - headers_.UserAgent()->value().size()); - return true; - } - break; - case HttpCheckData::HEADER_METHOD: - if (headers_.Method()) { - *value = std::string(headers_.Method()->value().c_str(), - headers_.Method()->value().size()); - return true; - } - break; - case HttpCheckData::HEADER_REFERER: { - const HeaderEntry* referer = headers_.get(kRefererHeaderKey); - if (referer) { - *value = std::string(referer->value().c_str(), referer->value().size()); - return true; - } - } break; - } - return false; -} - -bool CheckData::FindHeaderByName(const std::string& name, - std::string* value) const { - const HeaderEntry* entry = headers_.get(LowerCaseString(name)); - if (entry) { - *value = std::string(entry->value().c_str(), entry->value().size()); - return true; - } - return false; -} - -bool CheckData::FindQueryParameter(const std::string& name, - std::string* value) const { - const auto& it = query_params_.find(name); - if (it != query_params_.end()) { - *value = it->second; - return true; - } - return false; -} - -bool CheckData::FindCookie(const std::string& name, std::string* value) const { - std::string cookie = Utility::parseCookieValue(headers_, name); - if (cookie != "") { - *value = cookie; - return true; - } - return false; -} - -bool CheckData::GetJWTPayload( - std::map* payload) const { - const HeaderEntry* entry = - headers_.get(JwtAuth::JwtAuthenticator::JwtPayloadKey()); - if (!entry) { - return false; - } - std::string value(entry->value().c_str(), entry->value().size()); - std::string payload_str = JwtAuth::Base64UrlDecode(value); - // Return an empty string if Base64 decode fails. - if (payload_str.empty()) { - ENVOY_LOG(error, "Invalid {} header, invalid base64: {}", - JwtAuth::JwtAuthenticator::JwtPayloadKey().get(), value); - return false; - } - try { - auto json_obj = Json::Factory::loadFromString(payload_str); - json_obj->iterate( - [payload](const std::string& key, const Json::Object& obj) -> bool { - // will throw execption if value type is not string. - try { - (*payload)[key] = obj.asString(); - } catch (...) { - } - return true; - }); - } catch (...) { - ENVOY_LOG(error, "Invalid {} header, invalid json: {}", - JwtAuth::JwtAuthenticator::JwtPayloadKey().get(), payload_str); - return false; - } - return true; -} - -bool CheckData::GetAuthenticationResult(istio::authn::Result* result) const { - return Utils::Authentication::FetchResultFromHeader(headers_, result); -} - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/check_data.h b/src/envoy/http/mixer/check_data.h deleted file mode 100644 index 2b9db96dc53..00000000000 --- a/src/envoy/http/mixer/check_data.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "common/http/utility.h" -#include "envoy/http/header_map.h" -#include "include/istio/control/http/controller.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -class CheckData : public ::istio::control::http::CheckData, - public Logger::Loggable { - public: - CheckData(const HeaderMap& headers, const Network::Connection* connection); - - // Find "x-istio-attributes" headers, if found base64 decode - // its value and remove it from the headers. - bool ExtractIstioAttributes(std::string* data) const override; - - bool GetSourceIpPort(std::string* ip, int* port) const override; - - bool GetSourceUser(std::string* user) const override; - - std::map GetRequestHeaders() const override; - - bool IsMutualTLS() const override; - - bool FindHeaderByType( - ::istio::control::http::CheckData::HeaderType header_type, - std::string* value) const override; - - bool FindHeaderByName(const std::string& name, - std::string* value) const override; - - bool FindQueryParameter(const std::string& name, - std::string* value) const override; - - bool FindCookie(const std::string& name, std::string* value) const override; - - bool GetJWTPayload( - std::map* payload) const override; - - bool GetAuthenticationResult(istio::authn::Result* result) const override; - - static const LowerCaseString& IstioAttributeHeader(); - - private: - const HeaderMap& headers_; - const Network::Connection* connection_; - Utility::QueryParams query_params_; -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/config.cc b/src/envoy/http/mixer/config.cc deleted file mode 100644 index 385cc22719d..00000000000 --- a/src/envoy/http/mixer/config.cc +++ /dev/null @@ -1,31 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/mixer/config.h" -#include "src/envoy/utils/config.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -Config::Config( - const ::istio::mixer::v1::config::client::HttpClientConfig& config_pb) - : config_pb_(config_pb) { - Utils::SetDefaultMixerClusters(config_pb_.mutable_transport()); -} - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/config.h b/src/envoy/http/mixer/config.h deleted file mode 100644 index c72823f65a9..00000000000 --- a/src/envoy/http/mixer/config.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "mixer/v1/config/client/client_config.pb.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -// Config for http filter. -class Config { - public: - Config(const ::istio::mixer::v1::config::client::HttpClientConfig& config_pb); - - // The Http client config. - const ::istio::mixer::v1::config::client::HttpClientConfig& config_pb() - const { - return config_pb_; - } - - // check cluster - const std::string& check_cluster() const { - return config_pb_.transport().check_cluster(); - } - // report cluster - const std::string& report_cluster() const { - return config_pb_.transport().report_cluster(); - } - - private: - // The Http client config. - ::istio::mixer::v1::config::client::HttpClientConfig config_pb_; -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/control.cc b/src/envoy/http/mixer/control.cc deleted file mode 100644 index 0ce9e327415..00000000000 --- a/src/envoy/http/mixer/control.cc +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/mixer/control.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -Control::Control(const Config& config, Upstream::ClusterManager& cm, - Event::Dispatcher& dispatcher, - Runtime::RandomGenerator& random, Stats::Scope& scope, - Utils::MixerFilterStats& stats) - : config_(config), - check_client_factory_(Utils::GrpcClientFactoryForCluster( - config_.check_cluster(), cm, scope)), - report_client_factory_(Utils::GrpcClientFactoryForCluster( - config_.report_cluster(), cm, scope)), - stats_obj_(dispatcher, stats, - config_.config_pb().transport().stats_update_interval(), - [this](::istio::mixerclient::Statistics* stat) -> bool { - return GetStats(stat); - }) { - ::istio::control::http::Controller::Options options(config_.config_pb()); - - Utils::CreateEnvironment(dispatcher, random, *check_client_factory_, - *report_client_factory_, &options.env); - - controller_ = ::istio::control::http::Controller::Create(options); -} - -Utils::CheckTransport::Func Control::GetCheckTransport( - Tracing::Span& parent_span) { - return Utils::CheckTransport::GetFunc(*check_client_factory_, parent_span); -} - -// Call controller to get statistics. -bool Control::GetStats(::istio::mixerclient::Statistics* stat) { - if (!controller_) { - return false; - } - controller_->GetStatistics(stat); - return true; -} - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/control.h b/src/envoy/http/mixer/control.h deleted file mode 100644 index 07740a4df9e..00000000000 --- a/src/envoy/http/mixer/control.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/event/dispatcher.h" -#include "envoy/runtime/runtime.h" -#include "envoy/thread_local/thread_local.h" -#include "envoy/upstream/cluster_manager.h" -#include "include/istio/control/http/controller.h" -#include "src/envoy/http/mixer/config.h" -#include "src/envoy/utils/grpc_transport.h" -#include "src/envoy/utils/mixer_control.h" -#include "src/envoy/utils/stats.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -// The control object created per-thread. -class Control final : public ThreadLocal::ThreadLocalObject { - public: - // The constructor. - Control(const Config& config, Upstream::ClusterManager& cm, - Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, - Stats::Scope& scope, Utils::MixerFilterStats& stats); - - // Get low-level controller object. - ::istio::control::http::Controller* controller() { return controller_.get(); } - - // Create a per-request Check transport function. - Utils::CheckTransport::Func GetCheckTransport(Tracing::Span& parent_span); - - private: - // Call controller to get statistics. - bool GetStats(::istio::mixerclient::Statistics* stat); - - // The mixer config. - const Config& config_; - // The mixer control - std::unique_ptr<::istio::control::http::Controller> controller_; - // async client factories - Grpc::AsyncClientFactoryPtr check_client_factory_; - Grpc::AsyncClientFactoryPtr report_client_factory_; - // The stats object. - Utils::MixerStatsObject stats_obj_; -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/control_factory.h b/src/envoy/http/mixer/control_factory.h deleted file mode 100644 index e6765cad102..00000000000 --- a/src/envoy/http/mixer/control_factory.h +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "src/envoy/http/mixer/control.h" -#include "src/envoy/utils/stats.h" - -namespace Envoy { -namespace Http { -namespace Mixer { -namespace { - -// Envoy stats perfix for HTTP filter stats. -const std::string kHttpStatsPrefix("http_mixer_filter."); - -} // namespace - -// This object is globally per listener. -// HttpMixerControl is created per-thread by this object. -class ControlFactory : public Logger::Loggable { - public: - ControlFactory(std::unique_ptr config, - Server::Configuration::FactoryContext& context) - : config_(std::move(config)), - tls_(context.threadLocal().allocateSlot()), - stats_{ALL_MIXER_FILTER_STATS( - POOL_COUNTER_PREFIX(context.scope(), kHttpStatsPrefix))} { - Upstream::ClusterManager& cm = context.clusterManager(); - Runtime::RandomGenerator& random = context.random(); - Stats::Scope& scope = context.scope(); - tls_->set([this, &cm, &random, &scope](Event::Dispatcher& dispatcher) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(*config_, cm, dispatcher, random, scope, - stats_); - }); - } - - Control& control() { return tls_->getTyped(); } - - private: - // Own the config object. - std::unique_ptr config_; - // Thread local slot. - ThreadLocal::SlotPtr tls_; - // This stats object. - Utils::MixerFilterStats stats_; -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/envoy.conf.template b/src/envoy/http/mixer/envoy.conf.template deleted file mode 100644 index bd5b8705dfa..00000000000 --- a/src/envoy/http/mixer/envoy.conf.template +++ /dev/null @@ -1,212 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://0.0.0.0:${PORT}", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "generate_request_id": true, - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "service1", - "opaque_config": { - "mixer_control": "on", - "mixer_forward": "off" - } - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/stdout" - } - ], - "filters": [ - { - "type": "decoder", - "name": "mixer", - "config": { - "v2": { - "defaultDestinationService": "hello.default.svc.cluster.local", - "mixerAttributes": { - "attributes": { - "destination.uid": { - "stringValue": "kubernetes://v1.default" - } - } - }, - "serviceConfigs": { - "hello.default.svc.cluster.local": { - "mixerAttributes": { - "attributes": { - "destination.service": { - "stringValue": "hello.default.svc.cluster.local" - } - } - } - } - } - } - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - }, - { - "address": "tcp://0.0.0.0:7070", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "generate_request_id": true, - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "service2" - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/stdout" - } - ], - "filters": [ - { - "type": "decoder", - "name": "mixer", - "config": { - "v2": { - "forwardAttributes": { - "attributes": { - "source.uid": { - "stringValue": "POD11" - }, - "source.namespace": { - "stringValue": "XYZ11" - } - } - } - } - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - }, - { - "address": "tcp://0.0.0.0:6060", - "bind_to_port": true, - "filters": [ - { - "type": "both", - "name": "mixer", - "config": { - "v2": {} - } - }, - { - "type": "read", - "name": "tcp_proxy", - "config": { - "stat_prefix": "tcp", - "route_config": { - "routes": [ - { - "cluster": "service1" - } - ] - } - } - } - ] - } - ], - "admin": { - "access_log_path": "/dev/stdout", - "address": "tcp://0.0.0.0:9001" - }, - "cluster_manager": { - "clusters": [ - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://${BACKEND}" - } - ] - }, - { - "name": "service2", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:9090" - } - ] - }, - { - "name": "mixer_server", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "features": "http2", - "hosts": [ - { - "url": "tcp://${MIXER_SERVER}" - } - ] - } - ] - } -} diff --git a/src/envoy/http/mixer/envoy_lds.conf b/src/envoy/http/mixer/envoy_lds.conf deleted file mode 100644 index 3b2ca50bf91..00000000000 --- a/src/envoy/http/mixer/envoy_lds.conf +++ /dev/null @@ -1,66 +0,0 @@ -{ - "listeners": [], - "lds": { - "cluster": "lds", - "refresh_delay_ms": 1000 - }, - "admin": { - "access_log_path": "/dev/stdout", - "address": "tcp://0.0.0.0:9001" - }, - "cluster_manager": { - "clusters": [ - { - "name": "lds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - { - "name": "service1", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - { - "name": "service2", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:9090" - } - ] - }, - { - "name": "mixer_server", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - }, - "lb_type": "round_robin", - "features": "http2", - "hosts": [ - { - "url": "tcp://localhost:9091" - } - ] - } - ] - } -} diff --git a/src/envoy/http/mixer/filter.cc b/src/envoy/http/mixer/filter.cc deleted file mode 100644 index 597ec5af51c..00000000000 --- a/src/envoy/http/mixer/filter.cc +++ /dev/null @@ -1,239 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/http/mixer/filter.h" - -#include "common/common/base64.h" -#include "include/istio/utils/status.h" -#include "src/envoy/http/mixer/check_data.h" -#include "src/envoy/http/mixer/header_update.h" -#include "src/envoy/http/mixer/report_data.h" -#include "src/envoy/utils/authn.h" - -using ::google::protobuf::util::Status; -using ::istio::mixer::v1::config::client::ServiceConfig; - -namespace Envoy { -namespace Http { -namespace Mixer { -namespace { - -// Per route opaque data for "destination.service". -const std::string kPerRouteDestinationService("destination.service"); -// Per route opaque data name "mixer" is base64(JSON(ServiceConfig)) -const std::string kPerRouteMixer("mixer"); -// Per route opaque data name "mixer_sha" is SHA(JSON(ServiceConfig)) -const std::string kPerRouteMixerSha("mixer_sha"); - -// Read a string value from a string map. -bool ReadStringMap(const std::multimap& string_map, - const std::string& name, std::string* value) { - auto it = string_map.find(name); - if (it != string_map.end()) { - *value = it->second; - return true; - } - return false; -} - -} // namespace - -Filter::Filter(Control& control) - : control_(control), - state_(NotStarted), - initiating_call_(false), - headers_(nullptr) { - ENVOY_LOG(debug, "Called Mixer::Filter : {}", __func__); -} - -void Filter::ReadPerRouteConfig( - const Router::RouteEntry* entry, - ::istio::control::http::Controller::PerRouteConfig* config) { - if (entry == nullptr) { - return; - } - - // Check v2 per-route config. - auto route_cfg = entry->perFilterConfigTyped("mixer"); - if (route_cfg) { - if (!control_.controller()->LookupServiceConfig(route_cfg->hash)) { - control_.controller()->AddServiceConfig(route_cfg->hash, - route_cfg->config); - } - config->service_config_id = route_cfg->hash; - return; - } - - const auto& string_map = entry->opaqueConfig(); - ReadStringMap(string_map, kPerRouteDestinationService, - &config->destination_service); - - if (!ReadStringMap(string_map, kPerRouteMixerSha, - &config->service_config_id) || - config->service_config_id.empty()) { - return; - } - - if (control_.controller()->LookupServiceConfig(config->service_config_id)) { - return; - } - - std::string config_base64; - if (!ReadStringMap(string_map, kPerRouteMixer, &config_base64)) { - ENVOY_LOG(warn, "Service {} missing [mixer] per-route attribute", - config->destination_service); - return; - } - std::string config_json = Base64::decode(config_base64); - if (config_json.empty()) { - ENVOY_LOG(warn, "Service {} invalid base64 config data", - config->destination_service); - return; - } - ServiceConfig config_pb; - auto status = Utils::ParseJsonMessage(config_json, &config_pb); - if (!status.ok()) { - ENVOY_LOG(warn, - "Service {} failed to convert JSON config to protobuf, error: {}", - config->destination_service, status.ToString()); - return; - } - control_.controller()->AddServiceConfig(config->service_config_id, config_pb); - ENVOY_LOG(info, "Service {}, config_id {}, config: {}", - config->destination_service, config->service_config_id, - config_pb.DebugString()); -} - -FilterHeadersStatus Filter::decodeHeaders(HeaderMap& headers, bool) { - ENVOY_LOG(debug, "Called Mixer::Filter : {}", __func__); - request_total_size_ += headers.byteSize(); - - ::istio::control::http::Controller::PerRouteConfig config; - auto route = decoder_callbacks_->route(); - if (route) { - ReadPerRouteConfig(route->routeEntry(), &config); - } - handler_ = control_.controller()->CreateRequestHandler(config); - - state_ = Calling; - initiating_call_ = true; - CheckData check_data(headers, decoder_callbacks_->connection()); - HeaderUpdate header_update(&headers); - headers_ = &headers; - cancel_check_ = handler_->Check( - &check_data, &header_update, - control_.GetCheckTransport(decoder_callbacks_->activeSpan()), - [this](const Status& status) { completeCheck(status); }); - initiating_call_ = false; - - if (state_ == Complete) { - return FilterHeadersStatus::Continue; - } - ENVOY_LOG(debug, "Called Mixer::Filter : {} Stop", __func__); - return FilterHeadersStatus::StopIteration; -} - -FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_LOG(debug, "Called Mixer::Filter : {} ({}, {})", __func__, - data.length(), end_stream); - request_total_size_ += data.length(); - if (state_ == Calling) { - return FilterDataStatus::StopIterationAndWatermark; - } - return FilterDataStatus::Continue; -} - -FilterTrailersStatus Filter::decodeTrailers(HeaderMap& trailers) { - ENVOY_LOG(debug, "Called Mixer::Filter : {}", __func__); - request_total_size_ += trailers.byteSize(); - if (state_ == Calling) { - return FilterTrailersStatus::StopIteration; - } - return FilterTrailersStatus::Continue; -} - -void Filter::setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) { - ENVOY_LOG(debug, "Called Mixer::Filter : {}", __func__); - decoder_callbacks_ = &callbacks; -} - -void Filter::completeCheck(const Status& status) { - ENVOY_LOG(debug, "Called Mixer::Filter : check complete {}", - status.ToString()); - // Remove Istio authentication header after Check() is completed - if (nullptr != headers_) { - Envoy::Utils::Authentication::ClearResultInHeader(headers_); - headers_ = nullptr; - } - - // This stream has been reset, abort the callback. - if (state_ == Responded) { - return; - } - if (!status.ok() && state_ != Responded) { - state_ = Responded; - int status_code = ::istio::utils::StatusHttpCode(status.error_code()); - Utility::sendLocalReply(*decoder_callbacks_, false, Code(status_code), - status.ToString()); - return; - } - - state_ = Complete; - if (!initiating_call_) { - decoder_callbacks_->continueDecoding(); - } -} - -void Filter::onDestroy() { - ENVOY_LOG(debug, "Called Mixer::Filter : {} state: {}", __func__, state_); - if (state_ != Calling) { - cancel_check_ = nullptr; - } - state_ = Responded; - if (cancel_check_) { - ENVOY_LOG(debug, "Cancelling check call"); - cancel_check_(); - cancel_check_ = nullptr; - } -} - -void Filter::log(const HeaderMap* request_headers, - const HeaderMap* response_headers, - const HeaderMap* response_trailers, - const RequestInfo::RequestInfo& request_info) { - ENVOY_LOG(debug, "Called Mixer::Filter : {}", __func__); - if (!handler_) { - if (request_headers == nullptr) { - return; - } - - // Here Request is rejected by other filters, Mixer filter is not called. - ::istio::control::http::Controller::PerRouteConfig config; - ReadPerRouteConfig(request_info.routeEntry(), &config); - handler_ = control_.controller()->CreateRequestHandler(config); - - CheckData check_data(*request_headers, nullptr); - handler_->ExtractRequestAttributes(&check_data); - } - // response trailer header is not counted to response total size. - ReportData report_data(response_headers, response_trailers, request_info, - request_total_size_); - handler_->Report(&report_data); -} - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/filter.h b/src/envoy/http/mixer/filter.h deleted file mode 100644 index 6c31add3993..00000000000 --- a/src/envoy/http/mixer/filter.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/access_log/access_log.h" -#include "envoy/http/filter.h" -#include "src/envoy/http/mixer/control.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -// The struct to store per-route service config and its hash. -struct PerRouteServiceConfig : public Router::RouteSpecificFilterConfig { - // The per_route service config. - ::istio::mixer::v1::config::client::ServiceConfig config; - - // Its config hash - std::string hash; -}; - -class Filter : public Http::StreamDecoderFilter, - public AccessLog::Instance, - public Logger::Loggable { - public: - Filter(Control& control); - - // Implementing virtual functions for StreamDecoderFilter - FilterHeadersStatus decodeHeaders(HeaderMap& headers, bool) override; - FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; - FilterTrailersStatus decodeTrailers(HeaderMap& trailers) override; - void setDecoderFilterCallbacks( - StreamDecoderFilterCallbacks& callbacks) override; - - // Http::StreamFilterBase - void onDestroy() override; - - // This is the callback function when Check is done. - void completeCheck(const ::google::protobuf::util::Status& status); - - // Called when the request is completed. - virtual void log(const HeaderMap* request_headers, - const HeaderMap* response_headers, - const HeaderMap* response_trailers, - const RequestInfo::RequestInfo& request_info) override; - - private: - // Read per-route config. - void ReadPerRouteConfig( - const Router::RouteEntry* entry, - ::istio::control::http::Controller::PerRouteConfig* config); - - // The control object. - Control& control_; - // The request handler. - std::unique_ptr<::istio::control::http::RequestHandler> handler_; - // The pending callback object. - istio::mixerclient::CancelFunc cancel_check_; - - enum State { NotStarted, Calling, Complete, Responded }; - // The state - State state_; - bool initiating_call_; - - // Point to the request HTTP headers - HeaderMap* headers_; - - // Total number of bytes received, including request headers, body, and - // trailers. - uint64_t request_total_size_{0}; - - // The stream decoder filter callback. - StreamDecoderFilterCallbacks* decoder_callbacks_{nullptr}; -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/filter_factory.cc b/src/envoy/http/mixer/filter_factory.cc deleted file mode 100644 index 4f045d479ba..00000000000 --- a/src/envoy/http/mixer/filter_factory.cc +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "common/config/utility.h" -#include "envoy/json/json_object.h" -#include "envoy/registry/registry.h" -#include "envoy/server/filter_config.h" -#include "src/envoy/http/mixer/control_factory.h" -#include "src/envoy/http/mixer/filter.h" -#include "src/envoy/utils/config.h" - -using ::istio::mixer::v1::config::client::HttpClientConfig; -using ::istio::mixer::v1::config::client::ServiceConfig; - -namespace Envoy { -namespace Server { -namespace Configuration { - -class MixerConfigFactory : public NamedHttpFilterConfigFactory { - public: - Http::FilterFactoryCb createFilterFactory(const Json::Object& config_json, - const std::string& prefix, - FactoryContext& context) override { - HttpClientConfig config_pb; - if (!Utils::ReadV2Config(config_json, &config_pb) && - !Utils::ReadV1Config(config_json, &config_pb)) { - throw EnvoyException("Failed to parse JSON config"); - } - - return createFilterFactory(config_pb, prefix, context); - } - - Http::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message& proto_config, const std::string& prefix, - FactoryContext& context) override { - return createFilterFactory( - dynamic_cast(proto_config), prefix, context); - } - - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return ProtobufTypes::MessagePtr{new HttpClientConfig}; - } - - ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override { - return ProtobufTypes::MessagePtr{new ServiceConfig}; - } - - Router::RouteSpecificFilterConfigConstSharedPtr - createRouteSpecificFilterConfig( - const Protobuf::Message& config, - Envoy::Server::Configuration::FactoryContext&) override { - auto obj = std::make_shared(); - // TODO: use downcastAndValidate once client_config.proto adds validate - // rules. - obj->config = dynamic_cast(config); - obj->hash = std::to_string(MessageUtil::hash(obj->config)); - return obj; - } - - std::string name() override { return "mixer"; } - - private: - Http::FilterFactoryCb createFilterFactory(const HttpClientConfig& config_pb, - const std::string&, - FactoryContext& context) { - std::unique_ptr config_obj( - new Http::Mixer::Config(config_pb)); - auto control_factory = std::make_shared( - std::move(config_obj), context); - return [control_factory]( - Http::FilterChainFactoryCallbacks& callbacks) -> void { - std::shared_ptr instance = - std::make_shared(control_factory->control()); - callbacks.addStreamDecoderFilter( - Http::StreamDecoderFilterSharedPtr(instance)); - callbacks.addAccessLogHandler(AccessLog::InstanceSharedPtr(instance)); - }; - } -}; - -static Registry::RegisterFactory - register_; - -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/http/mixer/header_update.h b/src/envoy/http/mixer/header_update.h deleted file mode 100644 index 73f05798b8b..00000000000 --- a/src/envoy/http/mixer/header_update.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/http/header_map.h" -#include "include/istio/control/http/controller.h" -#include "src/envoy/http/mixer/check_data.h" - -namespace Envoy { -namespace Http { -namespace Mixer { - -class HeaderUpdate : public ::istio::control::http::HeaderUpdate, - public Logger::Loggable { - HeaderMap* headers_; - - public: - HeaderUpdate(HeaderMap* headers) : headers_(headers) {} - - void RemoveIstioAttributes() override { - headers_->remove(CheckData::IstioAttributeHeader()); - } - - // base64 encode data, and add it to the HTTP header. - void AddIstioAttributes(const std::string& data) override { - std::string base64 = Base64::encode(data.c_str(), data.size()); - ENVOY_LOG(debug, "Mixer forward attributes set: {}", base64); - headers_->addReferenceKey(CheckData::IstioAttributeHeader(), base64); - } -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/report_data.h b/src/envoy/http/mixer/report_data.h deleted file mode 100644 index 46658b60b5f..00000000000 --- a/src/envoy/http/mixer/report_data.h +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/http/header_map.h" -#include "envoy/request_info/request_info.h" -#include "include/istio/control/http/controller.h" -#include "src/envoy/utils/utils.h" - -namespace Envoy { -namespace Http { -namespace Mixer { -namespace { -// Set of headers excluded from response.headers attribute. -const std::set ResponseHeaderExclusives = {}; - -} // namespace - -class ReportData : public ::istio::control::http::ReportData { - const HeaderMap *headers_; - const RequestInfo::RequestInfo &info_; - uint64_t response_total_size_; - uint64_t request_total_size_; - - public: - ReportData(const HeaderMap *headers, const HeaderMap *response_trailers, - const RequestInfo::RequestInfo &info, uint64_t request_total_size) - : headers_(headers), - info_(info), - response_total_size_(info.bytesSent()), - request_total_size_(request_total_size) { - if (headers != nullptr) { - response_total_size_ += headers->byteSize(); - } - if (response_trailers != nullptr) { - response_total_size_ += response_trailers->byteSize(); - } - } - - std::map GetResponseHeaders() const override { - if (headers_) { - return Utils::ExtractHeaders(*headers_, ResponseHeaderExclusives); - } - return std::map(); - } - - void GetReportInfo( - ::istio::control::http::ReportData::ReportInfo *data) const override { - data->request_body_size = info_.bytesReceived(); - data->response_body_size = info_.bytesSent(); - data->response_total_size = response_total_size_; - data->request_total_size = request_total_size_; - data->duration = - info_.requestComplete().value_or(std::chrono::nanoseconds{0}); - // responseCode is for the backend response. If it is not valid, the request - // is rejected by Envoy. Set the response code for such requests as 500. - data->response_code = info_.responseCode().value_or(500); - } - - bool GetDestinationIpPort(std::string *str_ip, int *port) const override { - if (info_.upstreamHost() && info_.upstreamHost()->address()) { - return Utils::GetIpPort(info_.upstreamHost()->address()->ip(), str_ip, - port); - } - return false; - } -}; - -} // namespace Mixer -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/mixer/start_envoy b/src/envoy/http/mixer/start_envoy deleted file mode 100755 index 5fef513fc08..00000000000 --- a/src/envoy/http/mixer/start_envoy +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -# -# Copyright 2016 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" - -function usage() { - [[ -n "${1}" ]] && echo "${1}" - - cat < "${CONFIG}" - -"${ENVOY}" -c "${CONFIG}" "${DEBUG}" diff --git a/src/envoy/tcp/mixer/config.h b/src/envoy/tcp/mixer/config.h deleted file mode 100644 index acf79feaa74..00000000000 --- a/src/envoy/tcp/mixer/config.h +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "mixer/v1/config/client/client_config.pb.h" -#include "src/envoy/utils/config.h" - -namespace Envoy { -namespace Tcp { -namespace Mixer { -namespace { - -// Default time interval for periodical report is 10 seconds. -const std::chrono::milliseconds kDefaultReportIntervalMs(10000); - -// Minimum time interval for periodical report is 1 seconds. -const std::chrono::milliseconds kMinReportIntervalMs(1000); - -} // namespace - -// Config for tcp filter. -class Config { - public: - Config(const ::istio::mixer::v1::config::client::TcpClientConfig& config_pb) - : config_pb_(config_pb) { - Utils::SetDefaultMixerClusters(config_pb_.mutable_transport()); - - if (config_pb_.has_report_interval() && - config_pb_.report_interval().seconds() >= 0 && - config_pb_.report_interval().nanos() >= 0) { - report_interval_ms_ = std::chrono::milliseconds( - config_pb_.report_interval().seconds() * 1000 + - config_pb_.report_interval().nanos() / 1000000); - // If configured time interval is less than 1 second, then set report - // interval to 1 second. - if (report_interval_ms_ < kMinReportIntervalMs) { - report_interval_ms_ = kMinReportIntervalMs; - } - } else { - report_interval_ms_ = kDefaultReportIntervalMs; - } - } - - // The Tcp client config. - const ::istio::mixer::v1::config::client::TcpClientConfig& config_pb() const { - return config_pb_; - } - - // check cluster - const std::string& check_cluster() const { - return config_pb_.transport().check_cluster(); - } - // report cluster - const std::string& report_cluster() const { - return config_pb_.transport().report_cluster(); - } - - std::chrono::milliseconds report_interval_ms() const { - return report_interval_ms_; - } - - private: - // The Tcp client config. - ::istio::mixer::v1::config::client::TcpClientConfig config_pb_; - // Time interval in milliseconds for sending periodical delta reports. - std::chrono::milliseconds report_interval_ms_; -}; - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/control.cc b/src/envoy/tcp/mixer/control.cc deleted file mode 100644 index 1c9146405a2..00000000000 --- a/src/envoy/tcp/mixer/control.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/tcp/mixer/control.h" -#include "src/envoy/utils/mixer_control.h" - -using ::istio::mixerclient::Statistics; - -namespace Envoy { -namespace Tcp { -namespace Mixer { - -Control::Control(const Config& config, Upstream::ClusterManager& cm, - Event::Dispatcher& dispatcher, - Runtime::RandomGenerator& random, Stats::Scope& scope, - Utils::MixerFilterStats& stats, const std::string& uuid) - : config_(config), - dispatcher_(dispatcher), - check_client_factory_(Utils::GrpcClientFactoryForCluster( - config_.check_cluster(), cm, scope)), - report_client_factory_(Utils::GrpcClientFactoryForCluster( - config_.report_cluster(), cm, scope)), - stats_obj_(dispatcher, stats, - config_.config_pb().transport().stats_update_interval(), - [this](Statistics* stat) -> bool { return GetStats(stat); }), - uuid_(uuid) { - ::istio::control::tcp::Controller::Options options(config_.config_pb()); - - Utils::CreateEnvironment(dispatcher, random, *check_client_factory_, - *report_client_factory_, &options.env); - - controller_ = ::istio::control::tcp::Controller::Create(options); -} - -// Call controller to get statistics. -bool Control::GetStats(Statistics* stat) { - if (!controller_) { - return false; - } - controller_->GetStatistics(stat); - return true; -} - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/control.h b/src/envoy/tcp/mixer/control.h deleted file mode 100644 index db6f69ef979..00000000000 --- a/src/envoy/tcp/mixer/control.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/event/dispatcher.h" -#include "envoy/runtime/runtime.h" -#include "envoy/thread_local/thread_local.h" -#include "envoy/upstream/cluster_manager.h" -#include "include/istio/control/tcp/controller.h" -#include "src/envoy/tcp/mixer/config.h" -#include "src/envoy/utils/stats.h" - -namespace Envoy { -namespace Tcp { -namespace Mixer { - -class Control final : public ThreadLocal::ThreadLocalObject { - public: - // The constructor. - Control(const Config& config, Upstream::ClusterManager& cm, - Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, - Stats::Scope& scope, Utils::MixerFilterStats& stats, - const std::string& uuid); - - ::istio::control::tcp::Controller* controller() { return controller_.get(); } - - Event::Dispatcher& dispatcher() { return dispatcher_; } - - const std::string& uuid() const { return uuid_; } - - const Config& config() const { return config_; } - - private: - // Call controller to get statistics. - bool GetStats(::istio::mixerclient::Statistics* stat); - - // The mixer config. - const Config& config_; - // The mixer control - std::unique_ptr<::istio::control::tcp::Controller> controller_; - - // dispatcher. - Event::Dispatcher& dispatcher_; - - // async client factories - Grpc::AsyncClientFactoryPtr check_client_factory_; - Grpc::AsyncClientFactoryPtr report_client_factory_; - - // statistics - Utils::MixerStatsObject stats_obj_; - // UUID of the Envoy TCP mixer filter. - const std::string& uuid_; -}; - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/control_factory.h b/src/envoy/tcp/mixer/control_factory.h deleted file mode 100644 index 763d0e65cfe..00000000000 --- a/src/envoy/tcp/mixer/control_factory.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "src/envoy/tcp/mixer/control.h" - -namespace Envoy { -namespace Tcp { -namespace Mixer { -namespace { - -// Envoy stats perfix for TCP filter stats. -const std::string kTcpStatsPrefix("tcp_mixer_filter."); - -} // namespace - -class ControlFactory : public Logger::Loggable { - public: - ControlFactory(std::unique_ptr config, - Server::Configuration::FactoryContext& context) - : config_(std::move(config)), - cm_(context.clusterManager()), - tls_(context.threadLocal().allocateSlot()), - stats_(generateStats(kTcpStatsPrefix, context.scope())), - uuid_(context.random().uuid()) { - Runtime::RandomGenerator& random = context.random(); - Stats::Scope& scope = context.scope(); - tls_->set([this, &random, &scope](Event::Dispatcher& dispatcher) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - return ThreadLocal::ThreadLocalObjectSharedPtr( - new Control(*config_, cm_, dispatcher, random, scope, stats_, uuid_)); - }); - } - - // Get the per-thread control - Control& control() { return tls_->getTyped(); } - - private: - // Generates stats struct. - static Utils::MixerFilterStats generateStats(const std::string& name, - Stats::Scope& scope) { - return {ALL_MIXER_FILTER_STATS(POOL_COUNTER_PREFIX(scope, name))}; - } - - // The config object - std::unique_ptr config_; - // The cluster manager - Upstream::ClusterManager& cm_; - // the thread local slots - ThreadLocal::SlotPtr tls_; - // The statistics struct. - Utils::MixerFilterStats stats_; - // UUID of the Envoy TCP mixer filter. - const std::string uuid_; -}; - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/filter.cc b/src/envoy/tcp/mixer/filter.cc deleted file mode 100644 index 3c0767d8ae9..00000000000 --- a/src/envoy/tcp/mixer/filter.cc +++ /dev/null @@ -1,189 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/tcp/mixer/filter.h" -#include "common/common/enum_to_int.h" -#include "src/envoy/utils/utils.h" - -using ::google::protobuf::util::Status; - -namespace Envoy { -namespace Tcp { -namespace Mixer { - -Filter::Filter(Control& control) : control_(control) { - ENVOY_LOG(debug, "Called tcp filter: {}", __func__); -} - -Filter::~Filter() { - cancelCheck(); - ENVOY_LOG(debug, "Called tcp filter : {}", __func__); -} - -void Filter::initializeReadFilterCallbacks( - Network::ReadFilterCallbacks& callbacks) { - ENVOY_LOG(debug, "Called tcp filter: {}", __func__); - filter_callbacks_ = &callbacks; - filter_callbacks_->connection().addConnectionCallbacks(*this); - start_time_ = std::chrono::system_clock::now(); -} - -void Filter::cancelCheck() { - if (state_ != State::Calling) { - cancel_check_ = nullptr; - } - state_ = State::Closed; - if (cancel_check_) { - ENVOY_LOG(debug, "Cancelling check call"); - cancel_check_(); - cancel_check_ = nullptr; - } -} - -// Makes a Check() call to Mixer. -void Filter::callCheck() { - handler_ = control_.controller()->CreateRequestHandler(); - - state_ = State::Calling; - filter_callbacks_->connection().readDisable(true); - calling_check_ = true; - cancel_check_ = handler_->Check( - this, [this](const Status& status) { completeCheck(status); }); - calling_check_ = false; -} - -// Network::ReadFilter -Network::FilterStatus Filter::onData(Buffer::Instance& data, bool) { - if (state_ == State::NotStarted) { - // By waiting to invoke the callCheck() at onData(), the call to Mixer - // will have sufficient SSL information to fill the check Request. - callCheck(); - } - - ENVOY_CONN_LOG(debug, "Called tcp filter onRead bytes: {}", - filter_callbacks_->connection(), data.length()); - received_bytes_ += data.length(); - - return state_ == State::Calling ? Network::FilterStatus::StopIteration - : Network::FilterStatus::Continue; -} - -// Network::WriteFilter -Network::FilterStatus Filter::onWrite(Buffer::Instance& data, bool) { - ENVOY_CONN_LOG(debug, "Called tcp filter onWrite bytes: {}", - filter_callbacks_->connection(), data.length()); - send_bytes_ += data.length(); - return Network::FilterStatus::Continue; -} - -Network::FilterStatus Filter::onNewConnection() { - ENVOY_CONN_LOG(debug, - "Called tcp filter onNewConnection: remote {}, local {}", - filter_callbacks_->connection(), - filter_callbacks_->connection().remoteAddress()->asString(), - filter_callbacks_->connection().localAddress()->asString()); - - // Wait until onData() is invoked. - return Network::FilterStatus::Continue; -} - -void Filter::completeCheck(const Status& status) { - ENVOY_LOG(debug, "Called tcp filter completeCheck: {}", status.ToString()); - cancel_check_ = nullptr; - if (state_ == State::Closed) { - return; - } - state_ = State::Completed; - filter_callbacks_->connection().readDisable(false); - - if (!status.ok()) { - filter_callbacks_->connection().close( - Network::ConnectionCloseType::NoFlush); - } else { - if (!calling_check_) { - filter_callbacks_->continueReading(); - } - report_timer_ = - control_.dispatcher().createTimer([this]() { OnReportTimer(); }); - report_timer_->enableTimer(control_.config().report_interval_ms()); - } -} - -// Network::ConnectionCallbacks -void Filter::onEvent(Network::ConnectionEvent event) { - if (filter_callbacks_->upstreamHost()) { - ENVOY_LOG(debug, "Called tcp filter onEvent: {} upstream {}", - enumToInt(event), - filter_callbacks_->upstreamHost()->address()->asString()); - } else { - ENVOY_LOG(debug, "Called tcp filter onEvent: {}", enumToInt(event)); - } - - if (event == Network::ConnectionEvent::RemoteClose || - event == Network::ConnectionEvent::LocalClose) { - if (state_ != State::Closed && handler_) { - if (report_timer_) { - report_timer_->disableTimer(); - } - handler_->Report(this, /* is_final_report */ true); - } - cancelCheck(); - } -} - -bool Filter::GetSourceIpPort(std::string* str_ip, int* port) const { - return Utils::GetIpPort(filter_callbacks_->connection().remoteAddress()->ip(), - str_ip, port); -} -bool Filter::GetSourceUser(std::string* user) const { - return Utils::GetSourceUser(&filter_callbacks_->connection(), user); -} - -bool Filter::IsMutualTLS() const { - return Utils::IsMutualTLS(&filter_callbacks_->connection()); -} - -bool Filter::GetDestinationIpPort(std::string* str_ip, int* port) const { - if (filter_callbacks_->upstreamHost() && - filter_callbacks_->upstreamHost()->address()) { - return Utils::GetIpPort(filter_callbacks_->upstreamHost()->address()->ip(), - str_ip, port); - } - return false; -} -void Filter::GetReportInfo( - ::istio::control::tcp::ReportData::ReportInfo* data) const { - data->received_bytes = received_bytes_; - data->send_bytes = send_bytes_; - data->duration = std::chrono::duration_cast( - std::chrono::system_clock::now() - start_time_); -} - -std::string Filter::GetConnectionId() const { - char connection_id_str[32]; - StringUtil::itoa(connection_id_str, 32, filter_callbacks_->connection().id()); - std::string uuid_connection_id = control_.uuid() + "-"; - uuid_connection_id.append(connection_id_str); - return uuid_connection_id; -} - -void Filter::OnReportTimer() { - handler_->Report(this, /* is_final_report */ false); - report_timer_->enableTimer(control_.config().report_interval_ms()); -} - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/filter.h b/src/envoy/tcp/mixer/filter.h deleted file mode 100644 index a612a870310..00000000000 --- a/src/envoy/tcp/mixer/filter.h +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "common/common/logger.h" -#include "envoy/network/connection.h" -#include "envoy/network/filter.h" -#include "src/envoy/tcp/mixer/control.h" - -namespace Envoy { -namespace Tcp { -namespace Mixer { - -class Filter : public Network::Filter, - public Network::ConnectionCallbacks, - public ::istio::control::tcp::CheckData, - public ::istio::control::tcp::ReportData, - public Logger::Loggable { - public: - Filter(Control& control); - ~Filter(); - - void initializeReadFilterCallbacks( - Network::ReadFilterCallbacks& callbacks) override; - - // Network::ReadFilter - Network::FilterStatus onData(Buffer::Instance& data, bool) override; - - // Network::WriteFilter - Network::FilterStatus onWrite(Buffer::Instance& data, bool) override; - Network::FilterStatus onNewConnection() override; - - // Network::ConnectionCallbacks - void onEvent(Network::ConnectionEvent event) override; - - void onAboveWriteBufferHighWatermark() override {} - void onBelowWriteBufferLowWatermark() override {} - - // CheckData virtual functions. - bool GetSourceIpPort(std::string* str_ip, int* port) const override; - bool GetSourceUser(std::string* user) const override; - bool IsMutualTLS() const override; - - // ReportData virtual functions. - bool GetDestinationIpPort(std::string* str_ip, int* port) const override; - void GetReportInfo( - ::istio::control::tcp::ReportData::ReportInfo* data) const override; - std::string GetConnectionId() const override; - - private: - enum class State { NotStarted, Calling, Completed, Closed }; - // This function is invoked when timer event fires. - // It sends periodical delta reports. - void OnReportTimer(); - - // Makes a Check() call to Mixer. - void callCheck(); - - // Called when Check is done. - void completeCheck(const ::google::protobuf::util::Status& status); - - // Cancel the pending Check call. - void cancelCheck(); - - // the cancel check - istio::mixerclient::CancelFunc cancel_check_; - // the control object. - Control& control_; - // pre-request handler - std::unique_ptr<::istio::control::tcp::RequestHandler> handler_; - // filter callback - Network::ReadFilterCallbacks* filter_callbacks_{}; - // state - State state_{State::NotStarted}; - // calling_check - bool calling_check_{}; - // received bytes - uint64_t received_bytes_{}; - // send bytes - uint64_t send_bytes_{}; - - // Timer that periodically sends reports. - Event::TimerPtr report_timer_; - // start_time - std::chrono::time_point start_time_; -}; - -} // namespace Mixer -} // namespace Tcp -} // namespace Envoy diff --git a/src/envoy/tcp/mixer/filter_factory.cc b/src/envoy/tcp/mixer/filter_factory.cc deleted file mode 100644 index fe9732a8865..00000000000 --- a/src/envoy/tcp/mixer/filter_factory.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "envoy/registry/registry.h" -#include "envoy/server/filter_config.h" -#include "src/envoy/tcp/mixer/control_factory.h" -#include "src/envoy/tcp/mixer/filter.h" - -using ::istio::mixer::v1::config::client::TcpClientConfig; - -namespace Envoy { -namespace Server { -namespace Configuration { - -class FilterFactory : public NamedNetworkFilterConfigFactory { - public: - Network::FilterFactoryCb createFilterFactory( - const Json::Object& config_json, FactoryContext& context) override { - TcpClientConfig config_pb; - if (!Utils::ReadV2Config(config_json, &config_pb) && - !Utils::ReadV1Config(config_json, &config_pb)) { - throw EnvoyException("Failed to parse JSON config"); - } - - return createFilterFactory(config_pb, context); - } - - Network::FilterFactoryCb createFilterFactoryFromProto( - const Protobuf::Message& config, FactoryContext& context) override { - return createFilterFactory(dynamic_cast(config), - context); - } - - ProtobufTypes::MessagePtr createEmptyConfigProto() override { - return ProtobufTypes::MessagePtr{new TcpClientConfig}; - } - - std::string name() override { return "mixer"; } - - private: - Network::FilterFactoryCb createFilterFactory(const TcpClientConfig& config_pb, - FactoryContext& context) { - std::unique_ptr config_obj( - new Tcp::Mixer::Config(config_pb)); - - auto control_factory = std::make_shared( - std::move(config_obj), context); - return [control_factory](Network::FilterManager& filter_manager) -> void { - std::shared_ptr instance = - std::make_shared(control_factory->control()); - filter_manager.addReadFilter(Network::ReadFilterSharedPtr(instance)); - filter_manager.addWriteFilter(Network::WriteFilterSharedPtr(instance)); - }; - } -}; - -static Registry::RegisterFactory - register_; - -} // namespace Configuration -} // namespace Server -} // namespace Envoy diff --git a/src/envoy/utils/BUILD b/src/envoy/utils/BUILD deleted file mode 100644 index cca27ac504b..00000000000 --- a/src/envoy/utils/BUILD +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2016 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_cc_test", -) - -envoy_cc_library( - name = "authn_lib", - srcs = [ - "authn.cc", - ], - hdrs = [ - "authn.h", - ], - repository = "@envoy", - visibility = ["//visibility:public"], - deps = [ - "//src/istio/authn:context_proto", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "utils_lib", - srcs = [ - "config.cc", - "grpc_transport.cc", - "mixer_control.cc", - "stats.cc", - "utils.cc", - ], - hdrs = [ - "config.h", - "grpc_transport.h", - "mixer_control.h", - "stats.h", - "utils.h", - ], - repository = "@envoy", - visibility = ["//visibility:public"], - deps = [ - "//external:mixer_client_config_cc_proto", - "//src/istio/mixerclient:mixerclient_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_test( - name = "authn_test", - srcs = [ - "authn_test.cc", - ], - repository = "@envoy", - deps = [ - ":authn_lib", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "utils_test", - srcs = [ - "utils_test.cc", - ], - repository = "@envoy", - deps = [ - ":utils_lib", - "@envoy//test/test_common:utility_lib", - ], -) diff --git a/src/envoy/utils/authn.cc b/src/envoy/utils/authn.cc deleted file mode 100644 index f8d496b36a5..00000000000 --- a/src/envoy/utils/authn.cc +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/authn.h" -#include "common/common/base64.h" -#include "src/istio/authn/context.pb.h" - -using istio::authn::Result; - -namespace Envoy { -namespace Utils { -namespace { - -// The HTTP header to save authentication result. -const Http::LowerCaseString kAuthenticationOutputHeaderLocation( - "sec-istio-authn-payload"); -} // namespace - -bool Authentication::SaveResultToHeader(const istio::authn::Result& result, - Http::HeaderMap* headers) { - if (HasResultInHeader(*headers)) { - ENVOY_LOG(warn, - "Authentication result already exist in header. Cannot save"); - return false; - } - - std::string payload_data; - result.SerializeToString(&payload_data); - headers->addCopy(kAuthenticationOutputHeaderLocation, - Base64::encode(payload_data.c_str(), payload_data.size())); - return true; -} - -bool Authentication::FetchResultFromHeader(const Http::HeaderMap& headers, - istio::authn::Result* result) { - const auto entry = headers.get(kAuthenticationOutputHeaderLocation); - if (entry == nullptr) { - return false; - } - std::string value(entry->value().c_str(), entry->value().size()); - return result->ParseFromString(Base64::decode(value)); -} - -void Authentication::ClearResultInHeader(Http::HeaderMap* headers) { - headers->remove(kAuthenticationOutputHeaderLocation); -} - -bool Authentication::HasResultInHeader(const Http::HeaderMap& headers) { - return headers.get(kAuthenticationOutputHeaderLocation) != nullptr; -} - -const Http::LowerCaseString& Authentication::GetHeaderLocation() { - return kAuthenticationOutputHeaderLocation; -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/authn.h b/src/envoy/utils/authn.h deleted file mode 100644 index 97138bd174a..00000000000 --- a/src/envoy/utils/authn.h +++ /dev/null @@ -1,54 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "common/common/logger.h" -#include "envoy/http/header_map.h" -#include "src/istio/authn/context.pb.h" - -namespace Envoy { -namespace Utils { - -class Authentication : public Logger::Loggable { - public: - // Saves (authentication) result to header with proper encoding (base64). The - // location is internal implementation detail, and is chosen to avoid possible - // collision. If header already contains data in that location, function will - // returns false and data is *not* overwritten. - static bool SaveResultToHeader(const istio::authn::Result& result, - Http::HeaderMap* headers); - - // Looks up authentication result data in the header. If data is available, - // decodes and output result proto. Returns false if data is not available, or - // in bad format. - static bool FetchResultFromHeader(const Http::HeaderMap& headers, - istio::authn::Result* result); - - // Clears authentication result in header, if exist. - static void ClearResultInHeader(Http::HeaderMap* headers); - - // Returns true if there is header entry at thelocation that is used to store - // authentication result. (function does not check for validity of the data - // though). - static bool HasResultInHeader(const Http::HeaderMap& headers); - - private: - // Return the header location key. For testing purpose only. - static const Http::LowerCaseString& GetHeaderLocation(); - - friend class AuthenticationTest; -}; - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/authn_test.cc b/src/envoy/utils/authn_test.cc deleted file mode 100644 index 9b640dc80af..00000000000 --- a/src/envoy/utils/authn_test.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/authn.h" -#include "common/protobuf/protobuf.h" -#include "envoy/http/header_map.h" -#include "src/istio/authn/context.pb.h" -#include "test/test_common/utility.h" - -using istio::authn::Result; - -namespace Envoy { -namespace Utils { - -class AuthenticationTest : public testing::Test { - protected: - void SetUp() override { - test_result_.set_principal("foo"); - test_result_.set_peer_user("bar"); - } - - const Http::LowerCaseString& GetHeaderLocation() { - return Authentication::GetHeaderLocation(); - } - Http::TestHeaderMapImpl request_headers_{}; - Result test_result_; -}; - -TEST_F(AuthenticationTest, SaveEmptyResult) { - EXPECT_FALSE(Authentication::HasResultInHeader(request_headers_)); - EXPECT_TRUE(Authentication::SaveResultToHeader(Result{}, &request_headers_)); - EXPECT_TRUE(Authentication::HasResultInHeader(request_headers_)); - const auto entry = request_headers_.get(GetHeaderLocation()); - EXPECT_TRUE(entry != nullptr); - EXPECT_EQ("", entry->value().getStringView()); -} - -TEST_F(AuthenticationTest, SaveSomeResult) { - EXPECT_TRUE( - Authentication::SaveResultToHeader(test_result_, &request_headers_)); - const auto entry = request_headers_.get(GetHeaderLocation()); - EXPECT_TRUE(entry != nullptr); - EXPECT_EQ("CgNmb28SA2Jhcg==", entry->value().getStringView()); -} - -TEST_F(AuthenticationTest, ResultAlreadyExist) { - request_headers_.addCopy(GetHeaderLocation(), "somedata"); - EXPECT_TRUE(Authentication::HasResultInHeader(request_headers_)); - EXPECT_FALSE(Authentication::SaveResultToHeader(Result{}, &request_headers_)); - EXPECT_TRUE(Authentication::HasResultInHeader(request_headers_)); - const auto entry = request_headers_.get(GetHeaderLocation()); - EXPECT_TRUE(entry != nullptr); - EXPECT_EQ("somedata", entry->value().getStringView()); -} - -TEST_F(AuthenticationTest, FetchResultNotExit) { - Result result; - EXPECT_FALSE( - Authentication::FetchResultFromHeader(request_headers_, &result)); -} - -TEST_F(AuthenticationTest, FetchResultBadFormat) { - request_headers_.addCopy(GetHeaderLocation(), "somedata"); - EXPECT_TRUE(Authentication::HasResultInHeader(request_headers_)); - Result result; - EXPECT_FALSE( - Authentication::FetchResultFromHeader(request_headers_, &result)); -} - -TEST_F(AuthenticationTest, FetchResult) { - EXPECT_TRUE( - Authentication::SaveResultToHeader(test_result_, &request_headers_)); - Result fetch_result; - EXPECT_TRUE( - Authentication::FetchResultFromHeader(request_headers_, &fetch_result)); - EXPECT_TRUE(TestUtility::protoEqual(test_result_, fetch_result)); -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/config.cc b/src/envoy/utils/config.cc deleted file mode 100644 index 799521ffcb4..00000000000 --- a/src/envoy/utils/config.cc +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/config.h" -#include "common/common/logger.h" -#include "google/protobuf/stubs/status.h" -#include "google/protobuf/util/json_util.h" -#include "src/envoy/utils/utils.h" - -using ::google::protobuf::Message; -using ::google::protobuf::util::Status; -using ::istio::mixer::v1::config::client::TransportConfig; - -namespace Envoy { -namespace Utils { -namespace { - -const std::string kV2Config("v2"); -const std::string kV1Config("v1"); - -// The name for the mixer server cluster. -const std::string kDefaultMixerClusterName("mixer_server"); - -// ReadConfig() finds config from |json| that matches version |config_version|, -// and parses config into |message|. Returns true if config is read and parsed -// successfully. -bool ReadConfig(const Json::Object &json, const std::string &config_version, - Message *message) { - if (!json.hasObject(config_version)) { - return false; - } - - std::string config_str = json.getObject(config_version)->asJsonString(); - Status status = ParseJsonMessage(config_str, message); - auto &logger = Logger::Registry::getLog(Logger::Id::config); - if (status.ok()) { - ENVOY_LOG_TO_LOGGER(logger, info, "{} mixer client config: {}", - config_version, message->DebugString()); - return true; - } - ENVOY_LOG_TO_LOGGER( - logger, error, - "Failed to convert mixer {} client config, error: {}, data: {}", - config_version, status.ToString(), config_str); - return false; -} - -} // namespace - -void SetDefaultMixerClusters(TransportConfig *config) { - if (config->check_cluster().empty()) { - config->set_check_cluster(kDefaultMixerClusterName); - } - if (config->report_cluster().empty()) { - config->set_report_cluster(kDefaultMixerClusterName); - } -} - -bool ReadV2Config(const Json::Object &json, Message *message) { - return ReadConfig(json, kV2Config, message); -} - -bool ReadV1Config(const Json::Object &json, Message *message) { - return ReadConfig(json, kV1Config, message); -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/config.h b/src/envoy/utils/config.h deleted file mode 100644 index e345d52edfc..00000000000 --- a/src/envoy/utils/config.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/json/json_object.h" -#include "mixer/v1/config/client/client_config.pb.h" - -namespace Envoy { -namespace Utils { - -// Set default mixer check_cluster and report_cluster -void SetDefaultMixerClusters( - ::istio::mixer::v1::config::client::TransportConfig *config); - -// Read Mixer filter v2 config. -bool ReadV2Config(const Json::Object &json, - ::google::protobuf::Message *message); - -// Read Mixer filter v1 config. -bool ReadV1Config(const Json::Object &json, - ::google::protobuf::Message *message); -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/grpc_transport.cc b/src/envoy/utils/grpc_transport.cc deleted file mode 100644 index 8c2829f13ae..00000000000 --- a/src/envoy/utils/grpc_transport.cc +++ /dev/null @@ -1,110 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/envoy/utils/grpc_transport.h" -#include "absl/types/optional.h" - -using ::google::protobuf::util::Status; -using StatusCode = ::google::protobuf::util::error::Code; - -namespace Envoy { -namespace Utils { -namespace { - -// gRPC request timeout -const std::chrono::milliseconds kGrpcRequestTimeoutMs(5000); - -} // namespace - -template -GrpcTransport::GrpcTransport( - Grpc::AsyncClientPtr async_client, const RequestType &request, - ResponseType *response, Tracing::Span &parent_span, - istio::mixerclient::DoneFunc on_done) - : async_client_(std::move(async_client)), - response_(response), - on_done_(on_done), - request_(async_client_->send( - descriptor(), request, *this, parent_span, - absl::optional(kGrpcRequestTimeoutMs))) { - ENVOY_LOG(debug, "Sending {} request: {}", descriptor().name(), - request.DebugString()); -} - -template -void GrpcTransport::onSuccess( - std::unique_ptr &&response, Tracing::Span &) { - ENVOY_LOG(debug, "{} response: {}", descriptor().name(), - response->DebugString()); - response->Swap(response_); - on_done_(Status::OK); - delete this; -} - -template -void GrpcTransport::onFailure( - Grpc::Status::GrpcStatus status, const std::string &message, - Tracing::Span &) { - ENVOY_LOG(debug, "{} failed with code: {}, {}", descriptor().name(), status, - message); - on_done_(Status(static_cast(status), message)); - delete this; -} - -template -void GrpcTransport::Cancel() { - ENVOY_LOG(debug, "Cancel gRPC request {}", descriptor().name()); - delete this; -} - -template -typename GrpcTransport::Func -GrpcTransport::GetFunc( - Grpc::AsyncClientFactory &factory, Tracing::Span &parent_span) { - return [&factory, &parent_span](const RequestType &request, - ResponseType *response, - istio::mixerclient::DoneFunc on_done) - -> istio::mixerclient::CancelFunc { - auto transport = new GrpcTransport( - factory.create(), request, response, parent_span, on_done); - return [transport]() { transport->Cancel(); }; - }; -} - -template <> -const google::protobuf::MethodDescriptor &CheckTransport::descriptor() { - static const google::protobuf::MethodDescriptor *check_descriptor = - istio::mixer::v1::Mixer::descriptor()->FindMethodByName("Check"); - ASSERT(check_descriptor); - - return *check_descriptor; -} - -template <> -const google::protobuf::MethodDescriptor &ReportTransport::descriptor() { - static const google::protobuf::MethodDescriptor *report_descriptor = - istio::mixer::v1::Mixer::descriptor()->FindMethodByName("Report"); - ASSERT(report_descriptor); - - return *report_descriptor; -} - -// explicitly instantiate CheckTransport and ReportTransport -template CheckTransport::Func CheckTransport::GetFunc( - Grpc::AsyncClientFactory &factory, Tracing::Span &parent_span); -template ReportTransport::Func ReportTransport::GetFunc( - Grpc::AsyncClientFactory &factory, Tracing::Span &parent_span); - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/grpc_transport.h b/src/envoy/utils/grpc_transport.h deleted file mode 100644 index 41bbffe5407..00000000000 --- a/src/envoy/utils/grpc_transport.h +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "common/common/logger.h" -#include "envoy/event/dispatcher.h" -#include "envoy/grpc/async_client.h" -#include "envoy/http/header_map.h" - -#include "envoy/upstream/cluster_manager.h" -#include "include/istio/mixerclient/client.h" - -namespace Envoy { -namespace Utils { - -// An object to use Envoy::Grpc::AsyncClient to make grpc call. -template -class GrpcTransport : public Grpc::TypedAsyncRequestCallbacks, - public Logger::Loggable { - public: - using Func = std::function; - - static Func GetFunc(Grpc::AsyncClientFactory& factory, - Tracing::Span& parent_span); - - GrpcTransport(Grpc::AsyncClientPtr async_client, const RequestType& request, - ResponseType* response, Tracing::Span& parent_span, - istio::mixerclient::DoneFunc on_done); - - void onCreateInitialMetadata(Http::HeaderMap& metadata) override { - // We generate cluster name contains invalid characters, so override the - // authority header temorarily until it can be specified via CDS. - // See https://github.com/envoyproxy/envoy/issues/3297 for details. - metadata.Host()->value("mixer", 5); - } - - void onSuccess(std::unique_ptr&& response, - Tracing::Span& span) override; - - void onFailure(Grpc::Status::GrpcStatus status, const std::string& message, - Tracing::Span& span) override; - - void Cancel(); - - private: - static const google::protobuf::MethodDescriptor& descriptor(); - - Grpc::AsyncClientPtr async_client_; - ResponseType* response_; - ::istio::mixerclient::DoneFunc on_done_; - Grpc::AsyncRequest* request_{}; -}; - -typedef GrpcTransport - CheckTransport; - -typedef GrpcTransport - ReportTransport; - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/mixer_control.cc b/src/envoy/utils/mixer_control.cc deleted file mode 100644 index 082dde704b5..00000000000 --- a/src/envoy/utils/mixer_control.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/mixer_control.h" -#include "src/envoy/utils/grpc_transport.h" - -using ::istio::mixerclient::Statistics; - -namespace Envoy { -namespace Utils { -namespace { - -// A class to wrap envoy timer for mixer client timer. -class EnvoyTimer : public ::istio::mixerclient::Timer { - public: - EnvoyTimer(Event::TimerPtr timer) : timer_(std::move(timer)) {} - - void Stop() override { timer_->disableTimer(); } - void Start(int interval_ms) override { - timer_->enableTimer(std::chrono::milliseconds(interval_ms)); - } - - private: - Event::TimerPtr timer_; -}; - -// Fork of Envoy::Grpc::AsyncClientFactoryImpl, workaround for -// https://github.com/envoyproxy/envoy/issues/2762 -class EnvoyGrpcAsyncClientFactory : public Grpc::AsyncClientFactory { - public: - EnvoyGrpcAsyncClientFactory(Upstream::ClusterManager &cm, - envoy::api::v2::core::GrpcService config) - : cm_(cm), config_(config) {} - - Grpc::AsyncClientPtr create() override { - return std::make_unique(cm_, config_); - } - - private: - Upstream::ClusterManager &cm_; - envoy::api::v2::core::GrpcService config_; -}; - -} // namespace - -// Create all environment functions for mixerclient -void CreateEnvironment(Event::Dispatcher &dispatcher, - Runtime::RandomGenerator &random, - Grpc::AsyncClientFactory &check_client_factory, - Grpc::AsyncClientFactory &report_client_factory, - ::istio::mixerclient::Environment *env) { - env->check_transport = CheckTransport::GetFunc(check_client_factory, - Tracing::NullSpan::instance()); - env->report_transport = ReportTransport::GetFunc( - report_client_factory, Tracing::NullSpan::instance()); - - env->timer_create_func = [&dispatcher](std::function timer_cb) - -> std::unique_ptr<::istio::mixerclient::Timer> { - return std::unique_ptr<::istio::mixerclient::Timer>( - new EnvoyTimer(dispatcher.createTimer(timer_cb))); - }; - - env->uuid_generate_func = [&random]() -> std::string { - return random.uuid(); - }; -} - -Grpc::AsyncClientFactoryPtr GrpcClientFactoryForCluster( - const std::string &cluster_name, Upstream::ClusterManager &cm, - Stats::Scope &scope) { - envoy::api::v2::core::GrpcService service; - service.mutable_envoy_grpc()->set_cluster_name(cluster_name); - - // Workaround for https://github.com/envoyproxy/envoy/issues/2762 - UNREFERENCED_PARAMETER(scope); - return std::make_unique(cm, service); -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/mixer_control.h b/src/envoy/utils/mixer_control.h deleted file mode 100644 index 3f6a8730d97..00000000000 --- a/src/envoy/utils/mixer_control.h +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/event/dispatcher.h" -#include "envoy/runtime/runtime.h" -#include "envoy/upstream/cluster_manager.h" -#include "include/istio/mixerclient/client.h" -#include "src/envoy/utils/config.h" - -namespace Envoy { -namespace Utils { - -// Create all environment functions for mixerclient -void CreateEnvironment(Event::Dispatcher &dispatcher, - Runtime::RandomGenerator &random, - Grpc::AsyncClientFactory &check_client_factory, - Grpc::AsyncClientFactory &report_client_factory, - ::istio::mixerclient::Environment *env); - -Grpc::AsyncClientFactoryPtr GrpcClientFactoryForCluster( - const std::string &cluster_name, Upstream::ClusterManager &cm, - Stats::Scope &scope); - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/stats.cc b/src/envoy/utils/stats.cc deleted file mode 100644 index cb1211c5c34..00000000000 --- a/src/envoy/utils/stats.cc +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "src/envoy/utils/stats.h" - -namespace Envoy { -namespace Utils { -namespace { - -// The time interval for envoy stats update. -const int kStatsUpdateIntervalInMs = 10000; - -} // namespace - -MixerStatsObject::MixerStatsObject(Event::Dispatcher& dispatcher, - MixerFilterStats& stats, - ::google::protobuf::Duration update_interval, - GetStatsFunc func) - : stats_(stats), get_stats_func_(func) { - stats_update_interval_ = - update_interval.seconds() * 1000 + update_interval.nanos() / 1000000; - if (stats_update_interval_ <= 0) { - stats_update_interval_ = kStatsUpdateIntervalInMs; - } - memset(&old_stats_, 0, sizeof(old_stats_)); - - if (get_stats_func_) { - timer_ = dispatcher.createTimer([this]() { OnTimer(); }); - timer_->enableTimer(std::chrono::milliseconds(stats_update_interval_)); - } -} - -void MixerStatsObject::OnTimer() { - ::istio::mixerclient::Statistics new_stats; - bool get_stats = get_stats_func_(&new_stats); - if (get_stats) { - CheckAndUpdateStats(new_stats); - } - timer_->enableTimer(std::chrono::milliseconds(stats_update_interval_)); -} - -void MixerStatsObject::CheckAndUpdateStats( - const ::istio::mixerclient::Statistics& new_stats) { - if (new_stats.total_check_calls > old_stats_.total_check_calls) { - stats_.total_check_calls_.add(new_stats.total_check_calls - - old_stats_.total_check_calls); - } - if (new_stats.total_remote_check_calls > - old_stats_.total_remote_check_calls) { - stats_.total_remote_check_calls_.add(new_stats.total_remote_check_calls - - old_stats_.total_remote_check_calls); - } - if (new_stats.total_blocking_remote_check_calls > - old_stats_.total_blocking_remote_check_calls) { - stats_.total_blocking_remote_check_calls_.add( - new_stats.total_blocking_remote_check_calls - - old_stats_.total_blocking_remote_check_calls); - } - if (new_stats.total_quota_calls > old_stats_.total_quota_calls) { - stats_.total_quota_calls_.add(new_stats.total_quota_calls - - old_stats_.total_quota_calls); - } - if (new_stats.total_remote_quota_calls > - old_stats_.total_remote_quota_calls) { - stats_.total_remote_quota_calls_.add(new_stats.total_remote_quota_calls - - old_stats_.total_remote_quota_calls); - } - if (new_stats.total_blocking_remote_quota_calls > - old_stats_.total_blocking_remote_quota_calls) { - stats_.total_blocking_remote_quota_calls_.add( - new_stats.total_blocking_remote_quota_calls - - old_stats_.total_blocking_remote_quota_calls); - } - if (new_stats.total_report_calls > old_stats_.total_report_calls) { - stats_.total_report_calls_.add(new_stats.total_report_calls - - old_stats_.total_report_calls); - } - if (new_stats.total_remote_report_calls > - old_stats_.total_remote_report_calls) { - stats_.total_remote_report_calls_.add(new_stats.total_remote_report_calls - - old_stats_.total_remote_report_calls); - } - - // Copy new_stats to old_stats_ for next stats update. - old_stats_ = new_stats; -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/stats.h b/src/envoy/utils/stats.h deleted file mode 100644 index 7b7e63ff712..00000000000 --- a/src/envoy/utils/stats.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "envoy/event/dispatcher.h" -#include "envoy/event/timer.h" -#include "envoy/stats/stats_macros.h" -#include "include/istio/mixerclient/client.h" - -namespace Envoy { -namespace Utils { - -/** - * All mixer filter stats. @see stats_macros.h - */ -// clang-format off -#define ALL_MIXER_FILTER_STATS(COUNTER) \ - COUNTER(total_check_calls) \ - COUNTER(total_remote_check_calls) \ - COUNTER(total_blocking_remote_check_calls) \ - COUNTER(total_quota_calls) \ - COUNTER(total_remote_quota_calls) \ - COUNTER(total_blocking_remote_quota_calls) \ - COUNTER(total_report_calls) \ - COUNTER(total_remote_report_calls) -// clang-format on - -/** - * Struct definition for all mixer filter stats. @see stats_macros.h - */ -struct MixerFilterStats { - ALL_MIXER_FILTER_STATS(GENERATE_COUNTER_STRUCT) -}; - -typedef std::function GetStatsFunc; - -// MixerStatsObject maintains statistics for number of check, quota and report -// calls issued by a mixer filter. -class MixerStatsObject { - public: - MixerStatsObject(Event::Dispatcher& dispatcher, MixerFilterStats& stats, - ::google::protobuf::Duration update_interval, - GetStatsFunc func); - - private: - // This function is invoked when timer event fires. - void OnTimer(); - - // Compares old stats with new stats and updates envoy stats. - void CheckAndUpdateStats(const ::istio::mixerclient::Statistics& new_stats); - - // A set of Envoy stats for the number of check, quota and report calls. - MixerFilterStats& stats_; - // Stores a function which gets statistics from mixer controller. - GetStatsFunc get_stats_func_; - - // stats from last call to get_stats_func_. This is needed to calculate the - // variances of stats and update envoy stats. - ::istio::mixerclient::Statistics old_stats_; - - // These members are used for creating a timer which update Envoy stats - // periodically. - ::Envoy::Event::TimerPtr timer_; - - // Time interval at which Envoy stats get updated. If stats update interval - // from config is larger than 0, then store configured interval here. - // Otherwise, set interval to 10 seconds. - int stats_update_interval_; -}; - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/utils.cc b/src/envoy/utils/utils.cc deleted file mode 100644 index 79bf1174e29..00000000000 --- a/src/envoy/utils/utils.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/utils.h" -#include "mixer/v1/attributes.pb.h" - -using ::google::protobuf::Message; -using ::google::protobuf::util::Status; - -namespace Envoy { -namespace Utils { - -namespace { - -const std::string kSPIFFEPrefix("spiffe://"); - -} // namespace - -std::map ExtractHeaders( - const Http::HeaderMap& header_map, - const std::set& exclusives) { - std::map headers; - struct Context { - Context(const std::set& exclusives, - std::map& headers) - : exclusives(exclusives), headers(headers) {} - const std::set& exclusives; - std::map& headers; - }; - Context ctx(exclusives, headers); - header_map.iterate( - [](const Http::HeaderEntry& header, - void* context) -> Http::HeaderMap::Iterate { - Context* ctx = static_cast(context); - if (ctx->exclusives.count(header.key().c_str()) == 0) { - ctx->headers[header.key().c_str()] = header.value().c_str(); - } - return Http::HeaderMap::Iterate::Continue; - }, - &ctx); - return headers; -} - -bool GetIpPort(const Network::Address::Ip* ip, std::string* str_ip, int* port) { - if (ip) { - *port = ip->port(); - if (ip->ipv4()) { - uint32_t ipv4 = ip->ipv4()->address(); - *str_ip = std::string(reinterpret_cast(&ipv4), sizeof(ipv4)); - return true; - } - if (ip->ipv6()) { - absl::uint128 ipv6 = ip->ipv6()->address(); - *str_ip = std::string(reinterpret_cast(&ipv6), 16); - return true; - } - } - return false; -} - -bool GetSourceUser(const Network::Connection* connection, std::string* user) { - if (connection) { - Ssl::Connection* ssl = const_cast(connection->ssl()); - if (ssl != nullptr) { - std::string result = ssl->uriSanPeerCertificate(); - if (result.empty()) { // empty source user is not allowed - return false; - } - if (result.length() >= kSPIFFEPrefix.length() && - result.compare(0, kSPIFFEPrefix.length(), kSPIFFEPrefix) == 0) { - // Strip out the prefix "spiffe://" in the identity. - *user = result.substr(kSPIFFEPrefix.size()); - } else { - *user = result; - } - return true; - } - } - return false; -} - -bool IsMutualTLS(const Network::Connection* connection) { - return connection != nullptr && connection->ssl() != nullptr && - connection->ssl()->peerCertificatePresented(); -} - -Status ParseJsonMessage(const std::string& json, Message* output) { - ::google::protobuf::util::JsonParseOptions options; - options.ignore_unknown_fields = true; - return ::google::protobuf::util::JsonStringToMessage(json, output, options); -} - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/utils.h b/src/envoy/utils/utils.h deleted file mode 100644 index 02fa92d44ec..00000000000 --- a/src/envoy/utils/utils.h +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include "envoy/http/header_map.h" -#include "envoy/network/connection.h" -#include "google/protobuf/util/json_util.h" - -namespace Envoy { -namespace Utils { - -// Extract HTTP headers into a string map -std::map ExtractHeaders( - const Http::HeaderMap& header_map, const std::set& exclusives); - -// Get ip and port from Envoy ip. -bool GetIpPort(const Network::Address::Ip* ip, std::string* str_ip, int* port); - -// Get user id from ssl. -bool GetSourceUser(const Network::Connection* connection, std::string* user); - -// Returns true if connection is mutual TLS enabled. -bool IsMutualTLS(const Network::Connection* connection); - -// Parse JSON string into message. -::google::protobuf::util::Status ParseJsonMessage( - const std::string& json, ::google::protobuf::Message* output); - -} // namespace Utils -} // namespace Envoy diff --git a/src/envoy/utils/utils_test.cc b/src/envoy/utils/utils_test.cc deleted file mode 100644 index 8d38b995e77..00000000000 --- a/src/envoy/utils/utils_test.cc +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/envoy/utils/utils.h" -#include "mixer/v1/config/client/client_config.pb.h" -#include "test/test_common/utility.h" - -using Envoy::Utils::ParseJsonMessage; - -namespace { - -TEST(UtilsTest, ParseNormalMessage) { - std::string config_str = R"({ - "default_destination_service": "service.svc.cluster.local", - })"; - ::istio::mixer::v1::config::client::HttpClientConfig http_config; - - auto status = ParseJsonMessage(config_str, &http_config); - EXPECT_OK(status) << status; - EXPECT_EQ(http_config.default_destination_service(), - "service.svc.cluster.local"); -} - -TEST(UtilsTest, ParseMessageWithUnknownField) { - std::string config_str = R"({ - "default_destination_service": "service.svc.cluster.local", - "unknown_field": "xxx", - })"; - ::istio::mixer::v1::config::client::HttpClientConfig http_config; - - EXPECT_OK(ParseJsonMessage(config_str, &http_config)); - EXPECT_EQ(http_config.default_destination_service(), - "service.svc.cluster.local"); -} -} // namespace diff --git a/src/istio/api_spec/BUILD b/src/istio/api_spec/BUILD deleted file mode 100644 index e64c4ecb9b2..00000000000 --- a/src/istio/api_spec/BUILD +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "api_spec_lib", - srcs = [ - "http_api_spec_parser_impl.cc", - "http_api_spec_parser_impl.h", - "http_template.cc", - "http_template.h", - "path_matcher.h", - "path_matcher_node.cc", - "path_matcher_node.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//external:mixer_client_config_cc_proto", - "//include/istio/api_spec:headers_lib", - "//include/istio/control/http:headers_lib", - ], -) - -cc_test( - name = "path_matcher_test", - size = "small", - srcs = ["path_matcher_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":api_spec_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "http_template_test", - size = "small", - srcs = ["http_template_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":api_spec_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "http_api_spec_parser_test", - size = "small", - srcs = ["http_api_spec_parser_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":api_spec_lib", - "//external:googletest_main", - "//src/istio/control/http:mock_headers", - "//src/istio/mixerclient:mixerclient_lib", - ], -) diff --git a/src/istio/api_spec/http_api_spec_parser_impl.cc b/src/istio/api_spec/http_api_spec_parser_impl.cc deleted file mode 100644 index de1a2abf637..00000000000 --- a/src/istio/api_spec/http_api_spec_parser_impl.cc +++ /dev/null @@ -1,121 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/api_spec/http_api_spec_parser_impl.h" -#include "google/protobuf/stubs/logging.h" - -#include -#include - -using ::istio::control::http::CheckData; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::config::client::APIKey; -using ::istio::mixer::v1::config::client::HTTPAPISpec; -using ::istio::mixer::v1::config::client::HTTPAPISpecPattern; - -namespace istio { -namespace api_spec { -namespace { -// If api-key is not defined in APISpec, use following defaults. -const std::string kApiKeyDefaultQueryName1("key"); -const std::string kApiKeyDefaultQueryName2("api_key"); -const std::string kApiKeyDefaultHeader("x-api-key"); -} // namespace - -HttpApiSpecParserImpl::HttpApiSpecParserImpl(const HTTPAPISpec& api_spec) - : api_spec_(api_spec) { - BuildPathMatcher(); - BuildApiKeyData(); -} - -void HttpApiSpecParserImpl::BuildPathMatcher() { - PathMatcherBuilder pmb; - for (const auto& pattern : api_spec_.patterns()) { - if (pattern.pattern_case() == HTTPAPISpecPattern::kUriTemplate) { - if (!pmb.Register(pattern.http_method(), pattern.uri_template(), - std::string(), &pattern.attributes())) { - GOOGLE_LOG(WARNING) - << "Invalid uri_template: " << pattern.uri_template(); - } - } else { - regex_list_.emplace_back(pattern.regex(), pattern.http_method(), - &pattern.attributes()); - } - } - path_matcher_ = pmb.Build(); -} - -void HttpApiSpecParserImpl::BuildApiKeyData() { - if (api_spec_.api_keys_size() == 0) { - api_spec_.add_api_keys()->set_query(kApiKeyDefaultQueryName1); - api_spec_.add_api_keys()->set_query(kApiKeyDefaultQueryName2); - api_spec_.add_api_keys()->set_header(kApiKeyDefaultHeader); - } -} - -void HttpApiSpecParserImpl::AddAttributes( - const std::string& http_method, const std::string& path, - ::istio::mixer::v1::Attributes* attributes) { - // Add the global attributes. - attributes->MergeFrom(api_spec_.attributes()); - - const Attributes* matched_attributes = - path_matcher_->Lookup(http_method, path); - if (matched_attributes) { - attributes->MergeFrom(*matched_attributes); - } - - // Check regex list - for (const auto& re : regex_list_) { - if (re.http_method == http_method && std::regex_match(path, re.regex)) { - attributes->MergeFrom(*re.attributes); - } - } -} - -bool HttpApiSpecParserImpl::ExtractApiKey(CheckData* check_data, - std::string* value) { - for (const auto& api_key : api_spec_.api_keys()) { - switch (api_key.key_case()) { - case APIKey::kQuery: - if (check_data->FindQueryParameter(api_key.query(), value)) { - return true; - } - break; - case APIKey::kHeader: - if (check_data->FindHeaderByName(api_key.header(), value)) { - return true; - } - break; - case APIKey::kCookie: - if (check_data->FindCookie(api_key.cookie(), value)) { - return true; - } - break; - case APIKey::KEY_NOT_SET: - break; - } - } - return false; -} - -std::unique_ptr HttpApiSpecParser::Create( - const ::istio::mixer::v1::config::client::HTTPAPISpec& api_spec) { - return std::unique_ptr( - new HttpApiSpecParserImpl(api_spec)); -} - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/api_spec/http_api_spec_parser_impl.h b/src/istio/api_spec/http_api_spec_parser_impl.h deleted file mode 100644 index 223c9e7379c..00000000000 --- a/src/istio/api_spec/http_api_spec_parser_impl.h +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_API_SPEC_HTTP_ISTIO_API_SPEC_PARSER_IMPL_H_ -#define ISTIO_API_SPEC_HTTP_ISTIO_API_SPEC_PARSER_IMPL_H_ - -#include "include/istio/api_spec/http_api_spec_parser.h" -#include "src/istio/api_spec/path_matcher.h" - -#include -#include - -namespace istio { -namespace api_spec { - -// The implementation class for HttpApiSpecParser interface. -class HttpApiSpecParserImpl : public HttpApiSpecParser { - public: - HttpApiSpecParserImpl( - const ::istio::mixer::v1::config::client::HTTPAPISpec& api_spec); - - void AddAttributes(const std::string& http_method, const std::string& path, - ::istio::mixer::v1::Attributes* attributes) override; - - virtual bool ExtractApiKey(::istio::control::http::CheckData* check_data, - std::string* api_key) override; - - private: - // Build PatchMatcher for extracting api attributes. - void BuildPathMatcher(); - // Build Api key extraction used data. - void BuildApiKeyData(); - - // The http api spec. - ::istio::mixer::v1::config::client::HTTPAPISpec api_spec_; - - // The path matcher for all url templates - PathMatcherPtr path_matcher_; - - struct RegexData { - RegexData(const std::string& regex, const std::string& http_method, - const ::istio::mixer::v1::Attributes* attributes) - : regex(regex), http_method(http_method), attributes(attributes) {} - - std::regex regex; - std::string http_method; - // The attributes to add if matched. - const ::istio::mixer::v1::Attributes* attributes; - }; - std::vector regex_list_; -}; - -} // namespace api_spec -} // namespace istio - -#endif // ISTIO_API_SPEC_HTTP_ISTIO_API_SPEC_PARSER_IMPL_H_ diff --git a/src/istio/api_spec/http_api_spec_parser_test.cc b/src/istio/api_spec/http_api_spec_parser_test.cc deleted file mode 100644 index ff7a409a066..00000000000 --- a/src/istio/api_spec/http_api_spec_parser_test.cc +++ /dev/null @@ -1,234 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/api_spec/http_api_spec_parser.h" -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/message_differencer.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/control/http/mock_check_data.h" - -using ::google::protobuf::TextFormat; -using ::google::protobuf::util::MessageDifferencer; -using ::istio::control::http::MockCheckData; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::config::client::HTTPAPISpec; -using ::istio::utils::AttributesBuilder; - -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace api_spec { - -const char kSpec[] = R"( -attributes { - attributes { - key: "key0" - value { - string_value: "value0" - } - } -} -patterns { - attributes { - attributes { - key: "key1" - value { - string_value: "value1" - } - } - } - http_method: "GET" - uri_template: "/books/{id=*}" -} -patterns { - attributes { - attributes { - key: "key2" - value { - string_value: "value2" - } - } - } - http_method: "GET" - regex: "/books/.*" -} -)"; - -const char kResult[] = R"( -attributes { - key: "key0" - value { - string_value: "value0" - } -} -attributes { - key: "key1" - value { - string_value: "value1" - } -} -attributes { - key: "key2" - value { - string_value: "value2" - } -} -)"; - -TEST(HttpApiSpecParserTest, TestEmptySpec) { - HTTPAPISpec spec; - auto parser = HttpApiSpecParser::Create(spec); - - Attributes attributes; - parser->AddAttributes("GET", "/books", &attributes); - EXPECT_TRUE(attributes.attributes().size() == 0); -} - -TEST(HttpApiSpecParserTest, TestBoth) { - HTTPAPISpec spec; - ASSERT_TRUE(TextFormat::ParseFromString(kSpec, &spec)); - auto parser = HttpApiSpecParser::Create(spec); - - Attributes attributes; - parser->AddAttributes("GET", "/books/10", &attributes); - - Attributes expected; - ASSERT_TRUE(TextFormat::ParseFromString(kResult, &expected)); - EXPECT_TRUE(MessageDifferencer::Equals(attributes, expected)); -} - -TEST(HttpApiSpecParserTest, TestDefaultApiKey) { - HTTPAPISpec spec; - auto parser = HttpApiSpecParser::Create(spec); - - // Failed - ::testing::NiceMock mock_data0; - std::string api_key0; - EXPECT_FALSE(parser->ExtractApiKey(&mock_data0, &api_key0)); - - // "key" query - ::testing::NiceMock mock_data1; - EXPECT_CALL(mock_data1, FindQueryParameter(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "key") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key1; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data1, &api_key1)); - EXPECT_EQ(api_key1, "this-is-a-test-api-key"); - - // "api_key" query - ::testing::NiceMock mock_data2; - EXPECT_CALL(mock_data2, FindQueryParameter(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "api_key") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key2; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data2, &api_key2)); - EXPECT_EQ(api_key2, "this-is-a-test-api-key"); - - // "x-api-key" header - ::testing::NiceMock mock_data3; - EXPECT_CALL(mock_data3, FindHeaderByName(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "x-api-key") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key3; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data3, &api_key3)); - EXPECT_EQ(api_key3, "this-is-a-test-api-key"); -} - -TEST(HttpApiSpecParserTest, TestCustomApiKey) { - HTTPAPISpec spec; - spec.add_api_keys()->set_query("api_key_query"); - spec.add_api_keys()->set_header("Api-Key-Header"); - spec.add_api_keys()->set_cookie("Api-Key-Cookie"); - auto parser = HttpApiSpecParser::Create(spec); - - // Failed - ::testing::NiceMock mock_data0; - std::string api_key0; - EXPECT_FALSE(parser->ExtractApiKey(&mock_data0, &api_key0)); - - // "api_key_query" - ::testing::NiceMock mock_data1; - EXPECT_CALL(mock_data1, FindQueryParameter(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "api_key_query") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key1; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data1, &api_key1)); - EXPECT_EQ(api_key1, "this-is-a-test-api-key"); - - // "api-key-header" header - ::testing::NiceMock mock_data2; - EXPECT_CALL(mock_data2, FindHeaderByName(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "Api-Key-Header") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key2; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data2, &api_key2)); - EXPECT_EQ(api_key2, "this-is-a-test-api-key"); - - // "Api-Key-Cookie" cookie - ::testing::NiceMock mock_data3; - EXPECT_CALL(mock_data3, FindCookie(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "Api-Key-Cookie") { - *value = "this-is-a-test-api-key"; - return true; - } - return false; - })); - - std::string api_key3; - EXPECT_TRUE(parser->ExtractApiKey(&mock_data3, &api_key3)); - EXPECT_EQ(api_key3, "this-is-a-test-api-key"); -} - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/api_spec/http_template.cc b/src/istio/api_spec/http_template.cc deleted file mode 100644 index 834045134e9..00000000000 --- a/src/istio/api_spec/http_template.cc +++ /dev/null @@ -1,379 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "src/istio/api_spec/http_template.h" - -namespace istio { -namespace api_spec { - -namespace { - -// TODO: implement an error sink. - -// HTTP Template Grammar: -// Questions: -// - what are the constraints on LITERAL and IDENT? -// - what is the character set for the grammar? -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -class Parser { - public: - Parser(const std::string &input) - : input_(input), tb_(0), te_(0), in_variable_(false) {} - - bool Parse() { - if (!ParseTemplate() || !ConsumedAllInput()) { - return false; - } - PostProcessVariables(); - return true; - } - - std::vector &segments() { return segments_; } - std::string &verb() { return verb_; } - std::vector &variables() { return variables_; } - - // only constant path segments are allowed after '**'. - bool ValidateParts() { - bool found_wild_card = false; - for (size_t i = 0; i < segments_.size(); i++) { - if (!found_wild_card) { - if (segments_[i] == HttpTemplate::kWildCardPathKey) { - found_wild_card = true; - } - } else if (segments_[i] == HttpTemplate::kSingleParameterKey || - segments_[i] == HttpTemplate::kWildCardPathPartKey || - segments_[i] == HttpTemplate::kWildCardPathKey) { - return false; - } - } - return true; - } - - private: - // Template = "/" Segments [ Verb ] ; - bool ParseTemplate() { - if (!Consume('/')) { - // Expected '/' - return false; - } - if (!ParseSegments()) { - return false; - } - - if (EnsureCurrent() && current_char() == ':') { - if (!ParseVerb()) { - return false; - } - } - return true; - } - - // Segments = Segment { "/" Segment } ; - bool ParseSegments() { - if (!ParseSegment()) { - return false; - } - - for (;;) { - if (!Consume('/')) break; - if (!ParseSegment()) { - return false; - } - } - - return true; - } - - // Segment = "*" | "**" | LITERAL | Variable ; - bool ParseSegment() { - if (!EnsureCurrent()) { - return false; - } - switch (current_char()) { - case '*': { - Consume('*'); - if (Consume('*')) { - // ** - segments_.push_back("**"); - if (in_variable_) { - return MarkVariableHasWildCardPath(); - } - return true; - } else { - segments_.push_back("*"); - return true; - } - } - - case '{': - return ParseVariable(); - default: - return ParseLiteralSegment(); - } - } - - // Variable = "{" FieldPath [ "=" Segments ] "}" ; - bool ParseVariable() { - if (!Consume('{')) { - return false; - } - if (!StartVariable()) { - return false; - } - if (!ParseFieldPath()) { - return false; - } - if (Consume('=')) { - if (!ParseSegments()) { - return false; - } - } else { - // {field_path} is equivalent to {field_path=*} - segments_.push_back("*"); - } - if (!EndVariable()) { - return false; - } - if (!Consume('}')) { - return false; - } - return true; - } - - bool ParseLiteralSegment() { - std::string ls; - if (!ParseLiteral(&ls)) { - return false; - } - segments_.push_back(ls); - return true; - } - - // FieldPath = IDENT { "." IDENT } ; - bool ParseFieldPath() { - if (!ParseIdentifier()) { - return false; - } - while (Consume('.')) { - if (!ParseIdentifier()) { - return false; - } - } - return true; - } - - // Verb = ":" LITERAL ; - bool ParseVerb() { - if (!Consume(':')) return false; - if (!ParseLiteral(&verb_)) return false; - return true; - } - - bool ParseIdentifier() { - std::string idf; - - // Initialize to false to handle empty literal. - bool result = false; - - while (NextChar()) { - char c; - switch (c = current_char()) { - case '.': - case '}': - case '=': - return result && AddFieldIdentifier(std::move(idf)); - default: - Consume(c); - idf.push_back(c); - break; - } - result = true; - } - return result && AddFieldIdentifier(std::move(idf)); - } - - bool ParseLiteral(std::string *lit) { - if (!EnsureCurrent()) { - return false; - } - - // Initialize to false in case we encounter an empty literal. - bool result = false; - - for (;;) { - char c; - switch (c = current_char()) { - case '/': - case ':': - case '}': - return result; - default: - Consume(c); - lit->push_back(c); - break; - } - - result = true; - - if (!NextChar()) { - break; - } - } - return result; - } - - bool Consume(char c) { - if (tb_ >= te_ && !NextChar()) { - return false; - } - if (current_char() != c) { - return false; - } - tb_++; - return true; - } - - bool ConsumedAllInput() { return tb_ >= input_.size(); } - - bool EnsureCurrent() { return tb_ < te_ || NextChar(); } - - bool NextChar() { - if (te_ < input_.size()) { - te_++; - return true; - } else { - return false; - } - } - - // Returns the character looked at. - char current_char() const { - return tb_ < te_ && te_ <= input_.size() ? input_[te_ - 1] : -1; - } - - HttpTemplate::Variable &CurrentVariable() { return variables_.back(); } - - bool StartVariable() { - if (!in_variable_) { - variables_.push_back(HttpTemplate::Variable{}); - CurrentVariable().start_segment = segments_.size(); - CurrentVariable().has_wildcard_path = false; - in_variable_ = true; - return true; - } else { - // nested variables are not allowed - return false; - } - } - - bool EndVariable() { - if (in_variable_ && !variables_.empty()) { - CurrentVariable().end_segment = segments_.size(); - in_variable_ = false; - return ValidateVariable(CurrentVariable()); - } else { - // something's wrong we're not in a variable - return false; - } - } - - bool AddFieldIdentifier(std::string id) { - if (in_variable_ && !variables_.empty()) { - CurrentVariable().field_path.emplace_back(std::move(id)); - return true; - } else { - // something's wrong we're not in a variable - return false; - } - } - - bool MarkVariableHasWildCardPath() { - if (in_variable_ && !variables_.empty()) { - CurrentVariable().has_wildcard_path = true; - return true; - } else { - // something's wrong we're not in a variable - return false; - } - } - - bool ValidateVariable(const HttpTemplate::Variable &var) { - return !var.field_path.empty() && (var.start_segment < var.end_segment) && - (var.end_segment <= static_cast(segments_.size())); - } - - void PostProcessVariables() { - for (auto &var : variables_) { - if (var.has_wildcard_path) { - // if the variable contains a '**', store the end_positon - // relative to the end, such that -1 corresponds to the end - // of the path. As we only support fixed path after '**', - // this will allow the matcher code to reconstruct the variable - // value based on the url segments. - var.end_segment = (var.end_segment - segments_.size() - 1); - - if (!verb_.empty()) { - // a custom verb will add an additional segment, so - // the end_postion needs a -1 - --var.end_segment; - } - } - } - } - - const std::string &input_; - - // Token delimiter indexes - size_t tb_; - size_t te_; - - // are we in nested Segments of a variable? - bool in_variable_; - - std::vector segments_; - std::string verb_; - std::vector variables_; -}; - -} // namespace - -const char HttpTemplate::kSingleParameterKey[] = "/."; - -const char HttpTemplate::kWildCardPathPartKey[] = "*"; - -const char HttpTemplate::kWildCardPathKey[] = "**"; - -std::unique_ptr HttpTemplate::Parse(const std::string &ht) { - Parser p(ht); - if (!p.Parse() || !p.ValidateParts()) { - return nullptr; - } - - return std::unique_ptr(new HttpTemplate( - std::move(p.segments()), std::move(p.verb()), std::move(p.variables()))); -} - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/api_spec/http_template.h b/src/istio/api_spec/http_template.h deleted file mode 100644 index b6c08ea153b..00000000000 --- a/src/istio/api_spec/http_template.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_API_SPEC_HTTP_TEMPLATE_H_ -#define ISTIO_API_SPEC_HTTP_TEMPLATE_H_ - -#include -#include -#include - -namespace istio { -namespace api_spec { - -class HttpTemplate { - public: - static std::unique_ptr Parse(const std::string &ht); - const std::vector &segments() const { return segments_; } - const std::string &verb() const { return verb_; } - - // The info about a variable binding {variable=subpath} in the template. - struct Variable { - // Specifies the range of segments [start_segment, end_segment) the - // variable binds to. Both start_segment and end_segment are 0 based. - // end_segment can also be negative, which means that the position is - // specified relative to the end such that -1 corresponds to the end - // of the path. - int start_segment; - int end_segment; - - // The path of the protobuf field the variable binds to. - std::vector field_path; - - // Do we have a ** in the variable template? - bool has_wildcard_path; - }; - - std::vector &Variables() { return variables_; } - - // '/.': match any single path segment. - static const char kSingleParameterKey[]; - // '*': Wildcard match for one path segment. - static const char kWildCardPathPartKey[]; - // '**': Wildcard match the remaining path. - static const char kWildCardPathKey[]; - - private: - HttpTemplate(std::vector &&segments, std::string &&verb, - std::vector &&variables) - : segments_(std::move(segments)), - verb_(std::move(verb)), - variables_(std::move(variables)) {} - const std::vector segments_; - std::string verb_; - std::vector variables_; -}; - -} // namespace api_spec -} // namespace istio - -#endif // ISTIO_API_SPEC_HTTP_TEMPLATE_H_ diff --git a/src/istio/api_spec/http_template_test.cc b/src/istio/api_spec/http_template_test.cc deleted file mode 100644 index 94109c5102a..00000000000 --- a/src/istio/api_spec/http_template_test.cc +++ /dev/null @@ -1,511 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/api_spec/http_template.h" -#include "gtest/gtest.h" - -#include -#include -#include - -namespace istio { -namespace api_spec { - -typedef std::vector Segments; -typedef HttpTemplate::Variable Variable; -typedef std::vector Variables; -typedef std::vector FieldPath; - -bool operator==(const Variable &v1, const Variable &v2) { - return v1.field_path == v2.field_path && - v1.start_segment == v2.start_segment && - v1.end_segment == v2.end_segment && - v1.has_wildcard_path == v2.has_wildcard_path; -} - -std::string FieldPathToString(const FieldPath &fp) { - std::string s; - for (const auto &f : fp) { - if (!s.empty()) { - s += "."; - } - s += f; - } - return s; -} - -std::ostream &operator<<(std::ostream &os, const Variable &var) { - return os << "{ " << FieldPathToString(var.field_path) << ", [" - << var.start_segment << ", " << var.end_segment << "), " - << var.has_wildcard_path << "}"; -} - -std::ostream &operator<<(std::ostream &os, const Variables &vars) { - for (const auto &var : vars) { - os << var << std::endl; - } - return os; -} - -TEST(HttpTemplate, ParseTest1) { - auto ht = HttpTemplate::Parse("/shelves/{shelf}/books/{book}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"shelves", "*", "books", "*"}), ht->segments()); - ASSERT_EQ(Variables({ - Variable{1, 2, FieldPath{"shelf"}, false}, - Variable{3, 4, FieldPath{"book"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest2) { - auto ht = HttpTemplate::Parse("/shelves/**"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"shelves", "**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({}), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest3) { - auto ht = HttpTemplate::Parse("/**"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables(), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest4a) { - auto ht = HttpTemplate::Parse("/a:foo"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a"}), ht->segments()); - ASSERT_EQ("foo", ht->verb()); - ASSERT_EQ(Variables(), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest4b) { - auto ht = HttpTemplate::Parse("/a/b/c:foo"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "b", "c"}), ht->segments()); - ASSERT_EQ("foo", ht->verb()); - ASSERT_EQ(Variables(), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest5) { - auto ht = HttpTemplate::Parse("/*/**"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*", "**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables(), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest6) { - auto ht = HttpTemplate::Parse("/*/a/**"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*", "a", "**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables(), ht->Variables()); -} - -TEST(HttpTemplate, ParseTest7) { - auto ht = HttpTemplate::Parse("/a/{a.b.c}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, 2, FieldPath{"a", "b", "c"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest8) { - auto ht = HttpTemplate::Parse("/a/{a.b.c=*}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, 2, FieldPath{"a", "b", "c"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest9) { - auto ht = HttpTemplate::Parse("/a/{b=*}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, 2, FieldPath{"b"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest10) { - auto ht = HttpTemplate::Parse("/a/{b=**}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, -1, FieldPath{"b"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest11) { - auto ht = HttpTemplate::Parse("/a/{b=c/*}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, 3, FieldPath{"b"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest12) { - auto ht = HttpTemplate::Parse("/a/{b=c/*/d}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "*", "d"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, 4, FieldPath{"b"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest13) { - auto ht = HttpTemplate::Parse("/a/{b=c/**}"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, -1, FieldPath{"b"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest14) { - auto ht = HttpTemplate::Parse("/a/{b=c/**}/d/e"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "**", "d", "e"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, -3, FieldPath{"b"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest15) { - auto ht = HttpTemplate::Parse("/a/{b=c/**/d}/e"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "**", "d", "e"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, -2, FieldPath{"b"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ParseTest16) { - auto ht = HttpTemplate::Parse("/a/{b=c/**/d}/e:verb"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "c", "**", "d", "e"}), ht->segments()); - ASSERT_EQ("verb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{1, -3, FieldPath{"b"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, CustomVerbTests) { - auto ht = HttpTemplate::Parse("/*:verb"); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ(Variables(), ht->Variables()); - - ht = HttpTemplate::Parse("/**:verb"); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ(Variables(), ht->Variables()); - - ht = HttpTemplate::Parse("/{a}:verb"); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"a"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/a/b/*:verb"); - ASSERT_EQ(Segments({"a", "b", "*"}), ht->segments()); - ASSERT_EQ(Variables(), ht->Variables()); - - ht = HttpTemplate::Parse("/a/b/**:verb"); - ASSERT_EQ(Segments({"a", "b", "**"}), ht->segments()); - ASSERT_EQ(Variables(), ht->Variables()); - - ht = HttpTemplate::Parse("/a/b/{a}:verb"); - ASSERT_EQ(Segments({"a", "b", "*"}), ht->segments()); - ASSERT_EQ(Variables({ - Variable{2, 3, FieldPath{"a"}, false}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, MoreVariableTests) { - auto ht = HttpTemplate::Parse("/{x}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"x"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"x", "y", "z"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x=*}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"x"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x=a/*}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "*"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 2, FieldPath{"x"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=*/a/b}/c"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*", "a", "b", "c"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 3, FieldPath{"x", "y", "z"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x=**}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -1, FieldPath{"x"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=**}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -1, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=a/**/b}"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "**", "b"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -1, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=a/**/b}/c/d"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "**", "b", "c", "d"}), ht->segments()); - ASSERT_EQ("", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -3, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, VariableAndCustomVerbTests) { - auto ht = HttpTemplate::Parse("/{x}:verb"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ("verb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"x"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z}:verb"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*"}), ht->segments()); - ASSERT_EQ("verb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 1, FieldPath{"x", "y", "z"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=*/*}:verb"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"*", "*"}), ht->segments()); - ASSERT_EQ("verb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, 2, FieldPath{"x", "y", "z"}, false}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x=**}:myverb"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ("myverb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -2, FieldPath{"x"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=**}:myverb"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"**"}), ht->segments()); - ASSERT_EQ("myverb", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -2, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=a/**/b}:custom"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "**", "b"}), ht->segments()); - ASSERT_EQ("custom", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -2, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); - - ht = HttpTemplate::Parse("/{x.y.z=a/**/b}/c/d:custom"); - - ASSERT_NE(nullptr, ht); - ASSERT_EQ(Segments({"a", "**", "b", "c", "d"}), ht->segments()); - ASSERT_EQ("custom", ht->verb()); - ASSERT_EQ(Variables({ - Variable{0, -4, FieldPath{"x", "y", "z"}, true}, - }), - ht->Variables()); -} - -TEST(HttpTemplate, ErrorTests) { - ASSERT_EQ(nullptr, HttpTemplate::Parse("")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("//")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/{}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a//b")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse(":verb")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/:verb")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/:verb")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse(":")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/*:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/**:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/{var}:")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b/:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b/*:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b/**:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b/{var}:")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{var")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{var.")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{x=var:verb}")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse("a")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("{x}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("{x=/a}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("{x=/a/b}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("a/b")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("a/b/{x}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("a/{x}/b")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("a/{x}/b:verb")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{var=/b}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/{var=a/{nested=b}}")); - - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a{x}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/{x}a")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a{x}b")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/{x}a{y}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b{x}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{x}b")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b{x}c")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{x}b{y}")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b{x}/s")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{x}b/s")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/b{x}c/s")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{x}b{y}/s")); -} - -TEST(HttpTemplate, ParseVerbTest2) { - auto ht = HttpTemplate::Parse("/a/*:verb"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(ht->segments(), Segments({"a", "*"})); - ASSERT_EQ("verb", ht->verb()); -} - -TEST(HttpTemplate, ParseVerbTest3) { - auto ht = HttpTemplate::Parse("/a/**:verb"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(ht->segments(), Segments({"a", "**"})); - ASSERT_EQ("verb", ht->verb()); -} - -TEST(HttpTemplate, ParseVerbTest4) { - auto ht = HttpTemplate::Parse("/a/{b=*}/**:verb"); - ASSERT_NE(nullptr, ht); - ASSERT_EQ(ht->segments(), Segments({"a", "*", "**"})); - ASSERT_EQ("verb", ht->verb()); -} - -TEST(HttpTemplate, ParseNonVerbTest) { - ASSERT_EQ(nullptr, HttpTemplate::Parse(":")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/*:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/**:")); - ASSERT_EQ(nullptr, HttpTemplate::Parse("/a/{b=*}/**:")); -} - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/api_spec/path_matcher.h b/src/istio/api_spec/path_matcher.h deleted file mode 100644 index 02537c23639..00000000000 --- a/src/istio/api_spec/path_matcher.h +++ /dev/null @@ -1,498 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_API_SPEC_PATH_MATCHER_H_ -#define ISTIO_API_SPEC_PATH_MATCHER_H_ - -#include -#include -#include -#include -#include -#include - -#include "src/istio/api_spec/http_template.h" -#include "src/istio/api_spec/path_matcher_node.h" - -namespace istio { -namespace api_spec { - -template -class PathMatcherBuilder; // required for PathMatcher constructor - -// The immutable, thread safe PathMatcher stores a mapping from a combination of -// a service (host) name and a HTTP path to your method (MethodInfo*). It is -// constructed with a PathMatcherBuilder and supports one operation: Lookup. -// Clients may use this method to locate your method (MethodInfo*) for a -// combination of service name and HTTP URL path. -// -// Usage example: -// 1) building the PathMatcher: -// PathMatcherBuilder builder(false); -// for each (service_name, http_method, url_path, associated method) -// builder.register(service_name, http_method, url_path, data); -// PathMater matcher = builder.Build(); -// 2) lookup: -// MethodInfo * method = matcher.Lookup(service_name, http_method, -// url_path); -// if (method == nullptr) failed to find it. -// -template -class PathMatcher { - public: - ~PathMatcher(){}; - - // TODO: Do not template VariableBinding - template - Method Lookup(const std::string& http_method, const std::string& path, - const std::string& query_params, - std::vector* variable_bindings, - std::string* body_field_path) const; - - Method Lookup(const std::string& http_method, const std::string& path) const; - - private: - // Creates a Path Matcher with a Builder by moving the builder's root node. - explicit PathMatcher(PathMatcherBuilder&& builder); - - // A root node shared by all services, i.e. paths of all services will be - // registered to this node. - std::unique_ptr root_ptr_; - // Holds the set of custom verbs found in configured templates. - std::set custom_verbs_; - // Data we store per each registered method - struct MethodData { - Method method; - std::vector variables; - std::string body_field_path; - }; - // The info associated with each method. The path matcher nodes - // will hold pointers to MethodData objects in this vector. - std::vector> methods_; - - private: - friend class PathMatcherBuilder; -}; - -template -using PathMatcherPtr = std::unique_ptr>; - -// This PathMatcherBuilder is used to register path-WrapperGraph pairs and -// instantiate an immutable, thread safe PathMatcher. -// -// The PathMatcherBuilder itself is NOT THREAD SAFE. -template -class PathMatcherBuilder { - public: - PathMatcherBuilder(); - ~PathMatcherBuilder() {} - - // Registers a method. - // - // Registrations are one-to-one. If this function is called more than once, it - // replaces the existing method. Only the last registered method is stored. - // Return false if path is an invalid http template. - bool Register(std::string http_method, std::string path, - std::string body_field_path, Method method); - - // Returns a unique_ptr to a thread safe PathMatcher that contains all - // registered path-WrapperGraph pairs. Note the PathMatchBuilder instance - // will be moved so cannot use after invoking Build(). - PathMatcherPtr Build(); - - private: - // A root node shared by all services, i.e. paths of all services will be - // registered to this node. - std::unique_ptr root_ptr_; - // The set of custom verbs configured. - // TODO: Perhaps this should not be at this level because there will - // be multiple templates in different services on a server. Consider moving - // this to PathMatcherNode. - std::set custom_verbs_; - typedef typename PathMatcher::MethodData MethodData; - std::vector> methods_; - - friend class PathMatcher; -}; - -namespace { - -std::vector& split(const std::string& s, char delim, - std::vector& elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -inline bool IsReservedChar(char c) { - // Reserved characters according to RFC 6570 - switch (c) { - case '!': - case '#': - case '$': - case '&': - case '\'': - case '(': - case ')': - case '*': - case '+': - case ',': - case '/': - case ':': - case ';': - case '=': - case '?': - case '@': - case '[': - case ']': - return true; - default: - return false; - } -} - -// Check if an ASCII character is a hex digit. We can't use ctype's -// isxdigit() because it is affected by locale. This function is applied -// to the escaped characters in a url, not to natural-language -// strings, so locale should not be taken into account. -inline bool ascii_isxdigit(char c) { - return ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') || - ('0' <= c && c <= '9'); -} - -inline int hex_digit_to_int(char c) { - /* Assume ASCII. */ - int x = static_cast(c); - if (x > '9') { - x += 9; - } - return x & 0xf; -} - -// This is a helper function for UrlUnescapeString. It takes a string and -// the index of where we are within that string. -// -// The function returns true if the next three characters are of the format: -// "%[0-9A-Fa-f]{2}". -// -// If the next three characters are an escaped character then this function will -// also return what character is escaped. -bool GetEscapedChar(const std::string& src, size_t i, - bool unescape_reserved_chars, char* out) { - if (i + 2 < src.size() && src[i] == '%') { - if (ascii_isxdigit(src[i + 1]) && ascii_isxdigit(src[i + 2])) { - char c = - (hex_digit_to_int(src[i + 1]) << 4) | hex_digit_to_int(src[i + 2]); - if (!unescape_reserved_chars && IsReservedChar(c)) { - return false; - } - *out = c; - return true; - } - } - return false; -} - -// Unescapes string 'part' and returns the unescaped string. Reserved characters -// (as specified in RFC 6570) are not escaped if unescape_reserved_chars is -// false. -std::string UrlUnescapeString(const std::string& part, - bool unescape_reserved_chars) { - std::string unescaped; - // Check whether we need to escape at all. - bool needs_unescaping = false; - char ch = '\0'; - for (size_t i = 0; i < part.size(); ++i) { - if (GetEscapedChar(part, i, unescape_reserved_chars, &ch)) { - needs_unescaping = true; - break; - } - } - if (!needs_unescaping) { - unescaped = part; - return unescaped; - } - - unescaped.resize(part.size()); - - char* begin = &(unescaped)[0]; - char* p = begin; - - for (size_t i = 0; i < part.size();) { - if (GetEscapedChar(part, i, unescape_reserved_chars, &ch)) { - *p++ = ch; - i += 3; - } else { - *p++ = part[i]; - i += 1; - } - } - - unescaped.resize(p - begin); - return unescaped; -} - -template -void ExtractBindingsFromPath(const std::vector& vars, - const std::vector& parts, - std::vector* bindings) { - for (const auto& var : vars) { - // Determine the subpath bound to the variable based on the - // [start_segment, end_segment) segment range of the variable. - // - // In case of matching "**" - end_segment is negative and is relative to - // the end such that end_segment = -1 will match all subsequent segments. - VariableBinding binding; - binding.field_path = var.field_path; - // Calculate the absolute index of the ending segment in case it's negative. - size_t end_segment = (var.end_segment >= 0) - ? var.end_segment - : parts.size() + var.end_segment + 1; - // It is multi-part match if we have more than one segment. We also make - // sure that a single URL segment match with ** is also considered a - // multi-part match by checking if it->second.end_segment is negative. - bool is_multipart = - (end_segment - var.start_segment) > 1 || var.end_segment < 0; - // Joins parts with "/" to form a path string. - for (size_t i = var.start_segment; i < end_segment; ++i) { - // For multipart matches only unescape non-reserved characters. - binding.value += UrlUnescapeString(parts[i], !is_multipart); - if (i < end_segment - 1) { - binding.value += "/"; - } - } - bindings->emplace_back(binding); - } -} - -template -void ExtractBindingsFromQueryParameters( - const std::string& query_params, const std::set& system_params, - std::vector* bindings) { - // The bindings in URL the query parameters have the following form: - // =value1&=value2&...&=valueN - // Query parameters may also contain system parameters such as `api_key`. - // We'll need to ignore these. Example: - // book.id=123&book.author=Neal%20Stephenson&api_key=AIzaSyAz7fhBkC35D2M - std::vector params; - split(query_params, '&', params); - for (const auto& param : params) { - size_t pos = param.find('='); - if (pos != 0 && pos != std::string::npos) { - auto name = param.substr(0, pos); - // Make sure the query parameter is not a system parameter (e.g. - // `api_key`) before adding the binding. - if (system_params.find(name) == std::end(system_params)) { - // The name of the parameter is a field path, which is a dot-delimited - // sequence of field names that identify the (potentially deep) field - // in the request, e.g. `book.author.name`. - VariableBinding binding; - split(name, '.', binding.field_path); - binding.value = UrlUnescapeString(param.substr(pos + 1), true); - bindings->emplace_back(std::move(binding)); - } - } - } -} - -// Converts a request path into a format that can be used to perform a request -// lookup in the PathMatcher trie. This utility method sanitizes the request -// path and then splits the path into slash separated parts. Returns an empty -// vector if the sanitized path is "/". -// -// custom_verbs is a set of configured custom verbs that are used to match -// against any custom verbs in request path. If the request_path contains a -// custom verb not found in custom_verbs, it is treated as a part of the path. -// -// - Strips off query string: "/a?foo=bar" --> "/a" -// - Collapses extra slashes: "///" --> "/" -std::vector ExtractRequestParts( - std::string path, const std::set& custom_verbs) { - // Remove query parameters. - path = path.substr(0, path.find_first_of('?')); - - // Replace last ':' with '/' to handle custom verb. - // But not for /foo:bar/const. - std::size_t last_colon_pos = path.find_last_of(':'); - std::size_t last_slash_pos = path.find_last_of('/'); - if (last_colon_pos != std::string::npos && last_colon_pos > last_slash_pos) { - std::string verb = path.substr(last_colon_pos + 1); - // only verb in the configured custom verbs, treat it as verb - // replace ":" with / as a separate segment. - if (custom_verbs.find(verb) != custom_verbs.end()) { - path[last_colon_pos] = '/'; - } - } - - std::vector result; - if (path.size() > 0) { - split(path.substr(1), '/', result); - } - // Removes all trailing empty parts caused by extra "/". - while (!result.empty() && (*(--result.end())).empty()) { - result.pop_back(); - } - return result; -} - -// Looks up on a PathMatcherNode. -PathMatcherLookupResult LookupInPathMatcherNode( - const PathMatcherNode& root, const std::vector& parts, - const HttpMethod& http_method) { - PathMatcherLookupResult result; - root.LookupPath(parts.begin(), parts.end(), http_method, &result); - return result; -} - -PathMatcherNode::PathInfo TransformHttpTemplate(const HttpTemplate& ht) { - PathMatcherNode::PathInfo::Builder builder; - - for (const std::string& part : ht.segments()) { - builder.AppendLiteralNode(part); - } - if (!ht.verb().empty()) { - builder.AppendLiteralNode(ht.verb()); - } - - return builder.Build(); -} - -} // namespace - -template -PathMatcher::PathMatcher(PathMatcherBuilder&& builder) - : root_ptr_(std::move(builder.root_ptr_)), - custom_verbs_(std::move(builder.custom_verbs_)), - methods_(std::move(builder.methods_)) {} - -// Lookup is a wrapper method for the recursive node Lookup. First, the wrapper -// splits the request path into slash-separated path parts. Next, the method -// checks that the |http_method| is supported. If not, then it returns an empty -// WrapperGraph::SharedPtr. Next, this method invokes the node's Lookup on -// the extracted |parts|. Finally, it fills the mapping from variables to their -// values parsed from the path. -// TODO: cache results by adding get/put methods here (if profiling reveals -// benefit) -template -template -Method PathMatcher::Lookup( - const std::string& http_method, const std::string& path, - const std::string& query_params, - std::vector* variable_bindings, - std::string* body_field_path) const { - const std::vector parts = - ExtractRequestParts(path, custom_verbs_); - - // If service_name has not been registered to ESP and strict_service_matching_ - // is set to false, tries to lookup the method in all registered services. - if (root_ptr_ == nullptr) { - return nullptr; - } - - PathMatcherLookupResult lookup_result = - LookupInPathMatcherNode(*root_ptr_, parts, http_method); - // Return nullptr if nothing is found. - // Not need to check duplication. Only first item is stored for duplicated - if (lookup_result.data == nullptr) { - return nullptr; - } - MethodData* method_data = reinterpret_cast(lookup_result.data); - if (variable_bindings != nullptr) { - variable_bindings->clear(); - ExtractBindingsFromPath(method_data->variables, parts, variable_bindings); - ExtractBindingsFromQueryParameters( - query_params, method_data->method->system_query_parameter_names(), - variable_bindings); - } - if (body_field_path != nullptr) { - *body_field_path = method_data->body_field_path; - } - return method_data->method; -} - -// TODO: refactor common code with method above -template -Method PathMatcher::Lookup(const std::string& http_method, - const std::string& path) const { - const std::vector parts = - ExtractRequestParts(path, custom_verbs_); - - // If service_name has not been registered to ESP and strict_service_matching_ - // is set to false, tries to lookup the method in all registered services. - if (root_ptr_ == nullptr) { - return nullptr; - } - - PathMatcherLookupResult lookup_result = - LookupInPathMatcherNode(*root_ptr_, parts, http_method); - // Return nullptr if nothing is found. - // Not need to check duplication. Only first item is stored for duplicated - if (lookup_result.data == nullptr) { - return nullptr; - } - MethodData* method_data = reinterpret_cast(lookup_result.data); - return method_data->method; -} - -// Initializes the builder with a root Path Segment -template -PathMatcherBuilder::PathMatcherBuilder() - : root_ptr_(new PathMatcherNode()) {} - -template -PathMatcherPtr PathMatcherBuilder::Build() { - return PathMatcherPtr(new PathMatcher(std::move(*this))); -} - -// This wrapper converts the |http_rule| into a HttpTemplate. Then, inserts the -// template into the trie. -template -bool PathMatcherBuilder::Register(std::string http_method, - std::string http_template, - std::string body_field_path, - Method method) { - std::unique_ptr ht = HttpTemplate::Parse(http_template); - if (nullptr == ht) { - return false; - } - PathMatcherNode::PathInfo path_info = TransformHttpTemplate(*ht); - if (path_info.path_info().size() == 0) { - return false; - } - // Create & initialize a MethodData struct. Then insert its pointer - // into the path matcher trie. - auto method_data = std::unique_ptr(new MethodData()); - method_data->method = method; - method_data->variables = std::move(ht->Variables()); - method_data->body_field_path = std::move(body_field_path); - - if (!root_ptr_->InsertPath(path_info, http_method, method_data.get(), true)) { - return false; - } - // Add the method_data to the methods_ vector for cleanup - methods_.emplace_back(std::move(method_data)); - if (!ht->verb().empty()) { - custom_verbs_.insert(ht->verb()); - } - return true; -} - -} // namespace api_spec -} // namespace istio - -#endif // ISTIO_API_SPEC_PATH_MATCHER_H_ diff --git a/src/istio/api_spec/path_matcher_node.cc b/src/istio/api_spec/path_matcher_node.cc deleted file mode 100644 index d9e40bca59a..00000000000 --- a/src/istio/api_spec/path_matcher_node.cc +++ /dev/null @@ -1,244 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/api_spec/path_matcher_node.h" -#include "src/istio/api_spec/http_template.h" - -namespace istio { -namespace api_spec { - -const char HttpMethod_WILD_CARD[] = "*"; - -namespace { - -// Tries to insert the given key-value pair into the collection. Returns nullptr -// if the insert succeeds. Otherwise, returns a pointer to the existing value. -// -// This complements UpdateReturnCopy in that it allows to update only after -// verifying the old value and still insert quickly without having to look up -// twice. Unlike UpdateReturnCopy this also does not come with the issue of an -// undefined previous* in case new data was inserted. -template -typename Collection::value_type::second_type* InsertOrReturnExisting( - Collection* const collection, const typename Collection::value_type& vt) { - std::pair ret = collection->insert(vt); - if (ret.second) { - return nullptr; // Inserted, no existing previous value. - } else { - return &ret.first->second; // Return address of already existing value. - } -} - -// Same as above, except for explicit key and data. -template -typename Collection::value_type::second_type* InsertOrReturnExisting( - Collection* const collection, - const typename Collection::value_type::first_type& key, - const typename Collection::value_type::second_type& data) { - return InsertOrReturnExisting(collection, - typename Collection::value_type(key, data)); -} - -// Returns a reference to the pointer associated with key. If not found, -// a pointee is constructed and added to the map. In that case, the new -// pointee is value-initialized (aka "default-constructed"). -// Useful for containers of the form Map, where Ptr is pointer-like. -template -typename Collection::value_type::second_type& LookupOrInsertNew( - Collection* const collection, - const typename Collection::value_type::first_type& key) { - typedef typename Collection::value_type::second_type Mapped; - typedef typename Mapped::element_type Element; - std::pair ret = - collection->insert(typename Collection::value_type(key, Mapped())); - if (ret.second) { - ret.first->second = Mapped(new Element()); - } - return ret.first->second; -} - -// A convinent function to lookup a STL colllection with two keys. -// Lookup key1 first, if not found, lookup key2, or return nullptr. -template -const typename Collection::value_type::second_type* Find2KeysOrNull( - const Collection& collection, - const typename Collection::value_type::first_type& key1, - const typename Collection::value_type::first_type& key2) { - auto it = collection.find(key1); - if (it == collection.end()) { - it = collection.find(key2); - if (it == collection.end()) { - return nullptr; - } - } - return &it->second; -} -} // namespace - -PathMatcherNode::PathInfo::Builder& -PathMatcherNode::PathInfo::Builder::AppendLiteralNode(std::string name) { - if (name == HttpTemplate::kSingleParameterKey) { - // status_.Update(util::Status(util::error::INVALID_ARGUMENT, - // StrCat(name, " is a reserved node name."))); - } - path_.emplace_back(name); - return *this; -} - -PathMatcherNode::PathInfo::Builder& -PathMatcherNode::PathInfo::Builder::AppendSingleParameterNode() { - path_.emplace_back(HttpTemplate::kSingleParameterKey); - return *this; -} - -PathMatcherNode::PathInfo PathMatcherNode::PathInfo::Builder::Build() const { - return PathMatcherNode::PathInfo(*this); -} - -PathMatcherNode::~PathMatcherNode() {} - -std::unique_ptr PathMatcherNode::Clone() const { - std::unique_ptr clone(new PathMatcherNode()); - clone->result_map_ = result_map_; - // deep-copy literal children - for (const auto& entry : children_) { - clone->children_.emplace(entry.first, entry.second->Clone()); - } - clone->wildcard_ = wildcard_; - return clone; -} - -// This recursive function performs an exhaustive DFS of the node's subtrie. -// The node attempts to find a match for the current part of the path among its -// children. Children are considered in sequence according to Google HTTP -// Template Spec matching precedence. If a match is found, the method recurses -// on the matching child with the next part in path. -// -// NB: If this path segment is of repeated-variable type and no matching child -// is found, the receiver recurses on itself with the next path part. -// -// Base Case: |current| is beyond the range of the path parts -// ========== -// The receiver node matched the final part in |path|. If a WrapperGraph exists -// for the given HTTP method, the method copies to the node's WrapperGraph to -// result and returns true. -void PathMatcherNode::LookupPath(const RequestPathParts::const_iterator current, - const RequestPathParts::const_iterator end, - HttpMethod http_method, - PathMatcherLookupResult* result) const { - // base case - if (current == end) { - if (!GetResultForHttpMethod(http_method, result)) { - // If we didn't find a wrapper graph at this node, check if we have one - // in a wildcard (**) child. If we do, use it. This will ensure we match - // the root with wildcard templates. - auto pair = children_.find(HttpTemplate::kWildCardPathKey); - if (pair != children_.end()) { - const auto& child = pair->second; - child->GetResultForHttpMethod(http_method, result); - } - } - return; - } - if (LookupPathFromChild(*current, current, end, http_method, result)) { - return; - } - // For wild card node, keeps searching for next path segment until either - // 1) reaching the end (/foo/** case), or 2) all remaining segments match - // one of child branches (/foo/**/bar/xyz case). - if (wildcard_) { - LookupPath(current + 1, end, http_method, result); - // Since only constant segments are allowed after wild card, no need to - // search another wild card nodes from children, so bail out here. - return; - } - - for (const std::string& child_key : - {HttpTemplate::kSingleParameterKey, HttpTemplate::kWildCardPathPartKey, - HttpTemplate::kWildCardPathKey}) { - if (LookupPathFromChild(child_key, current, end, http_method, result)) { - return; - } - } - return; -} - -bool PathMatcherNode::InsertPath(const PathInfo& node_path_info, - std::string http_method, void* method_data, - bool mark_duplicates) { - return InsertTemplate(node_path_info.path_info().begin(), - node_path_info.path_info().end(), http_method, - method_data, mark_duplicates); -} - -// This method locates a matching child for the |current| path part, inserting a -// child if not present. Then, the method recurses on this matching child with -// the next template path part. -// -// Base Case: |current| is beyond the range of the path parts -// ========== -// This node matched the final part in the iterator of parts. This method -// updates the node's WrapperGraph for the specified HTTP method. -bool PathMatcherNode::InsertTemplate( - const std::vector::const_iterator current, - const std::vector::const_iterator end, HttpMethod http_method, - void* method_data, bool mark_duplicates) { - if (current == end) { - PathMatcherLookupResult* const existing = InsertOrReturnExisting( - &result_map_, http_method, PathMatcherLookupResult(method_data, false)); - if (existing != nullptr) { - if (mark_duplicates) { - existing->is_multiple = true; - } - return false; - } - return true; - } - std::unique_ptr& child = - LookupOrInsertNew(&children_, *current); - if (*current == HttpTemplate::kWildCardPathKey) { - child->set_wildcard(true); - } - return child->InsertTemplate(current + 1, end, http_method, method_data, - mark_duplicates); -} - -bool PathMatcherNode::LookupPathFromChild( - const std::string child_key, const RequestPathParts::const_iterator current, - const RequestPathParts::const_iterator end, HttpMethod http_method, - PathMatcherLookupResult* result) const { - auto pair = children_.find(child_key); - if (pair != children_.end()) { - pair->second->LookupPath(current + 1, end, http_method, result); - if (result != nullptr && result->data != nullptr) { - return true; - } - } - return false; -} - -bool PathMatcherNode::GetResultForHttpMethod( - HttpMethod key, PathMatcherLookupResult* result) const { - const PathMatcherLookupResult* found_p = - Find2KeysOrNull(result_map_, key, HttpMethod_WILD_CARD); - if (found_p != nullptr) { - *result = *found_p; - return true; - } - return false; -} - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/api_spec/path_matcher_node.h b/src/istio/api_spec/path_matcher_node.h deleted file mode 100644 index 388c08a9494..00000000000 --- a/src/istio/api_spec/path_matcher_node.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_API_SPEC_PATH_MATCHER_NODE_H_ -#define ISTIO_API_SPEC_PATH_MATCHER_NODE_H_ - -#include -#include -#include -#include -#include - -namespace istio { -namespace api_spec { - -typedef std::string HttpMethod; - -struct PathMatcherLookupResult { - PathMatcherLookupResult() : data(nullptr), is_multiple(false) {} - - PathMatcherLookupResult(void* data, bool is_multiple) - : data(data), is_multiple(is_multiple) {} - - // The WrapperGraph that is registered to a method (or HTTP path). - void* data; - // Whether the method (or path) has been registered for more than once. - bool is_multiple; -}; - -// PathMatcherNodes represents a path part in a PathMatcher trie. Children nodes -// represent adjacent path parts. A node can have many literal children, one -// single-parameter child, and one repeated-parameter child. -// -// Thread Compatible. -class PathMatcherNode { - public: - // Provides information for inserting templates into the trie. Clients can - // instantiate PathInfo with the provided Builder. - class PathInfo { - public: - class Builder { - public: - friend class PathInfo; - Builder() : path_() {} - ~Builder() {} - - PathMatcherNode::PathInfo Build() const; - - // Appends a node that must match the string value of a request part. The - // strings "/." and "/.." are disallowed. - // - // Example: - // - // builder.AppendLiteralNode("a") - // .AppendLiteralNode("b") - // .AppendLiteralNode("c"); - // - // Matches the request path: a/b/c - Builder& AppendLiteralNode(std::string name); - - // Appends a node that ignores the string value and matches any single - // request part. - // - // Example: - // - // builder.AppendLiteralNode("a") - // .AppendSingleParameterNode() - // .AppendLiteralNode("c"); - // - // Matching request paths: a/foo/c, a/bar/c, a/1/c - Builder& AppendSingleParameterNode(); - - // TODO: Appends a node that ignores string values and matches any - // number of consecutive request parts. - // - // Example: - // - // builder.AppendLiteralNode("a") - // .AppendLiteralNode("b") - // .AppendRepeatedParameterNode(); - // - // Matching request paths: a/b/1/2/3/4/5, a/b/c - // Builder& AppendRepeatedParameterNode(); - - private: - std::vector path_; - }; // class Builder - - ~PathInfo() {} - - // Returns path information used to insert a new path into a PathMatcherNode - // trie. - const std::vector& path_info() const { return path_; } - - private: - explicit PathInfo(const Builder& builder) : path_(builder.path_) {} - std::vector path_; - }; // class PathInfo - - typedef std::vector RequestPathParts; - - // Creates a Root node with an empty WrapperGraph map. - PathMatcherNode() : result_map_(), children_(), wildcard_(false) {} - - ~PathMatcherNode(); - - // Creates a clone of this node and its subtrie - std::unique_ptr Clone() const; - - // Searches subtrie by finding a matching child for the current path part. If - // a matching child exists, this function recurses on current + 1 with that - // child as the receiver. If a matching descendant is found for the last part - // in then this method copies the matching descendant's WrapperGraph, - // VariableBindingInfoMap to the result pointers. - void LookupPath(const RequestPathParts::const_iterator current, - const RequestPathParts::const_iterator end, - HttpMethod http_method, - PathMatcherLookupResult* result) const; - - // This method inserts a path of nodes into this subtrie. The WrapperGraph, - // VariableBindingInfoMap are inserted at the terminal descendant node. - // Returns true if the template didn't previously exist. Returns false - // otherwise and depends on if mark_duplicates is true, the template will be - // marked as having been registered for more than once and the lookup of the - // template will yield a special error reporting WrapperGraph. - bool InsertPath(const PathInfo& node_path_info, std::string http_method, - void* method_data, bool mark_duplicates); - - void set_wildcard(bool wildcard) { wildcard_ = wildcard; } - - private: - // This method inserts a path of nodes into this subtrie (described by the - // vector, starting from the |current| position in the iterator of path - // parts, and if necessary, creating intermediate nodes along the way. The - // WrapperGraph, VariableBindingInfoMap are inserted at the terminal - // descendant node (which corresponds to the string part in the iterator). - // Returns true if the template didn't previously exist. Returns false - // otherwise and depends on if mark_duplicates is true, the template will be - // marked as having been registered for more than once and the lookup of the - // template will yield a special error reporting WrapperGraph. - bool InsertTemplate(const std::vector::const_iterator current, - const std::vector::const_iterator end, - HttpMethod http_method, void* method_data, - bool mark_duplicates); - - // Helper method for LookupPath. If the given child key exists, search - // continues on the child node pointed by the child key with the next part - // in the path. Returns true if found a match for the path eventually. - bool LookupPathFromChild(const std::string child_key, - const RequestPathParts::const_iterator current, - const RequestPathParts::const_iterator end, - HttpMethod http_method, - PathMatcherLookupResult* result) const; - - // If a WrapperGraph is found for the provided key, then this method returns - // true and copies the WrapperGraph to the provided result pointer. If no - // match is found, this method returns false and leaves the result unmodified. - // - // NB: If result == nullptr, method will return bool value without modifying - // result. - bool GetResultForHttpMethod(HttpMethod key, - PathMatcherLookupResult* result) const; - - std::map result_map_; - - // Lookup must be FAST - // - // n: the number of paths registered per client varies, but we can expect the - // size of |children_| to range from ~5 to ~100 entries. - // - // To ensure fast lookups when n grows large, it is prudent to consider an - // alternative to binary search on a sorted vector. - std::unordered_map> children_; - - // True if this node represents a wildcard path '**'. - bool wildcard_; -}; - -} // namespace api_spec -} // namespace istio - -#endif // ISTIO_API_SPEC_PATH_MATCHER_NODE_H_ diff --git a/src/istio/api_spec/path_matcher_test.cc b/src/istio/api_spec/path_matcher_test.cc deleted file mode 100644 index 133e894eef3..00000000000 --- a/src/istio/api_spec/path_matcher_test.cc +++ /dev/null @@ -1,788 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/api_spec/path_matcher.h" - -#include -#include -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using ::testing::ReturnRef; - -namespace istio { -namespace api_spec { - -namespace { - -// VariableBinding specifies a value for a single field in the request message. -// When transcoding HTTP/REST/JSON to gRPC/proto the request message is -// constructed using the HTTP body and the variable bindings (specified through -// request url). -struct Binding { - // The location of the field in the protobuf message, where the value - // needs to be inserted, e.g. "shelf.theme" would mean the "theme" field - // of the nested "shelf" message of the request protobuf message. - std::vector field_path; - // The value to be inserted. - std::string value; -}; - -typedef std::vector Bindings; -typedef std::vector FieldPath; -class MethodInfo { - public: - MOCK_CONST_METHOD0(system_query_parameter_names, - const std::set&()); -}; - -bool operator==(const Binding& b1, const Binding& b2) { - return b1.field_path == b2.field_path && b1.value == b2.value; -} - -std::string FieldPathToString(const FieldPath& fp) { - std::string s; - for (const auto& f : fp) { - if (!s.empty()) { - s += "."; - } - s += f; - } - return s; -} - -} // namespace - -std::ostream& operator<<(std::ostream& os, const Binding& b) { - return os << "{ " << FieldPathToString(b.field_path) << "=" << b.value << "}"; -} - -std::ostream& operator<<(std::ostream& os, const Bindings& bindings) { - for (const auto& b : bindings) { - os << b << std::endl; - } - return os; -} - -namespace { - -class PathMatcherTest : public ::testing::Test { - protected: - PathMatcherTest() {} - ~PathMatcherTest() {} - - MethodInfo* AddPathWithBodyFieldPath(std::string http_method, - std::string http_template, - std::string body_field_path) { - auto method = new MethodInfo(); - ON_CALL(*method, system_query_parameter_names()) - .WillByDefault(ReturnRef(empty_set_)); - if (!builder_.Register(http_method, http_template, body_field_path, - method)) { - delete method; - return nullptr; - } - stored_methods_.emplace_back(method); - return method; - } - - MethodInfo* AddPathWithSystemParams( - std::string http_method, std::string http_template, - const std::set* system_params) { - auto method = new MethodInfo(); - ON_CALL(*method, system_query_parameter_names()) - .WillByDefault(ReturnRef(*system_params)); - if (!builder_.Register(http_method, http_template, std::string(), method)) { - delete method; - return nullptr; - } - stored_methods_.emplace_back(method); - return method; - } - - MethodInfo* AddPath(std::string http_method, std::string http_template) { - return AddPathWithBodyFieldPath(http_method, http_template, std::string()); - } - - MethodInfo* AddGetPath(std::string path) { return AddPath("GET", path); } - - void Build() { matcher_ = builder_.Build(); } - - MethodInfo* LookupWithBodyFieldPath(std::string method, std::string path, - Bindings* bindings, - std::string* body_field_path) { - return matcher_->Lookup(method, path, "", bindings, body_field_path); - } - - MethodInfo* Lookup(std::string method, std::string path, Bindings* bindings) { - std::string body_field_path; - return matcher_->Lookup(method, path, std::string(), bindings, - &body_field_path); - } - - MethodInfo* LookupWithParams(std::string method, std::string path, - std::string query_params, Bindings* bindings) { - std::string body_field_path; - return matcher_->Lookup(method, path, query_params, bindings, - &body_field_path); - } - - MethodInfo* LookupNoBindings(std::string method, std::string path) { - Bindings bindings; - std::string body_field_path; - auto result = matcher_->Lookup(method, path, std::string(), &bindings, - &body_field_path); - EXPECT_EQ(0, bindings.size()); - return result; - } - - private: - PathMatcherBuilder builder_; - PathMatcherPtr matcher_; - std::vector> stored_methods_; - std::set empty_set_; -}; - -TEST_F(PathMatcherTest, WildCardMatchesRoot) { - MethodInfo* data = AddGetPath("/**"); - Build(); - - EXPECT_NE(nullptr, data); - - EXPECT_EQ(LookupNoBindings("GET", "/"), data); - EXPECT_EQ(LookupNoBindings("GET", "/a"), data); - EXPECT_EQ(LookupNoBindings("GET", "/a/"), data); -} - -TEST_F(PathMatcherTest, WildCardMatches) { - // '*' only matches one path segment, but '**' matches the remaining path. - MethodInfo* a__ = AddGetPath("/a/**"); - MethodInfo* b_ = AddGetPath("/b/*"); - MethodInfo* c_d__ = AddGetPath("/c/*/d/**"); - MethodInfo* c_de = AddGetPath("/c/*/d/e"); - MethodInfo* cfde = AddGetPath("/c/f/d/e"); - Build(); - - EXPECT_NE(nullptr, a__); - EXPECT_NE(nullptr, b_); - EXPECT_NE(nullptr, c_d__); - EXPECT_NE(nullptr, c_de); - EXPECT_NE(nullptr, cfde); - - EXPECT_EQ(LookupNoBindings("GET", "/a/b"), a__); - EXPECT_EQ(LookupNoBindings("GET", "/a/b/c"), a__); - EXPECT_EQ(LookupNoBindings("GET", "/b/c"), b_); - - EXPECT_EQ(LookupNoBindings("GET", "b/c/d"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/c/u/d/v"), c_d__); - EXPECT_EQ(LookupNoBindings("GET", "/c/v/d/w/x"), c_d__); - EXPECT_EQ(LookupNoBindings("GET", "/c/x/y/d/z"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/c//v/d/w/x"), nullptr); - - // Test that more specific match overrides wildcard "**"" match. - EXPECT_EQ(LookupNoBindings("GET", "/c/x/d/e"), c_de); - // Test that more specific match overrides wildcard "*"" match. - EXPECT_EQ(LookupNoBindings("GET", "/c/f/d/e"), cfde); -} - -TEST_F(PathMatcherTest, WildCardMethodMatches) { - MethodInfo* a__ = AddPath("*", "/a/**"); - MethodInfo* b_ = AddPath("*", "/b/*"); - Build(); - - EXPECT_NE(nullptr, a__); - EXPECT_NE(nullptr, b_); - - std::vector all_methods{"GET", "POST", "DELETE", "PATCH", "PUT"}; - for (const auto& method : all_methods) { - EXPECT_EQ(LookupNoBindings(method, "/a/b"), a__); - EXPECT_EQ(LookupNoBindings(method, "/a/b/c"), a__); - EXPECT_EQ(LookupNoBindings(method, "/b/c"), b_); - } -} - -TEST_F(PathMatcherTest, VariableBindings) { - MethodInfo* a_cde = AddGetPath("/a/{x}/c/d/e"); - MethodInfo* a_b_c = AddGetPath("/{x=a/*}/b/{y=*}/c"); - MethodInfo* ab_d__ = AddGetPath("/a/{x=b/*}/{y=d/**}"); - MethodInfo* alpha_beta__gamma = AddGetPath("/alpha/{x=*}/beta/{y=**}/gamma"); - MethodInfo* _a = AddGetPath("/{x=*}/a"); - MethodInfo* __ab = AddGetPath("/{x=**}/a/b"); - MethodInfo* ab_ = AddGetPath("/a/b/{x=*}"); - MethodInfo* abc__ = AddGetPath("/a/b/c/{x=**}"); - MethodInfo* _def__ = AddGetPath("/{x=*}/d/e/f/{y=**}"); - Build(); - - EXPECT_NE(nullptr, a_cde); - EXPECT_NE(nullptr, a_b_c); - EXPECT_NE(nullptr, ab_d__); - EXPECT_NE(nullptr, alpha_beta__gamma); - EXPECT_NE(nullptr, _a); - EXPECT_NE(nullptr, __ab); - EXPECT_NE(nullptr, ab_); - EXPECT_NE(nullptr, abc__); - EXPECT_NE(nullptr, _def__); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", "/a/book/c/d/e", &bindings), a_cde); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "book"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/hello/b/world/c", &bindings), a_b_c); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "a/hello"}, - Binding{FieldPath{"y"}, "world"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/zoo/d/animal/tiger", &bindings), ab_d__); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "b/zoo"}, - Binding{FieldPath{"y"}, "d/animal/tiger"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/alpha/dog/beta/eat/bones/gamma", &bindings), - alpha_beta__gamma); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "dog"}, - Binding{FieldPath{"y"}, "eat/bones"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/foo/a", &bindings), _a); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "foo"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/foo/bar/a/b", &bindings), __ab); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "foo/bar"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/foo", &bindings), ab_); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "foo"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/c/foo/bar/baz", &bindings), abc__); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "foo/bar/baz"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/foo/d/e/f/bar/baz", &bindings), _def__); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "foo"}, - Binding{FieldPath{"y"}, "bar/baz"}, - }), - bindings); -} - -TEST_F(PathMatcherTest, PercentEscapesUnescapedForSingleSegment) { - MethodInfo* a_c = AddGetPath("/a/{x}/c"); - Build(); - - EXPECT_NE(nullptr, a_c); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", "/a/p%20q%2Fr/c", &bindings), a_c); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "p q/r"}, - }), - bindings); -} - -namespace { - -char HexDigit(unsigned char digit, bool uppercase) { - if (digit < 10) { - return '0' + digit; - } else if (uppercase) { - return 'A' + digit - 10; - } else { - return 'a' + digit - 10; - } -} - -} // namespace - -TEST_F(PathMatcherTest, PercentEscapesUnescapedForSingleSegmentAllAsciiChars) { - MethodInfo* a_c = AddGetPath("/{x}"); - Build(); - - EXPECT_NE(nullptr, a_c); - - for (int u = 0; u < 2; ++u) { - for (char c = 0; c < 0x7f; ++c) { - std::string path("/%"); - path += HexDigit((c & 0xf0) >> 4, 0 != u); - path += HexDigit(c & 0x0f, 0 != u); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", path, &bindings), a_c); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, std::string(1, (char)c)}, - }), - bindings); - } - } -} - -TEST_F(PathMatcherTest, PercentEscapesNotUnescapedForMultiSegment1) { - MethodInfo* ap_q_c = AddGetPath("/a/{x=p/*/q/*}/c"); - Build(); - - EXPECT_NE(nullptr, ap_q_c); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", "/a/p/foo%20foo/q/bar%2Fbar/c", &bindings), ap_q_c); - // space (%20) is escaped, but slash (%2F) isn't. - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "p/foo foo/q/bar%2Fbar"}}), - bindings); -} - -TEST_F(PathMatcherTest, PercentEscapesNotUnescapedForMultiSegment2) { - MethodInfo* a__c = AddGetPath("/a/{x=**}/c"); - Build(); - - EXPECT_NE(nullptr, a__c); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", "/a/p/foo%20foo/q/bar%2Fbar/c", &bindings), a__c); - // space (%20) is escaped, but slash (%2F) isn't. - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "p/foo foo/q/bar%2Fbar"}}), - bindings); -} - -TEST_F(PathMatcherTest, OnlyUnreservedCharsAreUnescapedForMultiSegmentMatch) { - MethodInfo* a__c = AddGetPath("/a/{x=**}/c"); - Build(); - - EXPECT_NE(nullptr, a__c); - - Bindings bindings; - EXPECT_EQ( - Lookup("GET", - "/a/%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D/c", - &bindings), - a__c); - - // All %XX are reserved characters, they should be intact. - EXPECT_EQ(Bindings({Binding{ - FieldPath{"x"}, - "%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D"}}), - bindings); -} - -TEST_F(PathMatcherTest, VariableBindingsWithCustomVerb) { - MethodInfo* a_verb = AddGetPath("/a/{y=*}:verb"); - MethodInfo* ad__verb = AddGetPath("/a/{y=d/**}:verb"); - MethodInfo* _averb = AddGetPath("/{x=*}/a:verb"); - MethodInfo* __bverb = AddGetPath("/{x=**}/b:verb"); - MethodInfo* e_fverb = AddGetPath("/e/{x=*}/f:verb"); - MethodInfo* g__hverb = AddGetPath("/g/{x=**}/h:verb"); - Build(); - - EXPECT_NE(nullptr, a_verb); - EXPECT_NE(nullptr, ad__verb); - EXPECT_NE(nullptr, _averb); - EXPECT_NE(nullptr, __bverb); - EXPECT_NE(nullptr, e_fverb); - EXPECT_NE(nullptr, g__hverb); - - Bindings bindings; - EXPECT_EQ(Lookup("GET", "/a/world:verb", &bindings), a_verb); - EXPECT_EQ(Bindings({Binding{FieldPath{"y"}, "world"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/a/d/animal/tiger:verb", &bindings), ad__verb); - EXPECT_EQ(Bindings({Binding{FieldPath{"y"}, "d/animal/tiger"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/foo/a:verb", &bindings), _averb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/foo/bar/baz/b:verb", &bindings), __bverb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo/bar/baz"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/e/foo/f:verb", &bindings), e_fverb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/g/foo/bar/h:verb", &bindings), g__hverb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo/bar"}}), bindings); -} - -TEST_F(PathMatcherTest, ConstantSuffixesWithVariable) { - MethodInfo* ab__ = AddGetPath("/a/{x=b/**}"); - MethodInfo* ab__z = AddGetPath("/a/{x=b/**}/z"); - MethodInfo* ab__yz = AddGetPath("/a/{x=b/**}/y/z"); - MethodInfo* ab__verb = AddGetPath("/a/{x=b/**}:verb"); - MethodInfo* a__ = AddGetPath("/a/{x=**}"); - MethodInfo* c_d__e = AddGetPath("/c/{x=*}/{y=d/**}/e"); - MethodInfo* c_d__everb = AddGetPath("/c/{x=*}/{y=d/**}/e:verb"); - MethodInfo* f___g = AddGetPath("/f/{x=*}/{y=**}/g"); - MethodInfo* f___gverb = AddGetPath("/f/{x=*}/{y=**}/g:verb"); - MethodInfo* ab_yz__foo = AddGetPath("/a/{x=b/*/y/z/**}/foo"); - MethodInfo* ab___yzfoo = AddGetPath("/a/{x=b/*/**/y/z}/foo"); - Build(); - - EXPECT_NE(nullptr, ab__); - EXPECT_NE(nullptr, ab__z); - EXPECT_NE(nullptr, ab__yz); - EXPECT_NE(nullptr, ab__verb); - EXPECT_NE(nullptr, c_d__e); - EXPECT_NE(nullptr, c_d__everb); - EXPECT_NE(nullptr, f___g); - EXPECT_NE(nullptr, f___gverb); - EXPECT_NE(nullptr, ab_yz__foo); - EXPECT_NE(nullptr, ab___yzfoo); - - Bindings bindings; - - EXPECT_EQ(Lookup("GET", "/a/b/hello/world/c", &bindings), ab__); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "b/hello/world/c"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/world/c/z", &bindings), ab__z); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "b/world/c"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/world/c/y/z", &bindings), ab__yz); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "b/world/c"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/world/c:verb", &bindings), ab__verb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "b/world/c"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/a/hello/b/world/c", &bindings), a__); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "hello/b/world/c"}}), bindings); - - EXPECT_EQ(Lookup("GET", "/c/hello/d/esp/world/e", &bindings), c_d__e); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "hello"}, - Binding{FieldPath{"y"}, "d/esp/world"}}), - bindings); - - EXPECT_EQ(Lookup("GET", "/c/hola/d/esp/mundo/e:verb", &bindings), c_d__everb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "hola"}, - Binding{FieldPath{"y"}, "d/esp/mundo"}}), - bindings); - - EXPECT_EQ(Lookup("GET", "/f/foo/bar/baz/g", &bindings), f___g); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo"}, - Binding{FieldPath{"y"}, "bar/baz"}}), - bindings); - - EXPECT_EQ(Lookup("GET", "/f/foo/bar/baz/g:verb", &bindings), f___gverb); - EXPECT_EQ(Bindings({Binding{FieldPath{"x"}, "foo"}, - Binding{FieldPath{"y"}, "bar/baz"}}), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/foo/y/z/bar/baz/foo", &bindings), ab_yz__foo); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "b/foo/y/z/bar/baz"}, - }), - bindings); - - EXPECT_EQ(Lookup("GET", "/a/b/foo/bar/baz/y/z/foo", &bindings), ab___yzfoo); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "b/foo/bar/baz/y/z"}, - }), - bindings); -} - -TEST_F(PathMatcherTest, InvalidTemplates) { - EXPECT_EQ(nullptr, AddGetPath("/a{x=b/**}/{y=*}")); - EXPECT_EQ(nullptr, AddGetPath("/a{x=b/**}/bb/{y=*}")); - EXPECT_EQ(nullptr, AddGetPath("/a{x=b/**}/{y=**}")); - EXPECT_EQ(nullptr, AddGetPath("/a{x=b/**}/bb/{y=**}")); - - EXPECT_EQ(nullptr, AddGetPath("/a/**/*")); - EXPECT_EQ(nullptr, AddGetPath("/a/**/foo/*")); - EXPECT_EQ(nullptr, AddGetPath("/a/**/**")); - EXPECT_EQ(nullptr, AddGetPath("/a/**/foo/**")); -} - -TEST_F(PathMatcherTest, CustomVerbMatches) { - MethodInfo* some_const_verb = AddGetPath("/some/const:verb"); - MethodInfo* some__verb = AddGetPath("/some/*:verb"); - MethodInfo* some__foo_verb = AddGetPath("/some/*/foo:verb"); - MethodInfo* other__verb = AddGetPath("/other/**:verb"); - MethodInfo* other__const_verb = AddGetPath("/other/**/const:verb"); - Build(); - - EXPECT_NE(nullptr, some_const_verb); - EXPECT_NE(nullptr, some__verb); - EXPECT_NE(nullptr, some__foo_verb); - EXPECT_NE(nullptr, other__verb); - EXPECT_NE(nullptr, other__const_verb); - - EXPECT_EQ(LookupNoBindings("GET", "/some/const:verb"), some_const_verb); - EXPECT_EQ(LookupNoBindings("GET", "/some/other:verb"), some__verb); - EXPECT_EQ(LookupNoBindings("GET", "/some/other:verb/"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/some/bar/foo:verb"), some__foo_verb); - EXPECT_EQ(LookupNoBindings("GET", "/some/foo1/foo2/foo:verb"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/some/foo/bar:verb"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/other/bar/foo:verb"), other__verb); - EXPECT_EQ(LookupNoBindings("GET", "/other/bar/foo/const:verb"), - other__const_verb); -} - -TEST_F(PathMatcherTest, CustomVerbMatch2) { - MethodInfo* verb = AddGetPath("/*/*:verb"); - Build(); - EXPECT_EQ(LookupNoBindings("GET", "/some:verb/const:verb"), verb); -} - -TEST_F(PathMatcherTest, CustomVerbMatch3) { - MethodInfo* verb = AddGetPath("/foo/*"); - Build(); - - // This is not custom verb since it was not configured. - EXPECT_EQ(LookupNoBindings("GET", "/foo/other:verb"), verb); -} - -TEST_F(PathMatcherTest, CustomVerbMatch4) { - MethodInfo* a = AddGetPath("/foo/*/hello"); - Build(); - - EXPECT_NE(nullptr, a); - - // last slash is before last colon. - EXPECT_EQ(LookupNoBindings("GET", "/foo/other:verb/hello"), a); -} - -TEST_F(PathMatcherTest, RejectPartialMatches) { - MethodInfo* prefix_middle_suffix = AddGetPath("/prefix/middle/suffix"); - MethodInfo* prefix_middle = AddGetPath("/prefix/middle"); - MethodInfo* prefix = AddGetPath("/prefix"); - Build(); - - EXPECT_NE(nullptr, prefix_middle_suffix); - EXPECT_NE(nullptr, prefix_middle); - EXPECT_NE(nullptr, prefix); - - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle/suffix"), - prefix_middle_suffix); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle"), prefix_middle); - EXPECT_EQ(LookupNoBindings("GET", "/prefix"), prefix); - - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle/suffix/other"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle/other"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/other"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/other"), nullptr); -} - -TEST_F(PathMatcherTest, LookupReturnsNullIfMatcherEmpty) { - Build(); - EXPECT_EQ(LookupNoBindings("GET", "a/b/blue/foo"), nullptr); -} - -TEST_F(PathMatcherTest, LookupSimplePaths) { - MethodInfo* pms = AddGetPath("/prefix/middle/suffix"); - MethodInfo* pmo = AddGetPath("/prefix/middle/othersuffix"); - MethodInfo* pos = AddGetPath("/prefix/othermiddle/suffix"); - MethodInfo* oms = AddGetPath("/otherprefix/middle/suffix"); - MethodInfo* os = AddGetPath("/otherprefix/suffix"); - Build(); - - EXPECT_NE(nullptr, pms); - EXPECT_NE(nullptr, pmo); - EXPECT_NE(nullptr, pos); - EXPECT_NE(nullptr, oms); - EXPECT_NE(nullptr, os); - - EXPECT_EQ(LookupNoBindings("GET", "/prefix/not/a/path"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/not/othermiddle"), nullptr); - EXPECT_EQ(LookupNoBindings("GET", "/otherprefix/suffix/othermiddle"), - nullptr); - - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle/suffix"), pms); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/middle/othersuffix"), pmo); - EXPECT_EQ(LookupNoBindings("GET", "/prefix/othermiddle/suffix"), pos); - EXPECT_EQ(LookupNoBindings("GET", "/otherprefix/middle/suffix"), oms); - EXPECT_EQ(LookupNoBindings("GET", "/otherprefix/suffix"), os); - EXPECT_EQ(LookupNoBindings("GET", "/otherprefix/suffix?foo=bar"), os); -} - -TEST_F(PathMatcherTest, ReplacevoidForPath) { - const std::string path = "/foo/bar"; - auto first_mock_proc = AddGetPath(path); - EXPECT_NE(nullptr, first_mock_proc); - // Second call should fail - EXPECT_EQ(nullptr, AddGetPath(path)); - Build(); - - // Lookup result should get the first one. - EXPECT_EQ(LookupNoBindings("GET", path), first_mock_proc); -} - -TEST_F(PathMatcherTest, AllowDuplicate) { - MethodInfo* id = AddGetPath("/a/{id}"); - EXPECT_NE(nullptr, id); - // Second call should fail - EXPECT_EQ(nullptr, AddGetPath("/a/{name}")); - Build(); - - Bindings bindings; - // Lookup result should get the first one. - EXPECT_EQ(Lookup("GET", "/a/x", &bindings), id); -} - -TEST_F(PathMatcherTest, DuplicatedOptions) { - MethodInfo* get_id = AddPath("GET", "/a/{id}"); - MethodInfo* post_name = AddPath("POST", "/a/{name}"); - MethodInfo* options_id = AddPath("OPTIONS", "/a/{id}"); - EXPECT_EQ(nullptr, AddPath("OPTIONS", "/a/{name}")); - Build(); - - Bindings bindings; - // Lookup result should get the first one. - EXPECT_EQ(Lookup("OPTIONS", "/a/x", &bindings), options_id); - - EXPECT_EQ(Lookup("GET", "/a/x", &bindings), get_id); - EXPECT_EQ(Lookup("POST", "/a/x", &bindings), post_name); -} - -// If a path matches a complete branch of trie, but is longer than the branch -// (ie. the trie cannot match all the way to the end of the path), Lookup -// should return nullptr. -TEST_F(PathMatcherTest, LookupReturnsNullForOverspecifiedPath) { - EXPECT_NE(nullptr, AddGetPath("/a/b/c")); - EXPECT_NE(nullptr, AddGetPath("/a/b")); - Build(); - EXPECT_EQ(LookupNoBindings("GET", "/a/b/c/d"), nullptr); -} - -TEST_F(PathMatcherTest, ReturnNullvoidSharedPtrForUnderspecifiedPath) { - EXPECT_NE(nullptr, AddGetPath("/a/b/c/d")); - Build(); - EXPECT_EQ(LookupNoBindings("GET", "/a/b/c"), nullptr); -} - -TEST_F(PathMatcherTest, DifferentHttpMethod) { - auto ab = AddGetPath("/a/b"); - Build(); - EXPECT_NE(nullptr, ab); - EXPECT_EQ(LookupNoBindings("GET", "/a/b"), ab); - EXPECT_EQ(LookupNoBindings("POST", "/a/b"), nullptr); -} - -TEST_F(PathMatcherTest, BodyFieldPathTest) { - auto a = AddPathWithBodyFieldPath("GET", "/a", "b"); - auto cd = AddPathWithBodyFieldPath("GET", "/c/d", "e.f.g"); - Build(); - EXPECT_NE(nullptr, a); - EXPECT_NE(nullptr, cd); - std::string body_field_path; - EXPECT_EQ(LookupWithBodyFieldPath("GET", "/a", nullptr, &body_field_path), a); - EXPECT_EQ("b", body_field_path); - EXPECT_EQ(LookupWithBodyFieldPath("GET", "/c/d", nullptr, &body_field_path), - cd); - EXPECT_EQ("e.f.g", body_field_path); -} - -TEST_F(PathMatcherTest, VariableBindingsWithQueryParams) { - MethodInfo* a = AddGetPath("/a"); - MethodInfo* a_b = AddGetPath("/a/{x}/b"); - MethodInfo* a_b_c = AddGetPath("/a/{x}/b/{y}/c"); - Build(); - - EXPECT_NE(nullptr, a); - EXPECT_NE(nullptr, a_b); - EXPECT_NE(nullptr, a_b_c); - - Bindings bindings; - EXPECT_EQ(LookupWithParams("GET", "/a", "x=hello", &bindings), a); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "hello"}, - }), - bindings); - - EXPECT_EQ(LookupWithParams("GET", "/a/book/b", "y=shelf&z=author", &bindings), - a_b); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "book"}, - Binding{FieldPath{"y"}, "shelf"}, - Binding{FieldPath{"z"}, "author"}, - }), - bindings); - - EXPECT_EQ(LookupWithParams("GET", "/a/hello/b/endpoints/c", - "z=server&t=proxy", &bindings), - a_b_c); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "hello"}, - Binding{FieldPath{"y"}, "endpoints"}, - Binding{FieldPath{"z"}, "server"}, - Binding{FieldPath{"t"}, "proxy"}, - }), - bindings); -} - -TEST_F(PathMatcherTest, VariableBindingsWithQueryParamsEncoding) { - MethodInfo* a = AddGetPath("/a"); - Build(); - - EXPECT_NE(nullptr, a); - - Bindings bindings; - EXPECT_EQ(LookupWithParams("GET", "/a", "x=Hello%20world", &bindings), a); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "Hello world"}, - }), - bindings); - - EXPECT_EQ(LookupWithParams("GET", "/a", "x=%24%25%2F%20%0A", &bindings), a); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "$%/ \n"}, - }), - bindings); -} - -TEST_F(PathMatcherTest, VariableBindingsWithQueryParamsAndSystemParams) { - std::set system_params{"key", "api_key"}; - MethodInfo* a_b = AddPathWithSystemParams("GET", "/a/{x}/b", &system_params); - Build(); - - EXPECT_NE(nullptr, a_b); - - Bindings bindings; - EXPECT_EQ(LookupWithParams("GET", "/a/hello/b", "y=world&api_key=secret", - &bindings), - a_b); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "hello"}, - Binding{FieldPath{"y"}, "world"}, - }), - bindings); - EXPECT_EQ( - LookupWithParams("GET", "/a/hello/b", "key=secret&y=world", &bindings), - a_b); - EXPECT_EQ(Bindings({ - Binding{FieldPath{"x"}, "hello"}, - Binding{FieldPath{"y"}, "world"}, - }), - bindings); -} - -} // namespace - -} // namespace api_spec -} // namespace istio diff --git a/src/istio/authn/context.proto b/src/istio/authn/context.proto deleted file mode 100644 index 5ea3fdb67d5..00000000000 --- a/src/istio/authn/context.proto +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 Istio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package istio.authn; - -// Container to hold authenticated attributes from JWT. -message JwtPayload { - // This is a string of the issuer (iss) and subject (sub) claims within a - // JWT concatenated with “/” with a percent-encoded subject value. Example - // accounts.my-svc.com/104958560606 - string user = 1; - - // The intended audience(s) for this authentication information. This should - // reflect the audience (aud) claim within a JWT. Example - // [‘my-svc.com’, ‘scopes/read’] - repeated string audiences = 2; - - // The authorized presenter of the credential. This value should reflect the - // optional Authorized Presenter (azp) claim within a JWT or the OAuth2 client - // id. Example 123456789012.my-svc.com - string presenter = 3; - - // Only raw JWT string claims are kept. - map claims = 5; - - // All original claims in JsonString format, which can be parsed into json - // object (map) to access other claims that not cover with the string claims - // map above. - string raw_claims = 6; -} - -// Container to hold authenticated attributes from X509 (mTLS). -message X509Payload { - // Identity carries by X509 certification. This is extracted from SAN field - // for Istio provided certificate. - string user = 1; -}; - -message Payload { - oneof payload { - X509Payload x509 = 1; - JwtPayload jwt = 2; - } -} -message Result { - // Principal is set according to policy credential rule, from the peer - // identity (peer_user) or origin identity (origin.user). - string principal = 1; - - // Peer identity, result from peer authentication (for now, peer - // authentication outputs only identity). This is corresponding to - // Istio source.user Istio attribute - // (https://istio.io/docs/reference/config/mixer/attribute-vocabulary.html) - string peer_user = 2; - - // Origin authentication supports only JWT at the moment, so we can use - // JwtPayload for origin authenticated attributes. - JwtPayload origin = 3; -} diff --git a/src/istio/control/BUILD b/src/istio/control/BUILD deleted file mode 100644 index 9b7336e2635..00000000000 --- a/src/istio/control/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "common_lib", - srcs = [ - "attribute_names.cc", - "client_context_base.cc", - ], - hdrs = [ - "attribute_names.h", - "client_context_base.h", - "request_context.h", - ], - visibility = [":__subpackages__"], - deps = [ - "//external:mixer_client_config_cc_proto", - "//src/istio/mixerclient:mixerclient_lib", - "//src/istio/quota_config:config_parser_lib", - ], -) - -cc_library( - name = "mock_mixer_client", - hdrs = [ - "mock_mixer_client.h", - ], - visibility = [":__subpackages__"], - deps = [ - "//src/istio/mixerclient:mixerclient_lib", - ], -) diff --git a/src/istio/control/attribute_names.cc b/src/istio/control/attribute_names.cc deleted file mode 100644 index 953957998a9..00000000000 --- a/src/istio/control/attribute_names.cc +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "attribute_names.h" - -namespace istio { -namespace control { - -// Define attribute names -const char AttributeName::kSourceUser[] = "source.user"; -const char AttributeName::kSourcePrincipal[] = "source.principal"; - -const char AttributeName::kRequestHeaders[] = "request.headers"; -const char AttributeName::kRequestHost[] = "request.host"; -const char AttributeName::kRequestMethod[] = "request.method"; -const char AttributeName::kRequestPath[] = "request.path"; -const char AttributeName::kRequestReferer[] = "request.referer"; -const char AttributeName::kRequestScheme[] = "request.scheme"; -const char AttributeName::kRequestBodySize[] = "request.size"; -const char AttributeName::kRequestTotalSize[] = "request.total_size"; -const char AttributeName::kRequestTime[] = "request.time"; -const char AttributeName::kRequestUserAgent[] = "request.useragent"; -const char AttributeName::kRequestApiKey[] = "request.api_key"; - -const char AttributeName::kResponseCode[] = "response.code"; -const char AttributeName::kResponseDuration[] = "response.duration"; -const char AttributeName::kResponseHeaders[] = "response.headers"; -const char AttributeName::kResponseBodySize[] = "response.size"; -const char AttributeName::kResponseTotalSize[] = "response.total_size"; -const char AttributeName::kResponseTime[] = "response.time"; - -// TCP attributes -// Downstream tcp connection: source ip/port. -const char AttributeName::kSourceIp[] = "source.ip"; -const char AttributeName::kSourcePort[] = "source.port"; -// Upstream tcp connection: destionation ip/port. -const char AttributeName::kDestinationIp[] = "destination.ip"; -const char AttributeName::kDestinationPort[] = "destination.port"; -const char AttributeName::kConnectionReceviedBytes[] = - "connection.received.bytes"; -const char AttributeName::kConnectionReceviedTotalBytes[] = - "connection.received.bytes_total"; -const char AttributeName::kConnectionSendBytes[] = "connection.sent.bytes"; -const char AttributeName::kConnectionSendTotalBytes[] = - "connection.sent.bytes_total"; -const char AttributeName::kConnectionDuration[] = "connection.duration"; -const char AttributeName::kConnectionMtls[] = "connection.mtls"; -// Downstream TCP connection id. -const char AttributeName::kConnectionId[] = "connection.id"; -const char AttributeName::kConnectionEvent[] = "connection.event"; - -// Context attributes -const char AttributeName::kContextProtocol[] = "context.protocol"; -const char AttributeName::kContextTime[] = "context.time"; - -// Check error code and message. -const char AttributeName::kCheckErrorCode[] = "check.error_code"; -const char AttributeName::kCheckErrorMessage[] = "check.error_message"; - -// Check and Quota cache hit -const char AttributeName::kCheckCacheHit[] = "check.cache_hit"; -const char AttributeName::kQuotaCacheHit[] = "quota.cache_hit"; - -// Authentication attributes -const char AttributeName::kRequestAuthPrincipal[] = "request.auth.principal"; -const char AttributeName::kRequestAuthAudiences[] = "request.auth.audiences"; -const char AttributeName::kRequestAuthPresenter[] = "request.auth.presenter"; -const char AttributeName::kRequestAuthClaims[] = "request.auth.claims"; -const char AttributeName::kRequestAuthRawClaims[] = "request.auth.raw_claims"; - -} // namespace control -} // namespace istio diff --git a/src/istio/control/attribute_names.h b/src/istio/control/attribute_names.h deleted file mode 100644 index 2ed3dfdf46a..00000000000 --- a/src/istio/control/attribute_names.h +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_ATTRIBUTE_NAMES_H -#define ISTIO_CONTROL_ATTRIBUTE_NAMES_H - -#include - -namespace istio { -namespace control { - -// Define attribute names -struct AttributeName { - // source.user is replaced by source.principal - // https://github.com/istio/istio/issues/4689 - static const char kSourceUser[]; - static const char kSourcePrincipal[]; - - static const char kRequestHeaders[]; - static const char kRequestHost[]; - static const char kRequestMethod[]; - static const char kRequestPath[]; - static const char kRequestReferer[]; - static const char kRequestScheme[]; - static const char kRequestBodySize[]; - // Total size of request received, including request headers, body, and - // trailers. - static const char kRequestTotalSize[]; - static const char kRequestTime[]; - static const char kRequestUserAgent[]; - static const char kRequestApiKey[]; - - static const char kResponseCode[]; - static const char kResponseDuration[]; - static const char kResponseHeaders[]; - static const char kResponseBodySize[]; - // Total size of response sent, including response headers and body. - static const char kResponseTotalSize[]; - static const char kResponseTime[]; - - // TCP attributes - // Downstream tcp connection: source ip/port. - static const char kSourceIp[]; - static const char kSourcePort[]; - // Upstream tcp connection: destionation ip/port. - - static const char kDestinationIp[]; - static const char kDestinationPort[]; - static const char kConnectionReceviedBytes[]; - static const char kConnectionReceviedTotalBytes[]; - static const char kConnectionSendBytes[]; - static const char kConnectionSendTotalBytes[]; - static const char kConnectionDuration[]; - static const char kConnectionMtls[]; - static const char kConnectionId[]; - // Record TCP connection status: open, continue, close - static const char kConnectionEvent[]; - - // Context attributes - static const char kContextProtocol[]; - static const char kContextTime[]; - - // Check error code and message. - static const char kCheckErrorCode[]; - static const char kCheckErrorMessage[]; - - // Check and Quota cache hit - static const char kCheckCacheHit[]; - static const char kQuotaCacheHit[]; - - // Authentication attributes - static const char kRequestAuthPrincipal[]; - static const char kRequestAuthAudiences[]; - static const char kRequestAuthPresenter[]; - static const char kRequestAuthClaims[]; - static const char kRequestAuthRawClaims[]; -}; - -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_ATTRIBUTE_NAMES_H diff --git a/src/istio/control/client_context_base.cc b/src/istio/control/client_context_base.cc deleted file mode 100644 index 73a5cdad6fc..00000000000 --- a/src/istio/control/client_context_base.cc +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "client_context_base.h" -#include "include/istio/mixerclient/check_response.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/control/attribute_names.h" - -using ::google::protobuf::util::Status; -using ::istio::mixer::v1::config::client::NetworkFailPolicy; -using ::istio::mixer::v1::config::client::TransportConfig; -using ::istio::mixerclient::CancelFunc; -using ::istio::mixerclient::CheckOptions; -using ::istio::mixerclient::CheckResponseInfo; -using ::istio::mixerclient::DoneFunc; -using ::istio::mixerclient::Environment; -using ::istio::mixerclient::MixerClientOptions; -using ::istio::mixerclient::QuotaOptions; -using ::istio::mixerclient::ReportOptions; -using ::istio::mixerclient::Statistics; -using ::istio::mixerclient::TransportCheckFunc; - -namespace istio { -namespace control { -namespace { - -CheckOptions GetJustCheckOptions(const TransportConfig& config) { - if (config.disable_check_cache()) { - return CheckOptions(0); - } - return CheckOptions(); -} - -CheckOptions GetCheckOptions(const TransportConfig& config) { - auto options = GetJustCheckOptions(config); - if (config.has_network_fail_policy() && - config.network_fail_policy().policy() == NetworkFailPolicy::FAIL_CLOSE) { - options.network_fail_open = false; - } - return options; -} - -QuotaOptions GetQuotaOptions(const TransportConfig& config) { - if (config.disable_quota_cache()) { - return QuotaOptions(0, 1000); - } - return QuotaOptions(); -} - -ReportOptions GetReportOptions(const TransportConfig& config) { - if (config.disable_report_batch()) { - return ReportOptions(0, 1000); - } - return ReportOptions(); -} - -} // namespace - -ClientContextBase::ClientContextBase(const TransportConfig& config, - const Environment& env) { - MixerClientOptions options(GetCheckOptions(config), GetReportOptions(config), - GetQuotaOptions(config)); - options.env = env; - mixer_client_ = ::istio::mixerclient::CreateMixerClient(options); -} - -CancelFunc ClientContextBase::SendCheck(TransportCheckFunc transport, - DoneFunc on_done, - RequestContext* request) { - // Intercept the callback to save check status in request_context - auto local_on_done = [request, - on_done](const CheckResponseInfo& check_response_info) { - // save the check status code - request->check_status = check_response_info.response_status; - - utils::AttributesBuilder builder(&request->attributes); - builder.AddBool(AttributeName::kCheckCacheHit, - check_response_info.is_check_cache_hit); - builder.AddBool(AttributeName::kQuotaCacheHit, - check_response_info.is_quota_cache_hit); - on_done(check_response_info.response_status); - }; - - // TODO: add debug message - // GOOGLE_LOG(INFO) << "Check attributes: " << - // request->attributes.DebugString(); - return mixer_client_->Check(request->attributes, request->quotas, transport, - local_on_done); -} - -void ClientContextBase::SendReport(const RequestContext& request) { - // TODO: add debug message - // GOOGLE_LOG(INFO) << "Report attributes: " << - // request.attributes.DebugString(); - mixer_client_->Report(request.attributes); -} - -void ClientContextBase::GetStatistics(Statistics* stat) const { - mixer_client_->GetStatistics(stat); -} - -} // namespace control -} // namespace istio diff --git a/src/istio/control/client_context_base.h b/src/istio/control/client_context_base.h deleted file mode 100644 index d767265d38c..00000000000 --- a/src/istio/control/client_context_base.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_CLIENT_CONTEXT_BASE_H -#define ISTIO_CONTROL_CLIENT_CONTEXT_BASE_H - -#include "include/istio/mixerclient/client.h" -#include "mixer/v1/config/client/client_config.pb.h" -#include "request_context.h" - -namespace istio { -namespace control { - -// The global context object to hold the mixer client object -// to call Check/Report with cache. -class ClientContextBase { - public: - ClientContextBase( - const ::istio::mixer::v1::config::client::TransportConfig& config, - const ::istio::mixerclient::Environment& env); - - // A constructor for unit-test to pass in a mock mixer_client - ClientContextBase( - std::unique_ptr<::istio::mixerclient::MixerClient> mixer_client) - : mixer_client_(std::move(mixer_client)) {} - // virtual destrutor - virtual ~ClientContextBase() {} - - // Use mixer client object to make a Check call. - ::istio::mixerclient::CancelFunc SendCheck( - ::istio::mixerclient::TransportCheckFunc transport, - ::istio::mixerclient::DoneFunc on_done, RequestContext* request); - - // Use mixer client object to make a Report call. - void SendReport(const RequestContext& request); - - // Get statistics. - void GetStatistics(::istio::mixerclient::Statistics* stat) const; - - private: - // The mixer client object with check cache and report batch features. - std::unique_ptr<::istio::mixerclient::MixerClient> mixer_client_; -}; - -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_CLIENT_CONTEXT_BASE_H diff --git a/src/istio/control/http/BUILD b/src/istio/control/http/BUILD deleted file mode 100644 index 7911635b9f1..00000000000 --- a/src/istio/control/http/BUILD +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "control_lib", - srcs = [ - "attributes_builder.cc", - "attributes_builder.h", - "client_context.cc", - "client_context.h", - "controller_impl.cc", - "controller_impl.h", - "request_handler_impl.cc", - "request_handler_impl.h", - "service_context.cc", - "service_context.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//include/istio/control/http:headers_lib", - "//src/istio/api_spec:api_spec_lib", - "//src/istio/authn:context_proto", - "//src/istio/control:common_lib", - "//src/istio/utils:utils_lib", - ], -) - -cc_library( - name = "mock_headers", - hdrs = [ - "mock_check_data.h", - "mock_report_data.h", - ], - visibility = ["//visibility:public"], -) - -cc_test( - name = "attributes_builder_test", - size = "small", - srcs = [ - "attributes_builder_test.cc", - ], - linkstatic = 1, - deps = [ - ":control_lib", - ":mock_headers", - "//external:googletest_main", - ], -) - -cc_test( - name = "request_handler_impl_test", - size = "small", - srcs = [ - "request_handler_impl_test.cc", - ], - linkstatic = 1, - deps = [ - ":control_lib", - ":mock_headers", - "//external:googletest_main", - "//src/istio/control:mock_mixer_client", - ], -) diff --git a/src/istio/control/http/attributes_builder.cc b/src/istio/control/http/attributes_builder.cc deleted file mode 100644 index d85bc4f2bcf..00000000000 --- a/src/istio/control/http/attributes_builder.cc +++ /dev/null @@ -1,207 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/http/attributes_builder.h" - -#include "include/istio/utils/attributes_builder.h" -#include "include/istio/utils/status.h" -#include "src/istio/control/attribute_names.h" - -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_StringMap; - -namespace istio { -namespace control { -namespace http { - -void AttributesBuilder::ExtractRequestHeaderAttributes(CheckData *check_data) { - utils::AttributesBuilder builder(&request_->attributes); - std::map headers = check_data->GetRequestHeaders(); - builder.AddStringMap(AttributeName::kRequestHeaders, headers); - - struct TopLevelAttr { - CheckData::HeaderType header_type; - const std::string &name; - bool set_default; - const char *default_value; - }; - static TopLevelAttr attrs[] = { - {CheckData::HEADER_HOST, AttributeName::kRequestHost, true, ""}, - {CheckData::HEADER_METHOD, AttributeName::kRequestMethod, false, ""}, - {CheckData::HEADER_PATH, AttributeName::kRequestPath, true, ""}, - {CheckData::HEADER_REFERER, AttributeName::kRequestReferer, false, ""}, - {CheckData::HEADER_SCHEME, AttributeName::kRequestScheme, true, "http"}, - {CheckData::HEADER_USER_AGENT, AttributeName::kRequestUserAgent, false, - ""}, - }; - - for (const auto &it : attrs) { - std::string data; - if (check_data->FindHeaderByType(it.header_type, &data)) { - builder.AddString(it.name, data); - } else if (it.set_default) { - builder.AddString(it.name, it.default_value); - } - } -} - -void AttributesBuilder::ExtractAuthAttributes(CheckData *check_data) { - istio::authn::Result authn_result; - if (check_data->GetAuthenticationResult(&authn_result)) { - utils::AttributesBuilder builder(&request_->attributes); - if (!authn_result.principal().empty()) { - builder.AddString(AttributeName::kRequestAuthPrincipal, - authn_result.principal()); - } - if (!authn_result.peer_user().empty()) { - // TODO(diemtvu): remove kSourceUser once migration to source.principal is - // over. https://github.com/istio/istio/issues/4689 - builder.AddString(AttributeName::kSourceUser, authn_result.peer_user()); - builder.AddString(AttributeName::kSourcePrincipal, - authn_result.peer_user()); - } - if (authn_result.has_origin()) { - const auto &origin = authn_result.origin(); - if (!origin.audiences().empty()) { - // TODO(diemtvu): this should be send as repeated field once mixer - // support string_list (https://github.com/istio/istio/issues/2802) For - // now, just use the first value. - builder.AddString(AttributeName::kRequestAuthAudiences, - origin.audiences(0)); - } - if (!origin.presenter().empty()) { - builder.AddString(AttributeName::kRequestAuthPresenter, - origin.presenter()); - } - if (!origin.claims().empty()) { - builder.AddProtobufStringMap(AttributeName::kRequestAuthClaims, - origin.claims()); - } - if (!origin.raw_claims().empty()) { - builder.AddString(AttributeName::kRequestAuthRawClaims, - origin.raw_claims()); - } - } - return; - } - - // Fallback to extract from jwt filter directly. This can be removed once - // authn filter is in place. - std::map payload; - utils::AttributesBuilder builder(&request_->attributes); - if (check_data->GetJWTPayload(&payload) && !payload.empty()) { - // Populate auth attributes. - if (payload.count("iss") > 0 && payload.count("sub") > 0) { - builder.AddString(AttributeName::kRequestAuthPrincipal, - payload["iss"] + "/" + payload["sub"]); - } - if (payload.count("aud") > 0) { - builder.AddString(AttributeName::kRequestAuthAudiences, payload["aud"]); - } - if (payload.count("azp") > 0) { - builder.AddString(AttributeName::kRequestAuthPresenter, payload["azp"]); - } - builder.AddStringMap(AttributeName::kRequestAuthClaims, payload); - } - std::string source_user; - if (check_data->GetSourceUser(&source_user)) { - // TODO(diemtvu): remove kSourceUser once migration to source.principal is - // over. https://github.com/istio/istio/issues/4689 - builder.AddString(AttributeName::kSourceUser, source_user); - builder.AddString(AttributeName::kSourcePrincipal, source_user); - } -} // namespace http - -void AttributesBuilder::ExtractForwardedAttributes(CheckData *check_data) { - std::string forwarded_data; - if (!check_data->ExtractIstioAttributes(&forwarded_data)) { - return; - } - Attributes v2_format; - if (v2_format.ParseFromString(forwarded_data)) { - request_->attributes.MergeFrom(v2_format); - return; - } -} - -void AttributesBuilder::ExtractCheckAttributes(CheckData *check_data) { - ExtractRequestHeaderAttributes(check_data); - ExtractAuthAttributes(check_data); - - utils::AttributesBuilder builder(&request_->attributes); - - std::string source_ip; - int source_port; - if (check_data->GetSourceIpPort(&source_ip, &source_port)) { - builder.AddBytes(AttributeName::kSourceIp, source_ip); - builder.AddInt64(AttributeName::kSourcePort, source_port); - } - builder.AddBool(AttributeName::kConnectionMtls, check_data->IsMutualTLS()); - - builder.AddTimestamp(AttributeName::kRequestTime, - std::chrono::system_clock::now()); - builder.AddString(AttributeName::kContextProtocol, "http"); -} - -void AttributesBuilder::ForwardAttributes(const Attributes &forward_attributes, - HeaderUpdate *header_update) { - std::string str; - forward_attributes.SerializeToString(&str); - header_update->AddIstioAttributes(str); -} - -void AttributesBuilder::ExtractReportAttributes(ReportData *report_data) { - utils::AttributesBuilder builder(&request_->attributes); - - std::string dest_ip; - int dest_port; - if (report_data->GetDestinationIpPort(&dest_ip, &dest_port)) { - // Do not overwrite DestionationIP if it has already been set. - if (!builder.HasAttribute(AttributeName::kDestinationIp)) { - builder.AddBytes(AttributeName::kDestinationIp, dest_ip); - } - builder.AddInt64(AttributeName::kDestinationPort, dest_port); - } - - std::map headers = - report_data->GetResponseHeaders(); - builder.AddStringMap(AttributeName::kResponseHeaders, headers); - - builder.AddTimestamp(AttributeName::kResponseTime, - std::chrono::system_clock::now()); - - ReportData::ReportInfo info; - report_data->GetReportInfo(&info); - builder.AddInt64(AttributeName::kRequestBodySize, info.request_body_size); - builder.AddInt64(AttributeName::kResponseBodySize, info.response_body_size); - builder.AddInt64(AttributeName::kRequestTotalSize, info.request_total_size); - builder.AddInt64(AttributeName::kResponseTotalSize, info.response_total_size); - builder.AddDuration(AttributeName::kResponseDuration, info.duration); - if (!request_->check_status.ok()) { - builder.AddInt64( - AttributeName::kResponseCode, - utils::StatusHttpCode(request_->check_status.error_code())); - builder.AddInt64(AttributeName::kCheckErrorCode, - request_->check_status.error_code()); - builder.AddString(AttributeName::kCheckErrorMessage, - request_->check_status.ToString()); - } else { - builder.AddInt64(AttributeName::kResponseCode, info.response_code); - } -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/attributes_builder.h b/src/istio/control/http/attributes_builder.h deleted file mode 100644 index a8346f7f5ec..00000000000 --- a/src/istio/control/http/attributes_builder.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_ATTRIBUTES_BUILDER_H -#define ISTIO_CONTROL_HTTP_ATTRIBUTES_BUILDER_H - -#include "include/istio/control/http/check_data.h" -#include "include/istio/control/http/report_data.h" -#include "src/istio/control/request_context.h" - -namespace istio { -namespace control { -namespace http { - -// The context for each HTTP request. -class AttributesBuilder { - public: - AttributesBuilder(RequestContext* request) : request_(request) {} - - // Extract forwarded attributes from HTTP header. - void ExtractForwardedAttributes(CheckData* check_data); - // Forward attributes to upstream proxy. - static void ForwardAttributes( - const ::istio::mixer::v1::Attributes& attributes, - HeaderUpdate* header_update); - - // Extract attributes for Check call. - void ExtractCheckAttributes(CheckData* check_data); - // Extract attributes for Report call. - void ExtractReportAttributes(ReportData* report_data); - - private: - // Extract HTTP header attributes - void ExtractRequestHeaderAttributes(CheckData* check_data); - // Extract authentication attributes for Check call. Going forward, this - // function will use authentication result (from authn filter), which will set - // all authenticated attributes (including source_user, request.auth.*). - // During the transition (i.e authn filter is not added to sidecar), this - // function will also look up the (jwt) payload when authentication result is - // not available. - void ExtractAuthAttributes(CheckData* check_data); - - // The request context object. - RequestContext* request_; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_ATTRIBUTES_BUILDER_H diff --git a/src/istio/control/http/attributes_builder_test.cc b/src/istio/control/http/attributes_builder_test.cc deleted file mode 100644 index 9ac9019442e..00000000000 --- a/src/istio/control/http/attributes_builder_test.cc +++ /dev/null @@ -1,518 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/http/attributes_builder.h" - -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/message_differencer.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/control/attribute_names.h" -#include "src/istio/control/http/mock_check_data.h" -#include "src/istio/control/http/mock_report_data.h" - -using ::google::protobuf::TextFormat; -using ::google::protobuf::util::MessageDifferencer; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_StringMap; - -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace control { -namespace http { -namespace { - -const char kCheckAttributes[] = R"( -attributes { - key: "context.protocol" - value { - string_value: "http" - } -} -attributes { - key: "request.headers" - value { - string_map_value { - entries { - key: "host" - value: "localhost" - } - entries { - key: "path" - value: "/books" - } - } - } -} -attributes { - key: "request.host" - value { - string_value: "localhost" - } -} -attributes { - key: "request.path" - value { - string_value: "/books" - } -} -attributes { - key: "request.scheme" - value { - string_value: "http" - } -} -attributes { - key: "request.time" - value { - timestamp_value { - } - } -} -attributes { - key: "source.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "source.port" - value { - int64_value: 8080 - } -} -attributes { - key: "connection.mtls" - value { - bool_value: true - } -} -attributes { - key: "source.principal" - value { - string_value: "test_user" - } -} -attributes { - key: "source.user" - value { - string_value: "test_user" - } -} -attributes { - key: "request.auth.audiences" - value { - string_value: "thisisaud" - } -} -attributes { - key: "request.auth.claims" - value { - string_map_value { - entries { - key: "aud" - value: "thisisaud" - } - entries { - key: "azp" - value: "thisisazp" - } - entries { - key: "email" - value: "thisisemail@email.com" - } - entries { - key: "exp" - value: "5112754205" - } - entries { - key: "iat" - value: "1512754205" - } - entries { - key: "iss" - value: "thisisiss" - } - entries { - key: "sub" - value: "thisissub" - } - } - } -} -attributes { - key: "request.auth.presenter" - value { - string_value: "thisisazp" - } -} -attributes { - key: "request.auth.principal" - value { - string_value: "thisisiss/thisissub" - } -} -)"; - -const char kReportAttributes[] = R"( -attributes { - key: "request.size" - value { - int64_value: 100 - } -} -attributes { - key: "response.code" - value { - int64_value: 404 - } -} -attributes { - key: "response.duration" - value { - duration_value { - nanos: 1 - } - } -} -attributes { - key: "destination.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "destination.port" - value { - int64_value: 8080 - } -} -attributes { - key: "response.headers" - value { - string_map_value { - entries { - key: "content-length" - value: "123456" - } - entries { - key: "server" - value: "my-server" - } - } - } -} -attributes { - key: "response.size" - value { - int64_value: 200 - } -} -attributes { - key: "response.total_size" - value { - int64_value: 120 - } -} -attributes { - key: "request.total_size" - value { - int64_value: 240 - } -} -attributes { - key: "response.time" - value { - timestamp_value { - } - } -} -)"; - -void ClearContextTime(const std::string &name, RequestContext *request) { - // Override timestamp with - - utils::AttributesBuilder builder(&request->attributes); - std::chrono::time_point time0; - builder.AddTimestamp(name, time0); -} - -void SetDestinationIp(RequestContext *request, const std::string &ip) { - utils::AttributesBuilder builder(&request->attributes); - builder.AddBytes(AttributeName::kDestinationIp, ip); -} - -TEST(AttributesBuilderTest, TestExtractForwardedAttributes) { - Attributes attr; - (*attr.mutable_attributes())["test_key"].set_string_value("test_value"); - - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, ExtractIstioAttributes(_)) - .WillOnce(Invoke([&attr](std::string *data) -> bool { - attr.SerializeToString(data); - return true; - })); - - RequestContext request; - AttributesBuilder builder(&request); - builder.ExtractForwardedAttributes(&mock_data); - EXPECT_TRUE(MessageDifferencer::Equals(request.attributes, attr)); -} - -TEST(AttributesBuilderTest, TestForwardAttributes) { - Attributes forwarded_attr; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_header, AddIstioAttributes(_)) - .WillOnce(Invoke([&forwarded_attr](const std::string &data) { - EXPECT_TRUE(forwarded_attr.ParseFromString(data)); - })); - - Attributes origin_attr; - (*origin_attr.mutable_attributes())["test_key"].set_string_value( - "test_value"); - - AttributesBuilder::ForwardAttributes(origin_attr, &mock_header); - EXPECT_TRUE(MessageDifferencer::Equals(origin_attr, forwarded_attr)); -} - -TEST(AttributesBuilderTest, TestCheckAttributes) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)) - .WillOnce(Invoke([](std::string *ip, int *port) -> bool { - *ip = "1.2.3.4"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, GetSourceUser(_)) - .WillOnce(Invoke([](std::string *user) -> bool { - *user = "test_user"; - return true; - })); - EXPECT_CALL(mock_data, IsMutualTLS()).WillOnce(Invoke([]() -> bool { - return true; - })); - EXPECT_CALL(mock_data, GetRequestHeaders()) - .WillOnce(Invoke([]() -> std::map { - std::map map; - map["path"] = "/books"; - map["host"] = "localhost"; - return map; - })); - EXPECT_CALL(mock_data, FindHeaderByType(_, _)) - .WillRepeatedly(Invoke( - [](CheckData::HeaderType header_type, std::string *value) -> bool { - if (header_type == CheckData::HEADER_PATH) { - *value = "/books"; - return true; - } else if (header_type == CheckData::HEADER_HOST) { - *value = "localhost"; - return true; - } - return false; - })); - EXPECT_CALL(mock_data, GetAuthenticationResult(_)) - .WillOnce(testing::Return(false)); - EXPECT_CALL(mock_data, GetJWTPayload(_)) - .WillOnce(Invoke([](std::map *payload) -> bool { - (*payload)["iss"] = "thisisiss"; - (*payload)["sub"] = "thisissub"; - (*payload)["aud"] = "thisisaud"; - (*payload)["azp"] = "thisisazp"; - (*payload)["email"] = "thisisemail@email.com"; - (*payload)["iat"] = "1512754205"; - (*payload)["exp"] = "5112754205"; - return true; - })); - - RequestContext request; - AttributesBuilder builder(&request); - builder.ExtractCheckAttributes(&mock_data); - - ClearContextTime(AttributeName::kRequestTime, &request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - Attributes expected_attributes; - ASSERT_TRUE( - TextFormat::ParseFromString(kCheckAttributes, &expected_attributes)); - EXPECT_TRUE( - MessageDifferencer::Equals(request.attributes, expected_attributes)); -} - -TEST(AttributesBuilderTest, TestCheckAttributesWithAuthNResult) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)) - .WillOnce(Invoke([](std::string *ip, int *port) -> bool { - *ip = "1.2.3.4"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, IsMutualTLS()).WillOnce(Invoke([]() -> bool { - return true; - })); - EXPECT_CALL(mock_data, GetRequestHeaders()) - .WillOnce(Invoke([]() -> std::map { - std::map map; - map["path"] = "/books"; - map["host"] = "localhost"; - return map; - })); - EXPECT_CALL(mock_data, FindHeaderByType(_, _)) - .WillRepeatedly(Invoke( - [](CheckData::HeaderType header_type, std::string *value) -> bool { - if (header_type == CheckData::HEADER_PATH) { - *value = "/books"; - return true; - } else if (header_type == CheckData::HEADER_HOST) { - *value = "localhost"; - return true; - } - return false; - })); - EXPECT_CALL(mock_data, GetAuthenticationResult(_)) - .WillOnce(Invoke([](istio::authn::Result *result) -> bool { - result->set_principal("thisisiss/thisissub"); - result->set_peer_user("test_user"); - result->mutable_origin()->add_audiences("thisisaud"); - result->mutable_origin()->set_presenter("thisisazp"); - (*result->mutable_origin()->mutable_claims())["iss"] = "thisisiss"; - (*result->mutable_origin()->mutable_claims())["sub"] = "thisissub"; - (*result->mutable_origin()->mutable_claims())["aud"] = "thisisaud"; - (*result->mutable_origin()->mutable_claims())["azp"] = "thisisazp"; - (*result->mutable_origin()->mutable_claims())["email"] = - "thisisemail@email.com"; - (*result->mutable_origin()->mutable_claims())["iat"] = "1512754205"; - (*result->mutable_origin()->mutable_claims())["exp"] = "5112754205"; - result->mutable_origin()->set_raw_claims("test_raw_claims"); - return true; - })); - - RequestContext request; - AttributesBuilder builder(&request); - builder.ExtractCheckAttributes(&mock_data); - - ClearContextTime(AttributeName::kRequestTime, &request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - Attributes expected_attributes; - ASSERT_TRUE( - TextFormat::ParseFromString(kCheckAttributes, &expected_attributes)); - // kCheckAttributes is also used in TestCheckAttributes, which is a deprecated - // way to construct mixer attribute (it was a fallback when authn filter is - // not available, which can be removed after 0.8). For now, modifying expected - // data manually for this test. - (*expected_attributes - .mutable_attributes())[AttributeName::kRequestAuthRawClaims] - .set_string_value("test_raw_claims"); - - EXPECT_TRUE( - MessageDifferencer::Equals(request.attributes, expected_attributes)); -} - -TEST(AttributesBuilderTest, TestReportAttributes) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetDestinationIpPort(_, _)) - .WillOnce(Invoke([](std::string *ip, int *port) -> bool { - *ip = "1.2.3.4"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, GetResponseHeaders()) - .WillOnce(Invoke([]() -> std::map { - std::map map; - map["content-length"] = "123456"; - map["server"] = "my-server"; - return map; - })); - EXPECT_CALL(mock_data, GetReportInfo(_)) - .WillOnce(Invoke([](ReportData::ReportInfo *info) { - info->request_body_size = 100; - info->response_body_size = 200; - info->response_total_size = 120; - info->request_total_size = 240; - info->duration = std::chrono::nanoseconds(1); - info->response_code = 404; - })); - - RequestContext request; - AttributesBuilder builder(&request); - builder.ExtractReportAttributes(&mock_data); - - ClearContextTime(AttributeName::kResponseTime, &request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - Attributes expected_attributes; - ASSERT_TRUE( - TextFormat::ParseFromString(kReportAttributes, &expected_attributes)); - EXPECT_TRUE( - MessageDifferencer::Equals(request.attributes, expected_attributes)); -} - -TEST(AttributesBuilderTest, TestReportAttributesWithDestIP) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetDestinationIpPort(_, _)) - .WillOnce(Invoke([](std::string *ip, int *port) -> bool { - *ip = "2.3.4.5"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, GetResponseHeaders()) - .WillOnce(Invoke([]() -> std::map { - std::map map; - map["content-length"] = "123456"; - map["server"] = "my-server"; - return map; - })); - EXPECT_CALL(mock_data, GetReportInfo(_)) - .WillOnce(Invoke([](ReportData::ReportInfo *info) { - info->request_body_size = 100; - info->response_body_size = 200; - info->response_total_size = 120; - info->request_total_size = 240; - info->duration = std::chrono::nanoseconds(1); - info->response_code = 404; - })); - - RequestContext request; - SetDestinationIp(&request, "1.2.3.4"); - AttributesBuilder builder(&request); - builder.ExtractReportAttributes(&mock_data); - - ClearContextTime(AttributeName::kResponseTime, &request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - Attributes expected_attributes; - ASSERT_TRUE( - TextFormat::ParseFromString(kReportAttributes, &expected_attributes)); - EXPECT_TRUE( - MessageDifferencer::Equals(request.attributes, expected_attributes)); -} - -} // namespace -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/client_context.cc b/src/istio/control/http/client_context.cc deleted file mode 100644 index 02b108ea89f..00000000000 --- a/src/istio/control/http/client_context.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/http/client_context.h" - -using ::istio::mixer::v1::config::client::ServiceConfig; - -namespace istio { -namespace control { -namespace http { - -ClientContext::ClientContext(const Controller::Options& data) - : ClientContextBase(data.config.transport(), data.env), - config_(data.config), - service_config_cache_size_(data.service_config_cache_size) {} - -ClientContext::ClientContext( - std::unique_ptr<::istio::mixerclient::MixerClient> mixer_client, - const ::istio::mixer::v1::config::client::HttpClientConfig& config, - int service_config_cache_size) - : ClientContextBase(std::move(mixer_client)), - config_(config), - service_config_cache_size_(service_config_cache_size) {} - -const std::string& ClientContext::GetServiceName( - const std::string& service_name) const { - if (service_name.empty()) { - return config_.default_destination_service(); - } - const auto& config_map = config_.service_configs(); - auto it = config_map.find(service_name); - if (it == config_map.end()) { - return config_.default_destination_service(); - } - return service_name; -} - -// Get the service config by the name. -const ServiceConfig* ClientContext::GetServiceConfig( - const std::string& service_name) const { - const auto& config_map = config_.service_configs(); - auto it = config_map.find(service_name); - if (it != config_map.end()) { - return &it->second; - } - return nullptr; -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/client_context.h b/src/istio/control/http/client_context.h deleted file mode 100644 index c7966078a14..00000000000 --- a/src/istio/control/http/client_context.h +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_CLIENT_CONTEXT_H -#define ISTIO_CONTROL_HTTP_CLIENT_CONTEXT_H - -#include "include/istio/control/http/controller.h" -#include "src/istio/control/client_context_base.h" - -namespace istio { -namespace control { -namespace http { - -// The global context object to hold: -// * the mixer client config -// * the mixer client object to call Check/Report with cache. -class ClientContext : public ClientContextBase { - public: - ClientContext(const Controller::Options& data); - // A constructor for unit-test to pass in a mock mixer_client - ClientContext( - std::unique_ptr<::istio::mixerclient::MixerClient> mixer_client, - const ::istio::mixer::v1::config::client::HttpClientConfig& config, - int service_config_cache_size); - - // Retrieve mixer client config. - const ::istio::mixer::v1::config::client::HttpClientConfig& config() const { - return config_; - } - - // Get valid service name in the config map. - // If input service name is in the map, use it, otherwise, use the default - // one. - const std::string& GetServiceName(const std::string& service_name) const; - - // Get the service config by the name. - const ::istio::mixer::v1::config::client::ServiceConfig* GetServiceConfig( - const std::string& service_name) const; - - // Get the service config cache size - int service_config_cache_size() const { return service_config_cache_size_; } - - private: - // The http client config. - const ::istio::mixer::v1::config::client::HttpClientConfig& config_; - - // The service config cache size - int service_config_cache_size_; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_CLIENT_CONTEXT_H diff --git a/src/istio/control/http/controller_impl.cc b/src/istio/control/http/controller_impl.cc deleted file mode 100644 index a0f687cb82b..00000000000 --- a/src/istio/control/http/controller_impl.cc +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/http/controller_impl.h" -#include "src/istio/control/http/request_handler_impl.h" - -using ::istio::mixer::v1::config::client::ServiceConfig; -using ::istio::mixerclient::Statistics; - -namespace istio { -namespace control { -namespace http { - -namespace { -// The service context cache size. -const int kServiceContextCacheSize = 1000; -} // namespace - -ControllerImpl::ControllerImpl(std::shared_ptr client_context) - : client_context_(client_context) { - int cache_size = client_context_->service_config_cache_size(); - if (cache_size <= 0) { - cache_size = kServiceContextCacheSize; - } - service_context_cache_.reset(new LRUCache(cache_size)); -} - -ControllerImpl::~ControllerImpl() { service_context_cache_->RemoveAll(); } - -bool ControllerImpl::LookupServiceConfig(const std::string& service_config_id) { - LRUCache::ScopedLookup lookup(service_context_cache_.get(), - service_config_id); - return lookup.Found(); -} - -void ControllerImpl::AddServiceConfig( - const std::string& service_config_id, - const ::istio::mixer::v1::config::client::ServiceConfig& config) { - CacheElem* cache_elem = new CacheElem; - cache_elem->service_context = - std::make_shared(client_context_, &config); - service_context_cache_->Insert(service_config_id, cache_elem, 1); -} - -std::unique_ptr ControllerImpl::CreateRequestHandler( - const PerRouteConfig& per_route_config) { - return std::unique_ptr( - new RequestHandlerImpl(GetServiceContext(per_route_config))); -} - -void ControllerImpl::GetStatistics(Statistics* stat) const { - client_context_->GetStatistics(stat); -} - -std::shared_ptr ControllerImpl::GetServiceContext( - const PerRouteConfig& config) { - if (!config.service_config_id.empty()) { - LRUCache::ScopedLookup lookup(service_context_cache_.get(), - config.service_config_id); - if (lookup.Found()) { - return lookup.value()->service_context; - } - } - - const std::string& origin_name = config.destination_service; - auto service_context = service_context_map_[origin_name]; - if (!service_context) { - // Get the valid service name from service_configs map. - auto valid_name = client_context_->GetServiceName(origin_name); - if (valid_name != origin_name) { - service_context = service_context_map_[valid_name]; - } - if (!service_context) { - service_context = std::make_shared( - client_context_, client_context_->GetServiceConfig(valid_name)); - service_context_map_[valid_name] = service_context; - } - if (valid_name != origin_name) { - service_context_map_[origin_name] = service_context; - } - } - return service_context; -} - -std::unique_ptr Controller::Create(const Options& data) { - return std::unique_ptr( - new ControllerImpl(std::make_shared(data))); -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/controller_impl.h b/src/istio/control/http/controller_impl.h deleted file mode 100644 index 090e4b8cf05..00000000000 --- a/src/istio/control/http/controller_impl.h +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_CONTROLLER_IMPL_H -#define ISTIO_CONTROL_HTTP_CONTROLLER_IMPL_H - -#include -#include - -#include "include/istio/control/http/controller.h" -#include "include/istio/utils/simple_lru_cache.h" -#include "include/istio/utils/simple_lru_cache_inl.h" -#include "src/istio/control/http/client_context.h" -#include "src/istio/control/http/service_context.h" - -namespace istio { -namespace control { -namespace http { - -// The class to implement Controller interface. -class ControllerImpl : public Controller { - public: - ControllerImpl(std::shared_ptr client_context); - ~ControllerImpl(); - - // Lookup a service config by its config id. Return true if found. - bool LookupServiceConfig(const std::string& service_config_id) override; - - // Add a new service config. - void AddServiceConfig( - const std::string& service_config_id, - const ::istio::mixer::v1::config::client::ServiceConfig& config) override; - - // Creates a HTTP request handler - std::unique_ptr CreateRequestHandler( - const PerRouteConfig& per_route_config) override; - - // Get statistics. - void GetStatistics(::istio::mixerclient::Statistics* stat) const override; - - private: - // Create service config context for HTTP. - std::shared_ptr GetServiceContext( - const PerRouteConfig& per_route_config); - - // The client context object to hold client config and client cache. - std::shared_ptr client_context_; - - // The map to cache service context. key is destination.service - std::unordered_map> - service_context_map_; - - // per-route service config may be changed overtime. A LRU cacahe is used to - // store used service contexts. ServiceContext initialization is expensive. - // This cache helps reducing number of ServiceContext creation. - // The cache has fixed size to control the memory usage. The oldest ones - // will be purged if the size limit is reached. - struct CacheElem { - std::shared_ptr service_context; - }; - using LRUCache = ::istio::utils::SimpleLRUCache; - std::unique_ptr service_context_cache_; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_CONTROLLER_IMPL_H diff --git a/src/istio/control/http/mock_check_data.h b/src/istio/control/http/mock_check_data.h deleted file mode 100644 index 9fc44f20d71..00000000000 --- a/src/istio/control/http/mock_check_data.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_MOCK_CHECK_DATA_H -#define ISTIO_CONTROL_HTTP_MOCK_CHECK_DATA_H - -#include "gmock/gmock.h" -#include "include/istio/control/http/check_data.h" - -namespace istio { -namespace control { -namespace http { - -// The mock object for CheckData interface. -class MockCheckData : public CheckData { - public: - MOCK_CONST_METHOD1(ExtractIstioAttributes, bool(std::string *data)); - - MOCK_CONST_METHOD2(GetSourceIpPort, bool(std::string *ip, int *port)); - MOCK_CONST_METHOD1(GetSourceUser, bool(std::string *user)); - MOCK_CONST_METHOD0(GetRequestHeaders, std::map()); - MOCK_CONST_METHOD2(FindHeaderByType, - bool(HeaderType header_type, std::string *value)); - MOCK_CONST_METHOD2(FindHeaderByName, - bool(const std::string &name, std::string *value)); - MOCK_CONST_METHOD2(FindQueryParameter, - bool(const std::string &name, std::string *value)); - MOCK_CONST_METHOD2(FindCookie, - bool(const std::string &name, std::string *value)); - MOCK_CONST_METHOD1(GetJWTPayload, - bool(std::map *payload)); - MOCK_CONST_METHOD1(GetAuthenticationResult, - bool(istio::authn::Result *result)); - MOCK_CONST_METHOD0(IsMutualTLS, bool()); -}; - -// The mock object for HeaderUpdate interface. -class MockHeaderUpdate : public HeaderUpdate { - public: - MOCK_METHOD0(RemoveIstioAttributes, void()); - MOCK_METHOD1(AddIstioAttributes, void(const std::string &data)); -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_MOCK_CHECK_DATA_H diff --git a/src/istio/control/http/mock_report_data.h b/src/istio/control/http/mock_report_data.h deleted file mode 100644 index 7c125001ed0..00000000000 --- a/src/istio/control/http/mock_report_data.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_MOCK_REPORT_DATA_H -#define ISTIO_CONTROL_HTTP_MOCK_REPORT_DATA_H - -#include "gmock/gmock.h" -#include "include/istio/control/http/report_data.h" - -namespace istio { -namespace control { -namespace http { - -// The mock object for ReportData interface. -class MockReportData : public ReportData { - public: - MOCK_CONST_METHOD0(GetResponseHeaders, std::map()); - MOCK_CONST_METHOD1(GetReportInfo, void(ReportInfo* info)); - MOCK_CONST_METHOD2(GetDestinationIpPort, bool(std::string* ip, int* port)); -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_MOCK_REPORT_DATA_H diff --git a/src/istio/control/http/request_handler_impl.cc b/src/istio/control/http/request_handler_impl.cc deleted file mode 100644 index 080e4a2c5bb..00000000000 --- a/src/istio/control/http/request_handler_impl.cc +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/http/request_handler_impl.h" -#include "src/istio/control/http/attributes_builder.h" - -using ::google::protobuf::util::Status; -using ::istio::mixerclient::CancelFunc; -using ::istio::mixerclient::DoneFunc; -using ::istio::mixerclient::TransportCheckFunc; -using ::istio::quota_config::Requirement; - -namespace istio { -namespace control { -namespace http { - -RequestHandlerImpl::RequestHandlerImpl( - std::shared_ptr service_context) - : service_context_(service_context) {} - -void RequestHandlerImpl::ExtractRequestAttributes(CheckData* check_data) { - if (service_context_->enable_mixer_check() || - service_context_->enable_mixer_report()) { - service_context_->AddStaticAttributes(&request_context_); - - AttributesBuilder builder(&request_context_); - builder.ExtractForwardedAttributes(check_data); - builder.ExtractCheckAttributes(check_data); - - service_context_->AddApiAttributes(check_data, &request_context_); - } -} - -CancelFunc RequestHandlerImpl::Check(CheckData* check_data, - HeaderUpdate* header_update, - TransportCheckFunc transport, - DoneFunc on_done) { - ExtractRequestAttributes(check_data); - - if (service_context_->client_context()->config().has_forward_attributes()) { - AttributesBuilder::ForwardAttributes( - service_context_->client_context()->config().forward_attributes(), - header_update); - } else { - header_update->RemoveIstioAttributes(); - } - - if (!service_context_->enable_mixer_check()) { - on_done(Status::OK); - return nullptr; - } - - service_context_->AddQuotas(&request_context_); - - return service_context_->client_context()->SendCheck(transport, on_done, - &request_context_); -} - -// Make remote report call. -void RequestHandlerImpl::Report(ReportData* report_data) { - if (!service_context_->enable_mixer_report()) { - return; - } - AttributesBuilder builder(&request_context_); - builder.ExtractReportAttributes(report_data); - - service_context_->client_context()->SendReport(request_context_); -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/request_handler_impl.h b/src/istio/control/http/request_handler_impl.h deleted file mode 100644 index 3cdc9c9ff5b..00000000000 --- a/src/istio/control/http/request_handler_impl.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_REQUEST_HANDLER_IMPL_H -#define ISTIO_CONTROL_HTTP_REQUEST_HANDLER_IMPL_H - -#include "include/istio/control/http/request_handler.h" -#include "src/istio/control/http/client_context.h" -#include "src/istio/control/http/service_context.h" -#include "src/istio/control/request_context.h" - -namespace istio { -namespace control { -namespace http { - -// The class to implement HTTPRequestHandler interface. -class RequestHandlerImpl : public RequestHandler { - public: - RequestHandlerImpl(std::shared_ptr service_context); - - // Makes a Check call. - ::istio::mixerclient::CancelFunc Check( - CheckData* check_data, HeaderUpdate* header_update, - ::istio::mixerclient::TransportCheckFunc transport, - ::istio::mixerclient::DoneFunc on_done) override; - - // Make a Report call. - void Report(ReportData* report_data) override; - - void ExtractRequestAttributes(CheckData* check_data) override; - - private: - // The request context object. - RequestContext request_context_; - - // The service context. - std::shared_ptr service_context_; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_REQUEST_HANDLER_IMPL_H diff --git a/src/istio/control/http/request_handler_impl_test.cc b/src/istio/control/http/request_handler_impl_test.cc deleted file mode 100644 index b7c3f569e9e..00000000000 --- a/src/istio/control/http/request_handler_impl_test.cc +++ /dev/null @@ -1,448 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "google/protobuf/text_format.h" -#include "gtest/gtest.h" -#include "src/istio/control/attribute_names.h" -#include "src/istio/control/http/client_context.h" -#include "src/istio/control/http/controller_impl.h" -#include "src/istio/control/http/mock_check_data.h" -#include "src/istio/control/http/mock_report_data.h" -#include "src/istio/control/mock_mixer_client.h" - -using ::google::protobuf::TextFormat; -using ::google::protobuf::util::Status; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::config::client::HttpClientConfig; -using ::istio::mixer::v1::config::client::ServiceConfig; -using ::istio::mixerclient::CancelFunc; -using ::istio::mixerclient::CheckDoneFunc; -using ::istio::mixerclient::DoneFunc; -using ::istio::mixerclient::MixerClient; -using ::istio::mixerclient::TransportCheckFunc; -using ::istio::quota_config::Requirement; - -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace control { -namespace http { - -// The default client config -const char kDefaultClientConfig[] = R"( -service_configs { - key: ":default" - value { - mixer_attributes { - attributes { - key: "route0-key" - value { - string_value: "route0-value" - } - } - } - } -} -default_destination_service: ":default" -mixer_attributes { - attributes { - key: "global-key" - value { - string_value: "global-value" - } - } -} -)"; - -// The client config with empty service map. -const char kEmptyClientConfig[] = R"( -forward_attributes { - attributes { - key: "source-key" - value { - string_value: "source-value" - } - } -} -)"; - -class RequestHandlerImplTest : public ::testing::Test { - public: - void SetUp() { SetUpMockController(kDefaultClientConfig); } - - void SetUpMockController(const std::string& config_text) { - ASSERT_TRUE(TextFormat::ParseFromString(config_text, &client_config_)); - - mock_client_ = new ::testing::NiceMock; - // set LRU cache size is 3 - client_context_ = std::make_shared( - std::unique_ptr(mock_client_), client_config_, 3); - controller_ = - std::unique_ptr(new ControllerImpl(client_context_)); - } - - void SetServiceConfig(const std::string& name, const ServiceConfig& config) { - (*client_config_.mutable_service_configs())[name] = config; - } - - void ApplyPerRouteConfig(const ServiceConfig& service_config, - Controller::PerRouteConfig* per_route) { - per_route->service_config_id = "1111"; - controller_->AddServiceConfig(per_route->service_config_id, service_config); - } - - std::shared_ptr client_context_; - HttpClientConfig client_config_; - ::testing::NiceMock* mock_client_; - std::unique_ptr controller_; -}; - -TEST_F(RequestHandlerImplTest, TestServiceConfigManage) { - EXPECT_FALSE(controller_->LookupServiceConfig("1111")); - ServiceConfig config; - controller_->AddServiceConfig("1111", config); - EXPECT_TRUE(controller_->LookupServiceConfig("1111")); - - // LRU cache size is 3 - controller_->AddServiceConfig("2222", config); - controller_->AddServiceConfig("3333", config); - controller_->AddServiceConfig("4444", config); - - // 1111 should be purged - EXPECT_FALSE(controller_->LookupServiceConfig("1111")); - EXPECT_TRUE(controller_->LookupServiceConfig("2222")); - EXPECT_TRUE(controller_->LookupServiceConfig("3333")); - EXPECT_TRUE(controller_->LookupServiceConfig("4444")); -} - -TEST_F(RequestHandlerImplTest, TestHandlerDisabledCheckReport) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - // Not to extract attributes since both Check and Report are disabled. - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(0); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(0); - - // Check should NOT be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)).Times(0); - - ServiceConfig config; - config.set_disable_check_calls(true); - config.set_disable_report_calls(true); - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, - [](const Status& status) { EXPECT_TRUE(status.ok()); }); -} - -TEST_F(RequestHandlerImplTest, TestHandlerDisabledCheck) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - // Report is enabled so Attributes are extracted. - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should NOT be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)).Times(0); - - ServiceConfig config; - config.set_disable_check_calls(true); - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, - [](const Status& status) { EXPECT_TRUE(status.ok()); }); -} - -TEST_F(RequestHandlerImplTest, TestPerRouteAttributes) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["global-key"].string_value(), "global-value"); - EXPECT_EQ(map["per-route-key"].string_value(), "per-route-value"); - return nullptr; - })); - - ServiceConfig config; - auto map2 = config.mutable_mixer_attributes()->mutable_attributes(); - (*map2)["per-route-key"].set_string_value("per-route-value"); - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestDefaultRouteAttributes) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["global-key"].string_value(), "global-value"); - EXPECT_EQ(map["route0-key"].string_value(), "route0-value"); - return nullptr; - })); - - // destionation.server is empty, will use default one - Controller::PerRouteConfig config; - auto handler = controller_->CreateRequestHandler(config); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestRouteAttributes) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - ServiceConfig route_config; - auto map3 = route_config.mutable_mixer_attributes()->mutable_attributes(); - (*map3)["route1-key"].set_string_value("route1-value"); - SetServiceConfig("route1", route_config); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["global-key"].string_value(), "global-value"); - EXPECT_EQ(map["route1-key"].string_value(), "route1-value"); - return nullptr; - })); - - // destionation.server is empty, will use default one - Controller::PerRouteConfig config; - config.destination_service = "route1"; - auto handler = controller_->CreateRequestHandler(config); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestPerRouteQuota) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["global-key"].string_value(), "global-value"); - EXPECT_EQ(quotas.size(), 1); - EXPECT_EQ(quotas[0].quota, "route0-quota"); - EXPECT_EQ(quotas[0].charge, 10); - return nullptr; - })); - - ServiceConfig config; - auto quota = config.add_quota_spec()->add_rules()->add_quotas(); - quota->set_quota("route0-quota"); - quota->set_charge(10); - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestPerRouteApiSpec) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, FindHeaderByType(_, _)) - .WillRepeatedly( - Invoke([](CheckData::HeaderType type, std::string* value) -> bool { - if (type == CheckData::HEADER_PATH) { - *value = "/books/120"; - return true; - } - if (type == CheckData::HEADER_METHOD) { - *value = "GET"; - return true; - } - return false; - })); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["global-key"].string_value(), "global-value"); - EXPECT_EQ(map["api.name"].string_value(), "test-name"); - EXPECT_EQ(map["api.operation"].string_value(), "test-method"); - return nullptr; - })); - - ServiceConfig config; - auto api_spec = config.add_http_api_spec(); - auto map1 = api_spec->mutable_attributes()->mutable_attributes(); - (*map1)["api.name"].set_string_value("test-name"); - auto pattern = api_spec->add_patterns(); - auto map2 = pattern->mutable_attributes()->mutable_attributes(); - (*map2)["api.operation"].set_string_value("test-method"); - pattern->set_http_method("GET"); - pattern->set_uri_template("/books/*"); - - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestHandlerCheck) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)).Times(1); - - ServiceConfig config; - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestDefaultApiKey) { - ::testing::NiceMock mock_data; - ::testing::NiceMock mock_header; - EXPECT_CALL(mock_data, FindQueryParameter(_, _)) - .WillRepeatedly( - Invoke([](const std::string& name, std::string* value) -> bool { - if (name == "key") { - *value = "test-api-key"; - return true; - } - return false; - })); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map[AttributeName::kRequestApiKey].string_value(), - "test-api-key"); - return nullptr; - })); - - // destionation.server is empty, will use default one - Controller::PerRouteConfig config; - auto handler = controller_->CreateRequestHandler(config); - handler->Check(&mock_data, &mock_header, nullptr, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestHandlerReport) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetResponseHeaders()).Times(1); - EXPECT_CALL(mock_data, GetReportInfo(_)).Times(1); - - // Report should be called. - EXPECT_CALL(*mock_client_, Report(_)).Times(1); - - ServiceConfig config; - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Report(&mock_data); -} - -TEST_F(RequestHandlerImplTest, TestHandlerDisabledReport) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetResponseHeaders()).Times(0); - EXPECT_CALL(mock_data, GetReportInfo(_)).Times(0); - - // Report should NOT be called. - EXPECT_CALL(*mock_client_, Report(_)).Times(0); - - ServiceConfig config; - config.set_disable_report_calls(true); - Controller::PerRouteConfig per_route; - ApplyPerRouteConfig(config, &per_route); - - auto handler = controller_->CreateRequestHandler(per_route); - handler->Report(&mock_data); -} - -TEST_F(RequestHandlerImplTest, TestEmptyConfig) { - SetUpMockController(kEmptyClientConfig); - - ::testing::NiceMock mock_check; - ::testing::NiceMock mock_header; - // Not to extract attributes since both Check and Report are disabled. - EXPECT_CALL(mock_check, GetSourceIpPort(_, _)).Times(0); - EXPECT_CALL(mock_check, GetSourceUser(_)).Times(0); - - // Attributes is forwarded. - EXPECT_CALL(mock_header, AddIstioAttributes(_)) - .WillOnce(Invoke([](const std::string& data) { - Attributes forwarded_attr; - EXPECT_TRUE(forwarded_attr.ParseFromString(data)); - auto map = forwarded_attr.attributes(); - EXPECT_EQ(map["source-key"].string_value(), "source-value"); - })); - - // Check should NOT be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)).Times(0); - - ::testing::NiceMock mock_report; - EXPECT_CALL(mock_report, GetResponseHeaders()).Times(0); - EXPECT_CALL(mock_report, GetReportInfo(_)).Times(0); - - // Report should NOT be called. - EXPECT_CALL(*mock_client_, Report(_)).Times(0); - - Controller::PerRouteConfig config; - auto handler = controller_->CreateRequestHandler(config); - handler->Check(&mock_check, &mock_header, nullptr, - [](const Status& status) { EXPECT_TRUE(status.ok()); }); - handler->Report(&mock_report); -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/service_context.cc b/src/istio/control/http/service_context.cc deleted file mode 100644 index df6fedda97a..00000000000 --- a/src/istio/control/http/service_context.cc +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "service_context.h" -#include "src/istio/control/attribute_names.h" - -using ::istio::mixer::v1::config::client::ServiceConfig; - -namespace istio { -namespace control { -namespace http { - -ServiceContext::ServiceContext(std::shared_ptr client_context, - const ServiceConfig* config) - : client_context_(client_context) { - if (config) { - service_config_.reset(new ServiceConfig(*config)); - } - BuildParsers(); -} - -void ServiceContext::BuildParsers() { - if (!service_config_) { - return; - } - // Build api_spec parsers - for (const auto& api_spec : service_config_->http_api_spec()) { - api_spec_.MergeFrom(api_spec); - } - api_spec_parser_ = ::istio::api_spec::HttpApiSpecParser::Create(api_spec_); - - // Build quota parser - for (const auto& quota : service_config_->quota_spec()) { - quota_parsers_.push_back( - ::istio::quota_config::ConfigParser::Create(quota)); - } -} - -// Add static mixer attributes. -void ServiceContext::AddStaticAttributes(RequestContext* request) const { - if (client_context_->config().has_mixer_attributes()) { - request->attributes.MergeFrom(client_context_->config().mixer_attributes()); - } - if (service_config_->has_mixer_attributes()) { - request->attributes.MergeFrom(service_config_->mixer_attributes()); - } -} - -void ServiceContext::AddApiAttributes(CheckData* check_data, - RequestContext* request) const { - if (!api_spec_parser_) { - return; - } - std::string http_method; - std::string path; - if (check_data->FindHeaderByType(CheckData::HEADER_METHOD, &http_method) && - check_data->FindHeaderByType(CheckData::HEADER_PATH, &path)) { - api_spec_parser_->AddAttributes(http_method, path, &request->attributes); - } - - std::string api_key; - if (api_spec_parser_->ExtractApiKey(check_data, &api_key)) { - (*request->attributes.mutable_attributes())[AttributeName::kRequestApiKey] - .set_string_value(api_key); - } -} - -// Add quota requirements from quota configs. -void ServiceContext::AddQuotas(RequestContext* request) const { - for (const auto& parser : quota_parsers_) { - parser->GetRequirements(request->attributes, &request->quotas); - } -} - -} // namespace http -} // namespace control -} // namespace istio diff --git a/src/istio/control/http/service_context.h b/src/istio/control/http/service_context.h deleted file mode 100644 index 3b6d4880ad5..00000000000 --- a/src/istio/control/http/service_context.h +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_HTTP_SERVICE_CONTEXT_H -#define ISTIO_CONTROL_HTTP_SERVICE_CONTEXT_H - -#include "google/protobuf/stubs/status.h" -#include "include/istio/api_spec/http_api_spec_parser.h" -#include "include/istio/quota_config/config_parser.h" -#include "mixer/v1/attributes.pb.h" -#include "src/istio/control/http/client_context.h" - -namespace istio { -namespace control { -namespace http { - -// The context to hold service config for both HTTP and TCP. -class ServiceContext { - public: - ServiceContext( - std::shared_ptr client_context, - const ::istio::mixer::v1::config::client::ServiceConfig* config); - - std::shared_ptr client_context() const { - return client_context_; - } - - // Add static mixer attributes. - void AddStaticAttributes(RequestContext* request) const; - - // Add api attributes from api_spec. - void AddApiAttributes(CheckData* check_data, RequestContext* request) const; - - // Add quota requirements from quota configs. - void AddQuotas(RequestContext* request) const; - - bool enable_mixer_check() const { - return service_config_ && !service_config_->disable_check_calls(); - } - bool enable_mixer_report() const { - return service_config_ && !service_config_->disable_report_calls(); - } - - private: - // Pre-process the config data to build parser objects. - void BuildParsers(); - - // The client context object. - std::shared_ptr client_context_; - - // Concatenated api_spec_ - ::istio::mixer::v1::config::client::HTTPAPISpec api_spec_; - // Api spec parser to generate api attributes and api_key - std::unique_ptr<::istio::api_spec::HttpApiSpecParser> api_spec_parser_; - - // The quota parsers for each quota config. - std::vector> - quota_parsers_; - - // The service config. - std::unique_ptr<::istio::mixer::v1::config::client::ServiceConfig> - service_config_; -}; - -} // namespace http -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_HTTP_SERVICE_CONTEXT_H diff --git a/src/istio/control/mock_mixer_client.h b/src/istio/control/mock_mixer_client.h deleted file mode 100644 index b7367271370..00000000000 --- a/src/istio/control/mock_mixer_client.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_MOCK_MIXER_CLIENT_H -#define ISTIO_CONTROL_MOCK_MIXER_CLIENT_H - -#include "gmock/gmock.h" -#include "include/istio/mixerclient/client.h" - -namespace istio { -namespace control { - -// The mock object for MixerClient interface. -class MockMixerClient : public ::istio::mixerclient::MixerClient { - public: - MOCK_METHOD4( - Check, ::istio::mixerclient::CancelFunc( - const ::istio::mixer::v1::Attributes& attributes, - const std::vector<::istio::quota_config::Requirement>& quotas, - ::istio::mixerclient::TransportCheckFunc transport, - ::istio::mixerclient::CheckDoneFunc on_done)); - MOCK_METHOD1(Report, void(const ::istio::mixer::v1::Attributes& attributes)); - MOCK_CONST_METHOD1(GetStatistics, - void(::istio::mixerclient::Statistics* stat)); -}; - -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_MOCK_MIXER_CLIENT_H diff --git a/src/istio/control/request_context.h b/src/istio/control/request_context.h deleted file mode 100644 index 55dd4b3fa4f..00000000000 --- a/src/istio/control/request_context.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_REQUEST_CONTEXT_H -#define ISTIO_CONTROL_REQUEST_CONTEXT_H - -#include "google/protobuf/stubs/status.h" -#include "include/istio/quota_config/requirement.h" -#include "mixer/v1/attributes.pb.h" - -#include - -namespace istio { -namespace control { - -// The context to hold request data for both HTTP and TCP. -struct RequestContext { - // The attributes for both Check and Report. - ::istio::mixer::v1::Attributes attributes; - // The quota requirements - std::vector<::istio::quota_config::Requirement> quotas; - // The check status. - ::google::protobuf::util::Status check_status; -}; - -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_REQUEST_CONTEXT_H diff --git a/src/istio/control/tcp/BUILD b/src/istio/control/tcp/BUILD deleted file mode 100644 index 31bb2b3f07d..00000000000 --- a/src/istio/control/tcp/BUILD +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "control_lib", - srcs = [ - "attributes_builder.cc", - "attributes_builder.h", - "client_context.h", - "controller_impl.cc", - "controller_impl.h", - "request_handler_impl.cc", - "request_handler_impl.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//include/istio/control/tcp:headers_lib", - "//src/istio/control:common_lib", - ], -) - -cc_test( - name = "attributes_builder_test", - size = "small", - srcs = [ - "attributes_builder_test.cc", - "mock_check_data.h", - "mock_report_data.h", - ], - linkstatic = 1, - deps = [ - ":control_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "request_handler_impl_test", - size = "small", - srcs = [ - "mock_check_data.h", - "mock_report_data.h", - "request_handler_impl_test.cc", - ], - linkstatic = 1, - deps = [ - ":control_lib", - "//external:googletest_main", - "//src/istio/control:mock_mixer_client", - ], -) diff --git a/src/istio/control/tcp/attributes_builder.cc b/src/istio/control/tcp/attributes_builder.cc deleted file mode 100644 index 4a8a011aa0a..00000000000 --- a/src/istio/control/tcp/attributes_builder.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/tcp/attributes_builder.h" - -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/control/attribute_names.h" - -namespace istio { -namespace control { -namespace tcp { -namespace { -// Connection events for TCP connection. -const std::string kConnectionOpen("open"); -const std::string kConnectionContinue("continue"); -const std::string kConnectionClose("close"); -} // namespace - -void AttributesBuilder::ExtractCheckAttributes(CheckData* check_data) { - utils::AttributesBuilder builder(&request_->attributes); - - std::string source_ip; - int source_port; - if (check_data->GetSourceIpPort(&source_ip, &source_port)) { - builder.AddBytes(AttributeName::kSourceIp, source_ip); - builder.AddInt64(AttributeName::kSourcePort, source_port); - } - - // TODO(diemtvu): add TCP authn filter similar to http case, and use authn - // result output here instead. - std::string source_user; - if (check_data->GetSourceUser(&source_user)) { - // TODO(diemtvu): remove kSourceUser once migration to source.principal is - // over. https://github.com/istio/istio/issues/4689 - builder.AddString(AttributeName::kSourceUser, source_user); - builder.AddString(AttributeName::kSourcePrincipal, source_user); - } - builder.AddBool(AttributeName::kConnectionMtls, check_data->IsMutualTLS()); - - builder.AddTimestamp(AttributeName::kContextTime, - std::chrono::system_clock::now()); - builder.AddString(AttributeName::kContextProtocol, "tcp"); - builder.AddString(AttributeName::kConnectionEvent, kConnectionOpen); - - // Get unique downstream connection ID, which is -. - std::string connection_id = check_data->GetConnectionId(); - builder.AddString(AttributeName::kConnectionId, connection_id); -} - -void AttributesBuilder::ExtractReportAttributes( - ReportData* report_data, bool is_final_report, - ReportData::ReportInfo* last_report_info) { - utils::AttributesBuilder builder(&request_->attributes); - - ReportData::ReportInfo info; - report_data->GetReportInfo(&info); - builder.AddInt64(AttributeName::kConnectionReceviedBytes, - info.received_bytes - last_report_info->received_bytes); - builder.AddInt64(AttributeName::kConnectionReceviedTotalBytes, - info.received_bytes); - builder.AddInt64(AttributeName::kConnectionSendBytes, - info.send_bytes - last_report_info->send_bytes); - builder.AddInt64(AttributeName::kConnectionSendTotalBytes, info.send_bytes); - - if (is_final_report) { - builder.AddDuration(AttributeName::kConnectionDuration, info.duration); - if (!request_->check_status.ok()) { - builder.AddInt64(AttributeName::kCheckErrorCode, - request_->check_status.error_code()); - builder.AddString(AttributeName::kCheckErrorMessage, - request_->check_status.ToString()); - } - builder.AddString(AttributeName::kConnectionEvent, kConnectionClose); - } else { - last_report_info->received_bytes = info.received_bytes; - last_report_info->send_bytes = info.send_bytes; - builder.AddString(AttributeName::kConnectionEvent, kConnectionContinue); - } - - std::string dest_ip; - int dest_port; - if (report_data->GetDestinationIpPort(&dest_ip, &dest_port)) { - builder.AddBytes(AttributeName::kDestinationIp, dest_ip); - builder.AddInt64(AttributeName::kDestinationPort, dest_port); - } - - builder.AddTimestamp(AttributeName::kContextTime, - std::chrono::system_clock::now()); -} - -} // namespace tcp -} // namespace control -} // namespace istio diff --git a/src/istio/control/tcp/attributes_builder.h b/src/istio/control/tcp/attributes_builder.h deleted file mode 100644 index 1423f99d311..00000000000 --- a/src/istio/control/tcp/attributes_builder.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_ATTRIBUTES_BUILDER_H -#define ISTIO_CONTROL_TCP_ATTRIBUTES_BUILDER_H - -#include "include/istio/control/tcp/check_data.h" -#include "include/istio/control/tcp/report_data.h" -#include "src/istio/control/request_context.h" - -namespace istio { -namespace control { -namespace tcp { - -// The builder class to add TCP attributes. -class AttributesBuilder { - public: - AttributesBuilder(RequestContext* request) : request_(request) {} - - // Extract attributes for Check. - void ExtractCheckAttributes(CheckData* check_data); - // Extract attributes for Report. - void ExtractReportAttributes(ReportData* report_data, bool is_final_report, - ReportData::ReportInfo* last_report_info); - - private: - // The request context object. - RequestContext* request_; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_ATTRIBUTES_BUILDER_H diff --git a/src/istio/control/tcp/attributes_builder_test.cc b/src/istio/control/tcp/attributes_builder_test.cc deleted file mode 100644 index ab5d24db50d..00000000000 --- a/src/istio/control/tcp/attributes_builder_test.cc +++ /dev/null @@ -1,401 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/tcp/attributes_builder.h" - -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/message_differencer.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/control/attribute_names.h" -#include "src/istio/control/tcp/mock_check_data.h" -#include "src/istio/control/tcp/mock_report_data.h" - -using ::google::protobuf::TextFormat; -using ::google::protobuf::util::MessageDifferencer; - -using ::testing::Invoke; -using ::testing::Return; -using ::testing::_; - -namespace istio { -namespace control { -namespace tcp { -namespace { - -const char kCheckAttributes[] = R"( -attributes { - key: "connection.event" - value { - string_value: "open" - } -} -attributes { - key: "context.protocol" - value { - string_value: "tcp" - } -} -attributes { - key: "context.time" - value { - timestamp_value { - } - } -} -attributes { - key: "source.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "connection.mtls" - value { - bool_value: true - } -} -attributes { - key: "source.port" - value { - int64_value: 8080 - } -} -attributes { - key: "source.principal" - value { - string_value: "test_user" - } -} -attributes { - key: "source.user" - value { - string_value: "test_user" - } -} -attributes { - key: "connection.id" - value { - string_value: "1234-5" - } -} -)"; - -const char kReportAttributes[] = R"( -attributes { - key: "connection.event" - value { - string_value: "close" - } -} -attributes { - key: "check.error_code" - value { - int64_value: 3 - } -} -attributes { - key: "check.error_message" - value { - string_value: "INVALID_ARGUMENT:Invalid argument" - } -} -attributes { - key: "connection.duration" - value { - duration_value { - nanos: 3 - } - } -} -attributes { - key: "connection.received.bytes" - value { - int64_value: 144 - } -} -attributes { - key: "connection.received.bytes_total" - value { - int64_value: 345 - } -} -attributes { - key: "connection.sent.bytes" - value { - int64_value: 274 - } -} -attributes { - key: "connection.sent.bytes_total" - value { - int64_value: 678 - } -} -attributes { - key: "context.time" - value { - timestamp_value { - } - } -} -attributes { - key: "destination.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "destination.port" - value { - int64_value: 8080 - } -} -)"; - -const char kDeltaOneReportAttributes[] = R"( -attributes { - key: "connection.event" - value { - string_value: "continue" - } -} -attributes { - key: "connection.received.bytes" - value { - int64_value: 100 - } -} -attributes { - key: "connection.sent.bytes" - value { - int64_value: 200 - } -} -attributes { - key: "connection.received.bytes_total" - value { - int64_value: 100 - } -} -attributes { - key: "connection.sent.bytes_total" - value { - int64_value: 200 - } -} -attributes { - key: "context.time" - value { - timestamp_value { - } - } -} -attributes { - key: "destination.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "destination.port" - value { - int64_value: 8080 - } -} -)"; - -const char kDeltaTwoReportAttributes[] = R"( -attributes { - key: "connection.event" - value { - string_value: "continue" - } -} -attributes { - key: "connection.received.bytes" - value { - int64_value: 101 - } -} -attributes { - key: "connection.sent.bytes" - value { - int64_value: 204 - } -} -attributes { - key: "connection.received.bytes_total" - value { - int64_value: 201 - } -} -attributes { - key: "connection.sent.bytes_total" - value { - int64_value: 404 - } -} -attributes { - key: "context.time" - value { - timestamp_value { - } - } -} -attributes { - key: "destination.ip" - value { - bytes_value: "1.2.3.4" - } -} -attributes { - key: "destination.port" - value { - int64_value: 8080 - } -} -)"; - -void ClearContextTime(RequestContext* request) { - // Override timestamp with - - utils::AttributesBuilder builder(&request->attributes); - std::chrono::time_point time0; - builder.AddTimestamp(AttributeName::kContextTime, time0); -} - -TEST(AttributesBuilderTest, TestCheckAttributes) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)) - .WillOnce(Invoke([](std::string* ip, int* port) -> bool { - *ip = "1.2.3.4"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, IsMutualTLS()).WillOnce(Invoke([]() -> bool { - return true; - })); - EXPECT_CALL(mock_data, GetSourceUser(_)) - .WillOnce(Invoke([](std::string* user) -> bool { - *user = "test_user"; - return true; - })); - EXPECT_CALL(mock_data, GetConnectionId()).WillOnce(Return("1234-5")); - - RequestContext request; - AttributesBuilder builder(&request); - builder.ExtractCheckAttributes(&mock_data); - - ClearContextTime(&request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - ::istio::mixer::v1::Attributes expected_attributes; - ASSERT_TRUE( - TextFormat::ParseFromString(kCheckAttributes, &expected_attributes)); - EXPECT_TRUE( - MessageDifferencer::Equals(request.attributes, expected_attributes)); -} - -TEST(AttributesBuilderTest, TestReportAttributes) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetDestinationIpPort(_, _)) - .Times(3) - .WillRepeatedly(Invoke([](std::string* ip, int* port) -> bool { - *ip = "1.2.3.4"; - *port = 8080; - return true; - })); - EXPECT_CALL(mock_data, GetReportInfo(_)) - .Times(3) - .WillOnce(Invoke([](ReportData::ReportInfo* info) { - info->received_bytes = 100; - info->send_bytes = 200; - info->duration = std::chrono::nanoseconds(1); - })) - .WillOnce(Invoke([](ReportData::ReportInfo* info) { - info->received_bytes = 201; - info->send_bytes = 404; - info->duration = std::chrono::nanoseconds(2); - })) - .WillOnce(Invoke([](ReportData::ReportInfo* info) { - info->received_bytes = 345; - info->send_bytes = 678; - info->duration = std::chrono::nanoseconds(3); - })); - - RequestContext request; - request.check_status = ::google::protobuf::util::Status( - ::google::protobuf::util::error::INVALID_ARGUMENT, "Invalid argument"); - AttributesBuilder builder(&request); - - ReportData::ReportInfo last_report_info{0ULL, 0ULL, - std::chrono::nanoseconds::zero()}; - // Verify delta one report - builder.ExtractReportAttributes(&mock_data, /* is_final_report */ false, - &last_report_info); - ClearContextTime(&request); - - std::string out_str; - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - ::istio::mixer::v1::Attributes expected_delta_attributes; - ASSERT_TRUE(TextFormat::ParseFromString(kDeltaOneReportAttributes, - &expected_delta_attributes)); - EXPECT_TRUE(MessageDifferencer::Equals(request.attributes, - expected_delta_attributes)); - EXPECT_EQ(100, last_report_info.received_bytes); - EXPECT_EQ(200, last_report_info.send_bytes); - - // Verify delta two report - builder.ExtractReportAttributes(&mock_data, /* is_final_report */ false, - &last_report_info); - ClearContextTime(&request); - - out_str.clear(); - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - expected_delta_attributes.Clear(); - ASSERT_TRUE(TextFormat::ParseFromString(kDeltaTwoReportAttributes, - &expected_delta_attributes)); - EXPECT_TRUE(MessageDifferencer::Equals(request.attributes, - expected_delta_attributes)); - EXPECT_EQ(201, last_report_info.received_bytes); - EXPECT_EQ(404, last_report_info.send_bytes); - - // Verify final report - builder.ExtractReportAttributes(&mock_data, /* is_final_report */ true, - &last_report_info); - ClearContextTime(&request); - - out_str.clear(); - TextFormat::PrintToString(request.attributes, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - ::istio::mixer::v1::Attributes expected_final_attributes; - ASSERT_TRUE(TextFormat::ParseFromString(kReportAttributes, - &expected_final_attributes)); - EXPECT_TRUE(MessageDifferencer::Equals(request.attributes, - expected_final_attributes)); -} - -} // namespace -} // namespace tcp -} // namespace control -} // namespace istio diff --git a/src/istio/control/tcp/client_context.h b/src/istio/control/tcp/client_context.h deleted file mode 100644 index e093b9745ef..00000000000 --- a/src/istio/control/tcp/client_context.h +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_CLIENT_CONTEXT_H -#define ISTIO_CONTROL_TCP_CLIENT_CONTEXT_H - -#include "include/istio/control/tcp/controller.h" -#include "include/istio/quota_config/config_parser.h" -#include "src/istio/control/client_context_base.h" -#include "src/istio/control/request_context.h" - -namespace istio { -namespace control { -namespace tcp { - -// The global context object to hold: -// * the tcp client config -// * the mixer client object to call Check/Report with cache. -class ClientContext : public ClientContextBase { - public: - ClientContext(const Controller::Options& data) - : ClientContextBase(data.config.transport(), data.env), - config_(data.config) { - BuildQuotaParser(); - } - - // A constructor for unit-test to pass in a mock mixer_client - ClientContext( - std::unique_ptr<::istio::mixerclient::MixerClient> mixer_client, - const ::istio::mixer::v1::config::client::TcpClientConfig& config) - : ClientContextBase(std::move(mixer_client)), config_(config) { - BuildQuotaParser(); - } - - // Add static mixer attributes. - void AddStaticAttributes(RequestContext* request) const { - if (config_.has_mixer_attributes()) { - request->attributes.MergeFrom(config_.mixer_attributes()); - } - } - - // Add quota requirements from quota configs. - void AddQuotas(RequestContext* request) const { - if (quota_parser_) { - quota_parser_->GetRequirements(request->attributes, &request->quotas); - } - } - - bool enable_mixer_check() const { return !config_.disable_check_calls(); } - bool enable_mixer_report() const { return !config_.disable_report_calls(); } - - private: - // If there is quota config, build quota parser. - void BuildQuotaParser() { - if (config_.has_connection_quota_spec()) { - quota_parser_ = ::istio::quota_config::ConfigParser::Create( - config_.connection_quota_spec()); - } - } - // The mixer client config. - const ::istio::mixer::v1::config::client::TcpClientConfig& config_; - - // The quota parser. - std::unique_ptr<::istio::quota_config::ConfigParser> quota_parser_; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_CLIENT_CONTEXT_H diff --git a/src/istio/control/tcp/controller_impl.cc b/src/istio/control/tcp/controller_impl.cc deleted file mode 100644 index 23423d34af3..00000000000 --- a/src/istio/control/tcp/controller_impl.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/tcp/controller_impl.h" -#include "src/istio/control/tcp/request_handler_impl.h" - -using ::istio::mixer::v1::config::client::TcpClientConfig; -using ::istio::mixerclient::Statistics; - -namespace istio { -namespace control { -namespace tcp { - -ControllerImpl::ControllerImpl(const Options& data) { - client_context_.reset(new ClientContext(data)); -} - -std::unique_ptr ControllerImpl::CreateRequestHandler() { - return std::unique_ptr( - new RequestHandlerImpl(client_context_)); -} - -std::unique_ptr Controller::Create(const Options& data) { - return std::unique_ptr(new ControllerImpl(data)); -} - -void ControllerImpl::GetStatistics(Statistics* stat) const { - client_context_->GetStatistics(stat); -} - -} // namespace tcp -} // namespace control -} // namespace istio diff --git a/src/istio/control/tcp/controller_impl.h b/src/istio/control/tcp/controller_impl.h deleted file mode 100644 index aba229933ae..00000000000 --- a/src/istio/control/tcp/controller_impl.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_CONTROLLER_IMPL_H -#define ISTIO_CONTROL_TCP_CONTROLLER_IMPL_H - -#include - -#include "include/istio/control/tcp/controller.h" -#include "src/istio/control/tcp/client_context.h" - -namespace istio { -namespace control { -namespace tcp { - -// The class to implement Controller interface. -class ControllerImpl : public Controller { - public: - ControllerImpl(const Controller::Options& data); - // A constructor for unit-test to pass in a mock client_context - ControllerImpl(std::shared_ptr client_context) - : client_context_(client_context) {} - - // Creates a TCP request handler - std::unique_ptr CreateRequestHandler() override; - - // Get statistics. - void GetStatistics(::istio::mixerclient::Statistics* stat) const override; - - private: - // The client context object to hold client config and client cache. - std::shared_ptr client_context_; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_CONTROLLER_IMPL_H diff --git a/src/istio/control/tcp/mock_check_data.h b/src/istio/control/tcp/mock_check_data.h deleted file mode 100644 index 170e766b500..00000000000 --- a/src/istio/control/tcp/mock_check_data.h +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_MOCK_CHECK_DATA_H -#define ISTIO_CONTROL_TCP_MOCK_CHECK_DATA_H - -#include "gmock/gmock.h" -#include "include/istio/control/tcp/check_data.h" - -namespace istio { -namespace control { -namespace tcp { - -// The mock object for CheckData interface. -class MockCheckData : public CheckData { - public: - MOCK_CONST_METHOD2(GetSourceIpPort, bool(std::string* ip, int* port)); - MOCK_CONST_METHOD1(GetSourceUser, bool(std::string* user)); - MOCK_CONST_METHOD0(IsMutualTLS, bool()); - MOCK_CONST_METHOD0(GetConnectionId, std::string()); -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_MOCK_CHECK_DATA_H diff --git a/src/istio/control/tcp/mock_report_data.h b/src/istio/control/tcp/mock_report_data.h deleted file mode 100644 index 6825e577cf5..00000000000 --- a/src/istio/control/tcp/mock_report_data.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_MOCK_REPORT_DATA_H -#define ISTIO_CONTROL_TCP_MOCK_REPORT_DATA_H - -#include "gmock/gmock.h" -#include "include/istio/control/tcp/report_data.h" - -namespace istio { -namespace control { -namespace tcp { - -// The mock object for ReportData interface. -class MockReportData : public ReportData { - public: - MOCK_CONST_METHOD2(GetDestinationIpPort, bool(std::string* ip, int* port)); - MOCK_CONST_METHOD1(GetReportInfo, void(ReportInfo* info)); -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_MOCK_REPORT_DATA_H diff --git a/src/istio/control/tcp/request_handler_impl.cc b/src/istio/control/tcp/request_handler_impl.cc deleted file mode 100644 index 5930beba54a..00000000000 --- a/src/istio/control/tcp/request_handler_impl.cc +++ /dev/null @@ -1,71 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/control/tcp/request_handler_impl.h" -#include "src/istio/control/tcp/attributes_builder.h" - -using ::google::protobuf::util::Status; -using ::istio::mixerclient::CancelFunc; -using ::istio::mixerclient::DoneFunc; -using ::istio::quota_config::Requirement; - -namespace istio { -namespace control { -namespace tcp { - -RequestHandlerImpl::RequestHandlerImpl( - std::shared_ptr client_context) - : client_context_(client_context), - last_report_info_{0ULL, 0ULL, std::chrono::nanoseconds::zero()} {} - -CancelFunc RequestHandlerImpl::Check(CheckData* check_data, DoneFunc on_done) { - if (client_context_->enable_mixer_check() || - client_context_->enable_mixer_report()) { - client_context_->AddStaticAttributes(&request_context_); - - AttributesBuilder builder(&request_context_); - builder.ExtractCheckAttributes(check_data); - } - - if (!client_context_->enable_mixer_check()) { - on_done(Status::OK); - return nullptr; - } - - client_context_->AddQuotas(&request_context_); - - return client_context_->SendCheck(nullptr, on_done, &request_context_); -} - -// Make remote report call. -void RequestHandlerImpl::Report(ReportData* report_data) { - Report(report_data, /* is_final_report */ true); -} - -void RequestHandlerImpl::Report(ReportData* report_data, bool is_final_report) { - if (!client_context_->enable_mixer_report()) { - return; - } - - AttributesBuilder builder(&request_context_); - builder.ExtractReportAttributes(report_data, is_final_report, - &last_report_info_); - - client_context_->SendReport(request_context_); -} - -} // namespace tcp -} // namespace control -} // namespace istio diff --git a/src/istio/control/tcp/request_handler_impl.h b/src/istio/control/tcp/request_handler_impl.h deleted file mode 100644 index c0ab04c17e9..00000000000 --- a/src/istio/control/tcp/request_handler_impl.h +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_CONTROL_TCP_REQUEST_HANDLER_IMPL_H -#define ISTIO_CONTROL_TCP_REQUEST_HANDLER_IMPL_H - -#include "include/istio/control/tcp/request_handler.h" -#include "src/istio/control/request_context.h" -#include "src/istio/control/tcp/client_context.h" - -namespace istio { -namespace control { -namespace tcp { - -// The class to implement RequestHandler interface. -class RequestHandlerImpl : public RequestHandler { - public: - RequestHandlerImpl(std::shared_ptr client_context); - - // Make a Check call. - ::istio::mixerclient::CancelFunc Check( - CheckData* check_data, ::istio::mixerclient::DoneFunc on_done) override; - - // Make a Report call. - // TODO(JimmyCYJ): Let TCP filter use - // void Report(ReportData* report_data, bool is_final_report), and deprecate - // this method. - void Report(ReportData* report_data) override; - - // Make a Report call. If is_final_report is true, then report all attributes, - // otherwise, report delta attributes. - void Report(ReportData* report_data, bool is_final_report) override; - - private: - // The request context object. - RequestContext request_context_; - - // The client context object. - std::shared_ptr client_context_; - - // Records reported information in last Report() call. This is needed for - // calculating delta information which will be sent in periodical report. - // Delta information includes incremented sent bytes and received bytes - // between last report and this report. - ReportData::ReportInfo last_report_info_; -}; - -} // namespace tcp -} // namespace control -} // namespace istio - -#endif // ISTIO_CONTROL_TCP_REQUEST_HANDLER_IMPL_H diff --git a/src/istio/control/tcp/request_handler_impl_test.cc b/src/istio/control/tcp/request_handler_impl_test.cc deleted file mode 100644 index b28af059b26..00000000000 --- a/src/istio/control/tcp/request_handler_impl_test.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "gtest/gtest.h" -#include "src/istio/control/mock_mixer_client.h" -#include "src/istio/control/tcp/client_context.h" -#include "src/istio/control/tcp/controller_impl.h" -#include "src/istio/control/tcp/mock_check_data.h" -#include "src/istio/control/tcp/mock_report_data.h" - -using ::google::protobuf::util::Status; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::config::client::TcpClientConfig; -using ::istio::mixerclient::CancelFunc; -using ::istio::mixerclient::CheckDoneFunc; -using ::istio::mixerclient::DoneFunc; -using ::istio::mixerclient::MixerClient; -using ::istio::mixerclient::TransportCheckFunc; -using ::istio::quota_config::Requirement; - -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace control { -namespace tcp { - -class RequestHandlerImplTest : public ::testing::Test { - public: - void SetUp() { - auto map1 = client_config_.mutable_mixer_attributes()->mutable_attributes(); - (*map1)["key1"].set_string_value("value1"); - - auto quota = client_config_.mutable_connection_quota_spec() - ->add_rules() - ->add_quotas(); - quota->set_quota("quota"); - quota->set_charge(5); - - mock_client_ = new ::testing::NiceMock; - client_context_ = std::make_shared( - std::unique_ptr(mock_client_), client_config_); - controller_ = - std::unique_ptr(new ControllerImpl(client_context_)); - } - - std::shared_ptr client_context_; - TcpClientConfig client_config_; - ::testing::NiceMock* mock_client_; - std::unique_ptr controller_; -}; - -TEST_F(RequestHandlerImplTest, TestHandlerDisabledCheck) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should not be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)).Times(0); - - client_config_.set_disable_check_calls(true); - auto handler = controller_->CreateRequestHandler(); - handler->Check(&mock_data, - [](const Status& status) { EXPECT_TRUE(status.ok()); }); -} - -TEST_F(RequestHandlerImplTest, TestHandlerCheck) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetSourceIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetSourceUser(_)).Times(1); - - // Check should be called. - EXPECT_CALL(*mock_client_, Check(_, _, _, _)) - .WillOnce(Invoke([](const Attributes& attributes, - const std::vector& quotas, - TransportCheckFunc transport, - CheckDoneFunc on_done) -> CancelFunc { - auto map = attributes.attributes(); - EXPECT_EQ(map["key1"].string_value(), "value1"); - EXPECT_EQ(quotas.size(), 1); - EXPECT_EQ(quotas[0].quota, "quota"); - EXPECT_EQ(quotas[0].charge, 5); - return nullptr; - })); - - auto handler = controller_->CreateRequestHandler(); - handler->Check(&mock_data, nullptr); -} - -TEST_F(RequestHandlerImplTest, TestHandlerReport) { - ::testing::NiceMock mock_data; - EXPECT_CALL(mock_data, GetDestinationIpPort(_, _)).Times(1); - EXPECT_CALL(mock_data, GetReportInfo(_)).Times(1); - - // Report should be called. - EXPECT_CALL(*mock_client_, Report(_)).Times(1); - - auto handler = controller_->CreateRequestHandler(); - handler->Report(&mock_data); -} - -} // namespace tcp -} // namespace control -} // namespace istio diff --git a/src/istio/mixerclient/BUILD b/src/istio/mixerclient/BUILD deleted file mode 100644 index a34cc53e6c5..00000000000 --- a/src/istio/mixerclient/BUILD +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -py_binary( - name = "create_global_dictionary", - srcs = ["create_global_dictionary.py"], -) - -genrule( - name = "global_dictionary_header_gen", - srcs = [ - "@mixerapi_git//:global_dictionary_file", - ], - outs = [ - "global_dictionary.cc", - ], - cmd = "$(location :create_global_dictionary) $(location @mixerapi_git//:global_dictionary_file) > $@", - tools = [ - ":create_global_dictionary", - ], -) - -cc_library( - name = "mixerclient_lib", - srcs = [ - "attribute_compressor.cc", - "attribute_compressor.h", - "check_cache.cc", - "check_cache.h", - "client_impl.cc", - "client_impl.h", - "delta_update.cc", - "delta_update.h", - "global_dictionary.cc", - "global_dictionary.h", - "quota_cache.cc", - "quota_cache.h", - "referenced.cc", - "referenced.h", - "report_batch.cc", - "report_batch.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//external:mixer_api_cc_proto", - "//include/istio/mixerclient:headers_lib", - "//include/istio/quota_config:requirement_header", - "//include/istio/utils:simple_lru_cache", - "//src/istio/prefetch:quota_prefetch_lib", - "//src/istio/utils:md5_lib", - "//src/istio/utils:utils_lib", - ], -) - -cc_library( - name = "status_test_util_lib", - hdrs = [ - "status_test_util.h", - ], - visibility = ["//visibility:public"], -) - -cc_test( - name = "attribute_compressor_test", - size = "small", - srcs = ["attribute_compressor_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "check_cache_test", - size = "small", - srcs = ["check_cache_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - ":status_test_util_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "delta_update_test", - size = "small", - srcs = ["delta_update_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "report_batch_test", - size = "small", - srcs = ["report_batch_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "quota_cache_test", - size = "small", - srcs = ["quota_cache_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - ":status_test_util_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "referenced_test", - size = "small", - srcs = ["referenced_test.cc"], - linkstatic = 1, - deps = [ - ":mixerclient_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "client_impl_test", - size = "small", - srcs = ["client_impl_test.cc"], - linkopts = select({ - "//:darwin": [], - "//conditions:default": [ - "-lm", - "-lpthread", - "-lrt", - ], - }), - linkstatic = 1, - deps = [ - ":mixerclient_lib", - ":status_test_util_lib", - "//external:googletest_main", - ], -) diff --git a/src/istio/mixerclient/README.md b/src/istio/mixerclient/README.md deleted file mode 100644 index 3a1919fc45e..00000000000 --- a/src/istio/mixerclient/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Istio Mixerclient - -The Istio Mixerclient is a C++ library to support the mixer API with following features: - -- Uses simple struct Attributes to pass attributes. The library will convert them to the request proto message by using the global dictionary and per message dictionary. - -- Supports combining multiple quota calls into one single Check call together with precondition check. - -- Supports cache for precondition check result. Attributes used to calculate cache key are specified by the Mixer. By default, check cache is enabled unless CheckOptions.num_entries is 0. - -- Supports quota cache and prefetch. Attributes used to calculate quota cache key are specified by the Mixer too. By default, quota cache is enabled unless QuotaOptions.num_entries is 0. - -- Supports batch for Reports. All report requests are batched up to ReportOptions.max_batch_entries, or up to ReportOptions.max_match_time_ms. - - diff --git a/src/istio/mixerclient/attribute_compressor.cc b/src/istio/mixerclient/attribute_compressor.cc deleted file mode 100644 index f60e6f26ea0..00000000000 --- a/src/istio/mixerclient/attribute_compressor.cc +++ /dev/null @@ -1,213 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/attribute_compressor.h" -#include "include/istio/utils/protobuf.h" -#include "src/istio/mixerclient/delta_update.h" -#include "src/istio/mixerclient/global_dictionary.h" - -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_AttributeValue; -using ::istio::mixer::v1::Attributes_StringMap; -using ::istio::mixer::v1::CompressedAttributes; - -namespace istio { -namespace mixerclient { -namespace { - -// The size of first version of global dictionary. -// If any dictionary error, global dictionary will fall back to this version. -const int kGlobalDictionaryBaseSize = 111; - -// Return per message dictionary index. -int MessageDictIndex(int idx) { return -(idx + 1); } - -// Per message dictionary. -class MessageDictionary { - public: - MessageDictionary(const GlobalDictionary& global_dict) - : global_dict_(global_dict) {} - - int GetIndex(const std::string& name) { - int index; - if (global_dict_.GetIndex(name, &index)) { - return index; - } - - const auto& message_it = message_dict_.find(name); - if (message_it != message_dict_.end()) { - return MessageDictIndex(message_it->second); - } - - index = message_words_.size(); - message_words_.push_back(name); - message_dict_[name] = index; - return MessageDictIndex(index); - } - - const std::vector& GetWords() const { return message_words_; } - - private: - const GlobalDictionary& global_dict_; - - // Per message dictionary - std::vector message_words_; - std::unordered_map message_dict_; -}; - -::istio::mixer::v1::StringMap CreateStringMap( - const Attributes_StringMap& raw_map, MessageDictionary& dict) { - ::istio::mixer::v1::StringMap compressed_map; - auto* map_pb = compressed_map.mutable_entries(); - for (const auto& it : raw_map.entries()) { - (*map_pb)[dict.GetIndex(it.first)] = dict.GetIndex(it.second); - } - return compressed_map; -} - -bool CompressByDict(const Attributes& attributes, MessageDictionary& dict, - DeltaUpdate& delta_update, CompressedAttributes* pb) { - delta_update.Start(); - - // Fill attributes. - for (const auto& it : attributes.attributes()) { - const std::string& name = it.first; - const Attributes_AttributeValue& value = it.second; - - int index = dict.GetIndex(name); - - // Check delta update. If same, skip it. - if (delta_update.Check(index, value)) { - continue; - } - - // Fill the attribute to proper map. - switch (value.value_case()) { - case Attributes_AttributeValue::kStringValue: - (*pb->mutable_strings())[index] = dict.GetIndex(value.string_value()); - break; - case Attributes_AttributeValue::kBytesValue: - (*pb->mutable_bytes())[index] = value.bytes_value(); - break; - case Attributes_AttributeValue::kInt64Value: - (*pb->mutable_int64s())[index] = value.int64_value(); - break; - case Attributes_AttributeValue::kDoubleValue: - (*pb->mutable_doubles())[index] = value.double_value(); - break; - case Attributes_AttributeValue::kBoolValue: - (*pb->mutable_bools())[index] = value.bool_value(); - break; - case Attributes_AttributeValue::kTimestampValue: - (*pb->mutable_timestamps())[index] = value.timestamp_value(); - break; - case Attributes_AttributeValue::kDurationValue: - (*pb->mutable_durations())[index] = value.duration_value(); - break; - case Attributes_AttributeValue::kStringMapValue: - (*pb->mutable_string_maps())[index] = - CreateStringMap(value.string_map_value(), dict); - break; - case Attributes_AttributeValue::VALUE_NOT_SET: - break; - } - } - - return delta_update.Finish(); -} - -class BatchCompressorImpl : public BatchCompressor { - public: - BatchCompressorImpl(const GlobalDictionary& global_dict) - : dict_(global_dict), - delta_update_(DeltaUpdate::Create()), - report_(new ::istio::mixer::v1::ReportRequest) { - report_->set_global_word_count(global_dict.size()); - } - - bool Add(const Attributes& attributes) override { - CompressedAttributes pb; - if (!CompressByDict(attributes, dict_, *delta_update_, &pb)) { - return false; - } - pb.GetReflection()->Swap(report_->add_attributes(), &pb); - return true; - } - - int size() const override { return report_->attributes_size(); } - - std::unique_ptr<::istio::mixer::v1::ReportRequest> Finish() override { - for (const std::string& word : dict_.GetWords()) { - report_->add_default_words(word); - } - return std::move(report_); - } - - private: - MessageDictionary dict_; - std::unique_ptr delta_update_; - std::unique_ptr<::istio::mixer::v1::ReportRequest> report_; -}; - -} // namespace - -GlobalDictionary::GlobalDictionary() { - const std::vector& global_words = GetGlobalWords(); - for (unsigned int i = 0; i < global_words.size(); i++) { - global_dict_[global_words[i]] = i; - } - top_index_ = global_words.size(); -} - -// Lookup the index, return true if found. -bool GlobalDictionary::GetIndex(const std::string name, int* index) const { - const auto& it = global_dict_.find(name); - if (it != global_dict_.end() && it->second < top_index_) { - // Return global dictionary index. - *index = it->second; - return true; - } - return false; -} - -void GlobalDictionary::ShrinkToBase() { - if (top_index_ > kGlobalDictionaryBaseSize) { - top_index_ = kGlobalDictionaryBaseSize; - GOOGLE_LOG(INFO) << "Shrink global dictionary " << top_index_ - << " to base."; - } -} - -void AttributeCompressor::Compress( - const Attributes& attributes, - ::istio::mixer::v1::CompressedAttributes* pb) const { - MessageDictionary dict(global_dict_); - std::unique_ptr delta_update = DeltaUpdate::CreateNoOp(); - - CompressByDict(attributes, dict, *delta_update, pb); - - for (const std::string& word : dict.GetWords()) { - pb->add_words(word); - } -} - -std::unique_ptr AttributeCompressor::CreateBatchCompressor() - const { - return std::unique_ptr( - new BatchCompressorImpl(global_dict_)); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/attribute_compressor.h b/src/istio/mixerclient/attribute_compressor.h deleted file mode 100644 index f37072f692b..00000000000 --- a/src/istio/mixerclient/attribute_compressor.h +++ /dev/null @@ -1,84 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_ATTRIBUTE_COMPRESSOR_H -#define ISTIO_MIXERCLIENT_ATTRIBUTE_COMPRESSOR_H - -#include - -#include "mixer/v1/attributes.pb.h" -#include "mixer/v1/report.pb.h" - -namespace istio { -namespace mixerclient { - -// A class to store global dictionary -class GlobalDictionary { - public: - GlobalDictionary(); - - // Lookup the index, return true if found. - bool GetIndex(const std::string word, int* index) const; - - // Shrink the global dictioanry - void ShrinkToBase(); - - int size() const { return top_index_; } - - private: - std::unordered_map global_dict_; - // the last index of the global dictionary. - // If mis-matched with server, it will set to base - int top_index_; -}; - -// A attribute batch compressor for report. -class BatchCompressor { - public: - virtual ~BatchCompressor() {} - - // Add an attribute set to the batch. - // Return false if it could not be added for delta update. - virtual bool Add(const ::istio::mixer::v1::Attributes& attributes) = 0; - - // Get the batched size. - virtual int size() const = 0; - - // Finish the batch and create the batched report request. - virtual std::unique_ptr<::istio::mixer::v1::ReportRequest> Finish() = 0; -}; - -// Compress attributes. -class AttributeCompressor { - public: - void Compress(const ::istio::mixer::v1::Attributes& attributes, - ::istio::mixer::v1::CompressedAttributes* attributes_pb) const; - - // Create a batch compressor. - std::unique_ptr CreateBatchCompressor() const; - - int global_word_count() const { return global_dict_.size(); } - - // Shrink global dictionary to the first version. - void ShrinkGlobalDictionary() { global_dict_.ShrinkToBase(); } - - private: - GlobalDictionary global_dict_; -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_ATTRIBUTE_COMPRESSOR_H diff --git a/src/istio/mixerclient/attribute_compressor_test.cc b/src/istio/mixerclient/attribute_compressor_test.cc deleted file mode 100644 index 34fa59b3e67..00000000000 --- a/src/istio/mixerclient/attribute_compressor_test.cc +++ /dev/null @@ -1,271 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/attribute_compressor.h" -#include "include/istio/utils/attributes_builder.h" - -#include -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/message_differencer.h" -#include "gtest/gtest.h" - -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_AttributeValue; -using ::istio::mixer::v1::Attributes_StringMap; -using ::istio::mixer::v1::CompressedAttributes; -using std::string; - -using ::google::protobuf::TextFormat; -using ::google::protobuf::util::MessageDifferencer; - -namespace istio { -namespace mixerclient { -namespace { - -const char kAttributes[] = R"( -words: "JWT-Token" -strings { - key: 2 - value: 127 -} -strings { - key: 6 - value: 101 -} -int64s { - key: 1 - value: 35 -} -int64s { - key: 8 - value: 8080 -} -doubles { - key: 78 - value: 99.9 -} -bools { - key: 71 - value: true -} -timestamps { - key: 132 - value { - } -} -durations { - key: 29 - value { - seconds: 5 - } -} -bytes { - key: 0 - value: "text/html; charset=utf-8" -} -string_maps { - key: 15 - value { - entries { - key: 50 - value: -1 - } - entries { - key: 58 - value: 104 - } - } -} -)"; - -const char kReportAttributes[] = R"( -attributes { - strings { - key: 2 - value: 127 - } - strings { - key: 6 - value: 101 - } - int64s { - key: 1 - value: 35 - } - int64s { - key: 8 - value: 8080 - } - doubles { - key: 78 - value: 99.9 - } - bools { - key: 71 - value: true - } - timestamps { - key: 132 - value { - } - } - durations { - key: 29 - value { - seconds: 5 - } - } - bytes { - key: 0 - value: "text/html; charset=utf-8" - } - string_maps { - key: 15 - value { - entries { - key: 50 - value: -1 - } - entries { - key: 58 - value: 104 - } - } - } -} -attributes { - int64s { - key: 1 - value: 135 - } - int64s { - key: 27 - value: 111 - } - doubles { - key: 78 - value: 123.99 - } - bools { - key: 71 - value: false - } - string_maps { - key: 15 - value { - entries { - key: 32 - value: 90 - } - entries { - key: 58 - value: 104 - } - } - } -} -default_words: "JWT-Token" -global_word_count: 111 -)"; - -class AttributeCompressorTest : public ::testing::Test { - protected: - void SetUp() { - // Have to use words from global dictionary. - // Otherwise test is flaky since protobuf::map order is not deterministic - // if a word has to be in the per-message dictionary, its index depends - // on the order it created. - utils::AttributesBuilder builder(&attributes_); - builder.AddString("source.name", "connection.received.bytes_total"); - builder.AddBytes("source.ip", "text/html; charset=utf-8"); - builder.AddDouble("range", 99.9); - builder.AddInt64("source.port", 35); - builder.AddBool("keep-alive", true); - builder.AddString("source.user", "x-http-method-override"); - builder.AddInt64("target.port", 8080); - - std::chrono::time_point time_point; - std::chrono::seconds secs(5); - builder.AddTimestamp("context.timestamp", time_point); - builder.AddDuration( - "response.duration", - std::chrono::duration_cast(secs)); - - // JWT-token is only word not in the global dictionary. - std::map string_map = { - {"authorization", "JWT-Token"}, {"content-type", "application/json"}}; - builder.AddStringMap("request.headers", std::move(string_map)); - } - - Attributes attributes_; -}; - -TEST_F(AttributeCompressorTest, CompressTest) { - // A compressor with an empty global dictionary. - AttributeCompressor compressor; - ::istio::mixer::v1::CompressedAttributes attributes_pb; - compressor.Compress(attributes_, &attributes_pb); - - std::string out_str; - TextFormat::PrintToString(attributes_pb, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - ::istio::mixer::v1::CompressedAttributes expected_attributes_pb; - ASSERT_TRUE( - TextFormat::ParseFromString(kAttributes, &expected_attributes_pb)); - EXPECT_TRUE( - MessageDifferencer::Equals(attributes_pb, expected_attributes_pb)); -} - -TEST_F(AttributeCompressorTest, BatchCompressTest) { - // A compressor with an empty global dictionary. - AttributeCompressor compressor; - auto batch_compressor = compressor.CreateBatchCompressor(); - - EXPECT_TRUE(batch_compressor->Add(attributes_)); - - // modify some attributes - utils::AttributesBuilder builder(&attributes_); - builder.AddDouble("range", 123.99); - builder.AddInt64("source.port", 135); - builder.AddInt64("response.size", 111); - builder.AddBool("keep-alive", false); - builder.AddStringMap("request.headers", {{"content-type", "application/json"}, - {":method", "GET"}}); - - // Since there is no deletion, batch is good - EXPECT_TRUE(batch_compressor->Add(attributes_)); - - // remove a key - attributes_.mutable_attributes()->erase("response.size"); - // Batch should fail. - EXPECT_FALSE(batch_compressor->Add(attributes_)); - - auto report_pb = batch_compressor->Finish(); - - std::string out_str; - TextFormat::PrintToString(*report_pb, &out_str); - GOOGLE_LOG(INFO) << "===" << out_str << "==="; - - ::istio::mixer::v1::ReportRequest expected_report_pb; - ASSERT_TRUE( - TextFormat::ParseFromString(kReportAttributes, &expected_report_pb)); - report_pb->set_global_word_count(111); - EXPECT_TRUE(MessageDifferencer::Equals(*report_pb, expected_report_pb)); -} - -} // namespace -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/check_cache.cc b/src/istio/mixerclient/check_cache.cc deleted file mode 100644 index 14da9939ed5..00000000000 --- a/src/istio/mixerclient/check_cache.cc +++ /dev/null @@ -1,191 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/check_cache.h" -#include "include/istio/utils/protobuf.h" - -using namespace std::chrono; -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::CheckResponse; - -namespace istio { -namespace mixerclient { - -void CheckCache::CacheElem::CacheElem::SetResponse( - const CheckResponse &response, Tick time_now) { - if (response.has_precondition()) { - status_ = parent_.ConvertRpcStatus(response.precondition().status()); - - if (response.precondition().has_valid_duration()) { - expire_time_ = time_now + utils::ToMilliseonds( - response.precondition().valid_duration()); - } else { - // never expired. - expire_time_ = time_point::max(); - } - use_count_ = response.precondition().valid_use_count(); - } else { - status_ = Status(Code::INVALID_ARGUMENT, - "CheckResponse doesn't have PreconditionResult"); - use_count_ = 0; // 0 for not used this cache. - expire_time_ = time_now; // expired now. - } -} - -// check if the item is expired. -bool CheckCache::CacheElem::CacheElem::IsExpired(Tick time_now) { - if (time_now > expire_time_ || use_count_ == 0) { - return true; - } - if (use_count_ > 0) { - --use_count_; - } - return false; -} - -CheckCache::CheckResult::CheckResult() : status_(Code::UNAVAILABLE, "") {} - -bool CheckCache::CheckResult::IsCacheHit() const { - return status_.error_code() != Code::UNAVAILABLE; -} - -CheckCache::CheckCache(const CheckOptions &options) : options_(options) { - if (options.num_entries > 0) { - cache_.reset(new CheckLRUCache(options.num_entries)); - } -} - -CheckCache::~CheckCache() { - // FlushAll() will remove all cache items. - FlushAll(); -} - -void CheckCache::Check(const Attributes &attributes, CheckResult *result) { - Status status = Check(attributes, system_clock::now()); - if (status.error_code() != Code::NOT_FOUND) { - result->status_ = status; - } - - result->on_response_ = [this](const Status &status, - const Attributes &attributes, - const CheckResponse &response) -> Status { - if (!status.ok()) { - if (options_.network_fail_open) { - return Status::OK; - } else { - return status; - } - } else { - return CacheResponse(attributes, response, system_clock::now()); - } - }; -} - -Status CheckCache::Check(const Attributes &attributes, Tick time_now) { - if (!cache_) { - // By returning NOT_FOUND, caller will send request to server. - return Status(Code::NOT_FOUND, ""); - } - - for (const auto &it : referenced_map_) { - const Referenced &reference = it.second; - std::string signature; - if (!reference.Signature(attributes, "", &signature)) { - continue; - } - - std::lock_guard lock(cache_mutex_); - CheckLRUCache::ScopedLookup lookup(cache_.get(), signature); - if (lookup.Found()) { - CacheElem *elem = lookup.value(); - if (elem->IsExpired(time_now)) { - cache_->Remove(signature); - return Status(Code::NOT_FOUND, ""); - } - return elem->status(); - } - } - - return Status(Code::NOT_FOUND, ""); -} - -Status CheckCache::CacheResponse(const Attributes &attributes, - const CheckResponse &response, Tick time_now) { - if (!cache_ || !response.has_precondition()) { - if (response.has_precondition()) { - return ConvertRpcStatus(response.precondition().status()); - } else { - return Status(Code::INVALID_ARGUMENT, - "CheckResponse doesn't have PreconditionResult"); - } - } - - Referenced referenced; - if (!referenced.Fill(attributes, - response.precondition().referenced_attributes())) { - // Failed to decode referenced_attributes, not to cache this result. - return ConvertRpcStatus(response.precondition().status()); - } - std::string signature; - if (!referenced.Signature(attributes, "", &signature)) { - GOOGLE_LOG(ERROR) << "Response referenced mismatchs with request"; - GOOGLE_LOG(ERROR) << "Request attributes: " << attributes.DebugString(); - GOOGLE_LOG(ERROR) << "Referenced attributes: " << referenced.DebugString(); - return ConvertRpcStatus(response.precondition().status()); - } - - std::lock_guard lock(cache_mutex_); - std::string hash = referenced.Hash(); - if (referenced_map_.find(hash) == referenced_map_.end()) { - referenced_map_[hash] = referenced; - GOOGLE_LOG(INFO) << "Add a new Referenced for check cache: " - << referenced.DebugString(); - } - - CheckLRUCache::ScopedLookup lookup(cache_.get(), signature); - if (lookup.Found()) { - lookup.value()->SetResponse(response, time_now); - return lookup.value()->status(); - } - - CacheElem *cache_elem = new CacheElem(*this, response, time_now); - cache_->Insert(signature, cache_elem, 1); - return cache_elem->status(); -} - -// Flush out aggregated check requests, clear all cache items. -// Usually called at destructor. -Status CheckCache::FlushAll() { - if (cache_) { - std::lock_guard lock(cache_mutex_); - cache_->RemoveAll(); - } - - return Status::OK; -} - -Status CheckCache::ConvertRpcStatus(const ::google::rpc::Status &status) const { - // If server status code is INTERNAL, check network_fail_open flag. - if (status.code() == Code::INTERNAL && options_.network_fail_open) { - return Status::OK; - } else { - return Status(static_cast(status.code()), status.message()); - } -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/check_cache.h b/src/istio/mixerclient/check_cache.h deleted file mode 100644 index 865972fb716..00000000000 --- a/src/istio/mixerclient/check_cache.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Caches check attributes. - -#ifndef ISTIO_MIXERCLIENT_CHECK_CACHE_H -#define ISTIO_MIXERCLIENT_CHECK_CACHE_H - -#include -#include -#include -#include -#include - -#include "google/protobuf/stubs/status.h" -#include "include/istio/mixerclient/client.h" -#include "include/istio/mixerclient/options.h" -#include "include/istio/utils/simple_lru_cache.h" -#include "include/istio/utils/simple_lru_cache_inl.h" -#include "src/istio/mixerclient/referenced.h" - -namespace istio { -namespace mixerclient { - -// Cache Mixer Check call result. -// This interface is thread safe. -class CheckCache { - public: - CheckCache(const CheckOptions& options); - - virtual ~CheckCache(); - - // A check cache result for a request. Its usage - // cache->Check(attributes, result); - // if (result->IsCacheHit()) return result->Status(); - // Make remote call and on receiving response. - // result->SetReponse(status, response); - // return result->Status(); - class CheckResult { - public: - CheckResult(); - - bool IsCacheHit() const; - - ::google::protobuf::util::Status status() const { return status_; } - - void SetResponse(const ::google::protobuf::util::Status& status, - const ::istio::mixer::v1::Attributes& attributes, - const ::istio::mixer::v1::CheckResponse& response) { - if (on_response_) { - status_ = on_response_(status, attributes, response); - } - } - - private: - friend class CheckCache; - // Check status. - ::google::protobuf::util::Status status_; - - // The function to set check response. - using OnResponseFunc = std::function<::google::protobuf::util::Status( - const ::google::protobuf::util::Status&, - const ::istio::mixer::v1::Attributes& attributes, - const ::istio::mixer::v1::CheckResponse&)>; - OnResponseFunc on_response_; - }; - - void Check(const ::istio::mixer::v1::Attributes& attributes, - CheckResult* result); - - private: - friend class CheckCacheTest; - using Tick = std::chrono::time_point; - - // If the check could not be handled by the cache, returns NOT_FOUND, - // caller has to send the request to mixer. - ::google::protobuf::util::Status Check( - const ::istio::mixer::v1::Attributes& request, Tick time_now); - - // Caches a response from a remote mixer call. - // Return the converted status from response. - ::google::protobuf::util::Status CacheResponse( - const ::istio::mixer::v1::Attributes& attributes, - const ::istio::mixer::v1::CheckResponse& response, Tick time_now); - - // Flushes out all cached check responses; clears all cache items. - // Usually called at destructor. - ::google::protobuf::util::Status FlushAll(); - - // Convert from grpc status to protobuf status. - ::google::protobuf::util::Status ConvertRpcStatus( - const ::google::rpc::Status& status) const; - - class CacheElem { - public: - CacheElem(const CheckCache& parent, - const ::istio::mixer::v1::CheckResponse& response, Tick time) - : parent_(parent) { - SetResponse(response, time); - } - - // Set the response - void SetResponse(const ::istio::mixer::v1::CheckResponse& response, - Tick time_now); - - // Check if the item is expired. - bool IsExpired(Tick time_now); - - // getter for converted status from response. - ::google::protobuf::util::Status status() const { return status_; } - - private: - // To the parent cache object. - const CheckCache& parent_; - // The check status for the last check request. - ::google::protobuf::util::Status status_; - // Cache item should not be used after it is expired. - std::chrono::time_point expire_time_; - // if -1, not to check use_count. - // if 0, cache item should not be used. - // use_cound is decreased by 1 for each request, - int use_count_; - }; - - // Key is the signature of the Attributes. Value is the CacheElem. - // It is a LRU cache with maximum size. - // When the maximum size is reached, oldest idle items will be removed. - using CheckLRUCache = utils::SimpleLRUCache; - - // The check options. - CheckOptions options_; - - // Referenced map keyed with their hashes - std::unordered_map referenced_map_; - - // Mutex guarding the access of cache_; - std::mutex cache_mutex_; - - // The cache that maps from operation signature to an operation. - // We don't calculate fine grained cost for cache entries, assign each - // entry 1 cost unit. - // Guarded by mutex_, except when compare against NULL. - std::unique_ptr cache_; - - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(CheckCache); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_CHECK_CACHE_H diff --git a/src/istio/mixerclient/check_cache_test.cc b/src/istio/mixerclient/check_cache_test.cc deleted file mode 100644 index e35cac0bccd..00000000000 --- a/src/istio/mixerclient/check_cache_test.cc +++ /dev/null @@ -1,321 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/check_cache.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" -#include "include/istio/utils/protobuf.h" -#include "src/istio/mixerclient/status_test_util.h" - -using namespace std::chrono; -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::CheckResponse; -using ::istio::mixer::v1::ReferencedAttributes; - -namespace istio { -namespace mixerclient { - -time_point FakeTime(int t) { - return time_point(milliseconds(t)); -} - -class CheckCacheTest : public ::testing::Test { - public: - void SetUp() { - CheckOptions options; - cache_ = std::unique_ptr(new CheckCache(options)); - ASSERT_TRUE((bool)(cache_)); - - utils::AttributesBuilder(&attributes_) - .AddString("target.service", "this-is-a-string-value"); - } - - void VerifyDisabledCache() { - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - // Just to calculate signature - EXPECT_ERROR_CODE(Code::NOT_FOUND, cache_->Check(attributes_, FakeTime(0))); - // set to the cache - EXPECT_OK(cache_->CacheResponse(attributes_, ok_response, FakeTime(0))); - - // Still not_found, so cache is disabled. - EXPECT_ERROR_CODE(Code::NOT_FOUND, cache_->Check(attributes_, FakeTime(0))); - } - - Status Check(const Attributes& request, time_point time_now) { - return cache_->Check(request, time_now); - } - Status CacheResponse(const Attributes& attributes, - const ::istio::mixer::v1::CheckResponse& response, - time_point time_now) { - return cache_->CacheResponse(attributes, response, time_now); - } - - Attributes attributes_; - std::unique_ptr cache_; -}; - -TEST_F(CheckCacheTest, TestDisableCacheFromZeroCacheSize) { - // 0 cache entries. cache is disabled - CheckOptions options(0); - cache_ = std::unique_ptr(new CheckCache(options)); - - ASSERT_TRUE((bool)(cache_)); - VerifyDisabledCache(); -} - -TEST_F(CheckCacheTest, TestNeverExpired) { - EXPECT_ERROR_CODE(Code::NOT_FOUND, Check(attributes_, FakeTime(0))); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(10000); - EXPECT_OK(CacheResponse(attributes_, ok_response, FakeTime(0))); - for (int i = 0; i < 1000; ++i) { - EXPECT_OK(Check(attributes_, FakeTime(i * 1000000))); - } -} - -TEST_F(CheckCacheTest, TestExpiredByUseCount) { - EXPECT_ERROR_CODE(Code::NOT_FOUND, Check(attributes_, FakeTime(0))); - - CheckResponse ok_response; - // valid_use_count = 3 - ok_response.mutable_precondition()->set_valid_use_count(3); - EXPECT_OK(CacheResponse(attributes_, ok_response, FakeTime(0))); - - // 3 requests are OK - EXPECT_OK(Check(attributes_, FakeTime(1 * 1000000))); - EXPECT_OK(Check(attributes_, FakeTime(2 * 1000000))); - EXPECT_OK(Check(attributes_, FakeTime(3 * 1000000))); - - // The 4th one should fail. - EXPECT_ERROR_CODE(Code::NOT_FOUND, Check(attributes_, FakeTime(4 * 1000000))); -} - -TEST_F(CheckCacheTest, TestExpiredByDuration) { - EXPECT_ERROR_CODE(Code::NOT_FOUND, Check(attributes_, FakeTime(0))); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - // expired in 10 milliseconds. - *ok_response.mutable_precondition()->mutable_valid_duration() = - utils::CreateDuration(duration_cast(milliseconds(10))); - EXPECT_OK(CacheResponse(attributes_, ok_response, FakeTime(0))); - - // OK, In 1 milliseconds. - EXPECT_OK(Check(attributes_, FakeTime(1))); - - // Not found in 11 milliseconds. - EXPECT_ERROR_CODE(Code::NOT_FOUND, Check(attributes_, FakeTime(11))); -} - -TEST_F(CheckCacheTest, TestCheckResult) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_OK(result.status()); - - for (int i = 0; i < 100; ++i) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_TRUE(result.IsCacheHit()); - EXPECT_OK(result.status()); - } -} - -TEST_F(CheckCacheTest, TestInvalidResult) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - // Precondition result is not set - CheckResponse ok_response; - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_ERROR_CODE(Code::INVALID_ARGUMENT, result.status()); - - // Not found due to last invalid result. - CheckCache::CheckResult result1; - cache_->Check(attributes_, &result1); - EXPECT_FALSE(result1.IsCacheHit()); -} - -TEST_F(CheckCacheTest, TestCachedSetResponse) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_OK(result.status()); - - // Found in the cache - CheckCache::CheckResult result1; - cache_->Check(attributes_, &result1); - EXPECT_TRUE(result1.IsCacheHit()); - EXPECT_OK(result1.status()); - - // Set a negative response. - ok_response.mutable_precondition()->mutable_status()->set_code( - Code::UNAVAILABLE); - result1.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_ERROR_CODE(Code::UNAVAILABLE, result1.status()); -} - -TEST_F(CheckCacheTest, TestWithInvalidReferenced) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - auto match = ok_response.mutable_precondition() - ->mutable_referenced_attributes() - ->add_attribute_matches(); - match->set_condition(ReferencedAttributes::ABSENCE); - match->set_name(10000); // global index is too big - - // The status for the current check is still OK - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_OK(result.status()); - - // Since previous result was not saved to cache, this should not be cache hit - CheckCache::CheckResult result1; - cache_->Check(attributes_, &result1); - EXPECT_FALSE(result1.IsCacheHit()); -} - -TEST_F(CheckCacheTest, TestWithMismatchedReferenced) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - // Requires target.service to be absence - // But this attribute is in the request, the response is not saved to cache - auto match = ok_response.mutable_precondition() - ->mutable_referenced_attributes() - ->add_attribute_matches(); - match->set_condition(ReferencedAttributes::ABSENCE); - match->set_name(9); - - // The status for the current check is still OK - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_OK(result.status()); - - // Since previous result was not saved to cache, this should not be cache hit - CheckCache::CheckResult result1; - cache_->Check(attributes_, &result1); - EXPECT_FALSE(result1.IsCacheHit()); -} - -TEST_F(CheckCacheTest, TestTwoCacheKeys) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - auto match = ok_response.mutable_precondition() - ->mutable_referenced_attributes() - ->add_attribute_matches(); - match->set_condition(ReferencedAttributes::EXACT); - match->set_name(9); // target.service is used. - - result.SetResponse(Status::OK, attributes_, ok_response); - EXPECT_OK(result.status()); - - // Cached response is used. - CheckCache::CheckResult result1; - cache_->Check(attributes_, &result1); - EXPECT_TRUE(result1.IsCacheHit()); - - Attributes attributes1; - utils::AttributesBuilder(&attributes1) - .AddString("target.service", "different target service"); - - // Not in the cache since it has different value - CheckCache::CheckResult result2; - cache_->Check(attributes1, &result2); - EXPECT_FALSE(result2.IsCacheHit()); - - // Store the response to the cache - result2.SetResponse(Status::OK, attributes1, ok_response); - EXPECT_OK(result.status()); - - // Now it should be in the cache. - CheckCache::CheckResult result3; - cache_->Check(attributes1, &result3); - EXPECT_TRUE(result3.IsCacheHit()); - - // Also make sure key1 still in the cache - CheckCache::CheckResult result4; - cache_->Check(attributes_, &result4); - EXPECT_TRUE(result4.IsCacheHit()); -} - -TEST_F(CheckCacheTest, TestTwoReferenced) { - CheckCache::CheckResult result; - cache_->Check(attributes_, &result); - EXPECT_FALSE(result.IsCacheHit()); - - CheckResponse ok_response; - ok_response.mutable_precondition()->set_valid_use_count(1000); - auto match = ok_response.mutable_precondition() - ->mutable_referenced_attributes() - ->add_attribute_matches(); - match->set_condition(ReferencedAttributes::EXACT); - match->set_name(9); // target.service is used. - result.SetResponse(Status::OK, attributes_, ok_response); - - Attributes attributes1; - utils::AttributesBuilder(&attributes1) - .AddString("target.name", "target name"); - - // Not in the cache since it has different value - CheckCache::CheckResult result1; - cache_->Check(attributes1, &result1); - EXPECT_FALSE(result1.IsCacheHit()); - - // Store the response to the cache - CheckResponse ok_response1; - ok_response1.mutable_precondition()->set_valid_use_count(1000); - auto match1 = ok_response1.mutable_precondition() - ->mutable_referenced_attributes() - ->add_attribute_matches(); - match1->set_condition(ReferencedAttributes::EXACT); - match1->set_name(10); // target.name is used. - result1.SetResponse(Status::OK, attributes1, ok_response1); - - // Now both should be in the cache. - CheckCache::CheckResult result2; - cache_->Check(attributes_, &result2); - EXPECT_TRUE(result2.IsCacheHit()); - - CheckCache::CheckResult result3; - cache_->Check(attributes1, &result3); - EXPECT_TRUE(result3.IsCacheHit()); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/client_impl.cc b/src/istio/mixerclient/client_impl.cc deleted file mode 100644 index 2fb11d0d09d..00000000000 --- a/src/istio/mixerclient/client_impl.cc +++ /dev/null @@ -1,170 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/istio/mixerclient/client_impl.h" -#include "include/istio/mixerclient/check_response.h" -#include "include/istio/utils/protobuf.h" - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::CheckRequest; -using ::istio::mixer::v1::CheckResponse; -using ::istio::mixer::v1::ReportRequest; -using ::istio::mixer::v1::ReportResponse; - -namespace istio { -namespace mixerclient { - -MixerClientImpl::MixerClientImpl(const MixerClientOptions &options) - : options_(options) { - check_cache_ = - std::unique_ptr(new CheckCache(options.check_options)); - report_batch_ = std::unique_ptr( - new ReportBatch(options.report_options, options_.env.report_transport, - options.env.timer_create_func, compressor_)); - quota_cache_ = - std::unique_ptr(new QuotaCache(options.quota_options)); - - if (options_.env.uuid_generate_func) { - deduplication_id_base_ = options_.env.uuid_generate_func(); - } - - total_check_calls_ = 0; - total_remote_check_calls_ = 0; - total_blocking_remote_check_calls_ = 0; - total_quota_calls_ = 0; - total_remote_quota_calls_ = 0; - total_blocking_remote_quota_calls_ = 0; -} - -MixerClientImpl::~MixerClientImpl() {} - -CancelFunc MixerClientImpl::Check( - const Attributes &attributes, - const std::vector<::istio::quota_config::Requirement> "as, - TransportCheckFunc transport, CheckDoneFunc on_done) { - ++total_check_calls_; - - std::unique_ptr check_result( - new CheckCache::CheckResult); - check_cache_->Check(attributes, check_result.get()); - - CheckResponseInfo check_response_info; - check_response_info.is_check_cache_hit = check_result->IsCacheHit(); - check_response_info.response_status = check_result->status(); - - if (check_result->IsCacheHit() && !check_result->status().ok()) { - on_done(check_response_info); - return nullptr; - } - - if (!quotas.empty()) { - ++total_quota_calls_; - } - std::unique_ptr quota_result( - new QuotaCache::CheckResult); - // Only use quota cache if Check is using cache with OK status. - // Otherwise, a remote Check call may be rejected, but quota amounts were - // substracted from quota cache already. - quota_cache_->Check(attributes, quotas, check_result->IsCacheHit(), - quota_result.get()); - - CheckRequest request; - bool quota_call = quota_result->BuildRequest(&request); - check_response_info.is_quota_cache_hit = quota_result->IsCacheHit(); - check_response_info.response_status = quota_result->status(); - if (check_result->IsCacheHit() && quota_result->IsCacheHit()) { - on_done(check_response_info); - on_done = nullptr; - if (!quota_call) { - return nullptr; - } - } - - compressor_.Compress(attributes, request.mutable_attributes()); - request.set_global_word_count(compressor_.global_word_count()); - request.set_deduplication_id(deduplication_id_base_ + - std::to_string(deduplication_id_.fetch_add(1))); - - // Need to make a copy for processing the response for check cache. - Attributes *request_copy = new Attributes(attributes); - auto response = new CheckResponse; - // Lambda capture could not pass unique_ptr, use raw pointer. - CheckCache::CheckResult *raw_check_result = check_result.release(); - QuotaCache::CheckResult *raw_quota_result = quota_result.release(); - if (!transport) { - transport = options_.env.check_transport; - } - // We are going to make a remote call now. - ++total_remote_check_calls_; - if (!quotas.empty()) { - ++total_remote_quota_calls_; - } - if (on_done) { - ++total_blocking_remote_check_calls_; - if (!quotas.empty()) { - ++total_blocking_remote_quota_calls_; - } - } - - return transport( - request, response, - [this, request_copy, response, raw_check_result, raw_quota_result, - on_done](const Status &status) { - raw_check_result->SetResponse(status, *request_copy, *response); - raw_quota_result->SetResponse(status, *request_copy, *response); - CheckResponseInfo check_response_info; - if (on_done) { - if (!raw_check_result->status().ok()) { - check_response_info.response_status = raw_check_result->status(); - } else { - check_response_info.response_status = raw_quota_result->status(); - } - on_done(check_response_info); - } - delete raw_check_result; - delete raw_quota_result; - delete request_copy; - delete response; - - if (utils::InvalidDictionaryStatus(status)) { - compressor_.ShrinkGlobalDictionary(); - } - }); -} - -void MixerClientImpl::Report(const Attributes &attributes) { - report_batch_->Report(attributes); -} - -void MixerClientImpl::GetStatistics(Statistics *stat) const { - stat->total_check_calls = total_check_calls_; - stat->total_remote_check_calls = total_remote_check_calls_; - stat->total_blocking_remote_check_calls = total_blocking_remote_check_calls_; - stat->total_quota_calls = total_quota_calls_; - stat->total_remote_quota_calls = total_remote_quota_calls_; - stat->total_blocking_remote_quota_calls = total_blocking_remote_quota_calls_; - stat->total_report_calls = report_batch_->total_report_calls(); - stat->total_remote_report_calls = report_batch_->total_remote_report_calls(); -} - -// Creates a MixerClient object. -std::unique_ptr CreateMixerClient( - const MixerClientOptions &options) { - return std::unique_ptr(new MixerClientImpl(options)); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/client_impl.h b/src/istio/mixerclient/client_impl.h deleted file mode 100644 index d23ff15842e..00000000000 --- a/src/istio/mixerclient/client_impl.h +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_CLIENT_IMPL_H -#define ISTIO_MIXERCLIENT_CLIENT_IMPL_H - -#include "include/istio/mixerclient/client.h" -#include "src/istio/mixerclient/attribute_compressor.h" -#include "src/istio/mixerclient/check_cache.h" -#include "src/istio/mixerclient/quota_cache.h" -#include "src/istio/mixerclient/report_batch.h" - -#include - -namespace istio { -namespace mixerclient { - -class MixerClientImpl : public MixerClient { - public: - // Constructor - MixerClientImpl(const MixerClientOptions& options); - - // Destructor - virtual ~MixerClientImpl(); - - CancelFunc Check( - const ::istio::mixer::v1::Attributes& attributes, - const std::vector<::istio::quota_config::Requirement>& quotas, - TransportCheckFunc transport, CheckDoneFunc on_done) override; - void Report(const ::istio::mixer::v1::Attributes& attributes) override; - - void GetStatistics(Statistics* stat) const override; - - private: - // Store the options - MixerClientOptions options_; - - // To compress attributes. - AttributeCompressor compressor_; - - // Cache for Check call. - std::unique_ptr check_cache_; - // Report batch. - std::unique_ptr report_batch_; - // Cache for Quota call. - std::unique_ptr quota_cache_; - - // for deduplication_id - std::string deduplication_id_base_; - std::atomic deduplication_id_; - - // Atomic objects for recording statistics. - // check cache miss rate: - // total_blocking_remote_check_calls_ / total_check_calls_. - // quota cache miss rate: - // total_blocking_remote_quota_calls_ / total_quota_calls_. - std::atomic_int_fast64_t total_check_calls_; - std::atomic_int_fast64_t total_remote_check_calls_; - std::atomic_int_fast64_t total_blocking_remote_check_calls_; - std::atomic_int_fast64_t total_quota_calls_; - std::atomic_int_fast64_t total_remote_quota_calls_; - std::atomic_int_fast64_t total_blocking_remote_quota_calls_; - - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MixerClientImpl); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_CLIENT_IMPL_H diff --git a/src/istio/mixerclient/client_impl_test.cc b/src/istio/mixerclient/client_impl_test.cc deleted file mode 100644 index f15022239ce..00000000000 --- a/src/istio/mixerclient/client_impl_test.cc +++ /dev/null @@ -1,345 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "include/istio/mixerclient/check_response.h" -#include "include/istio/mixerclient/client.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/mixerclient/status_test_util.h" - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::CheckRequest; -using ::istio::mixer::v1::CheckResponse; -using ::istio::mixerclient::CheckResponseInfo; -using ::istio::quota_config::Requirement; -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace mixerclient { -namespace { - -const std::string kRequestCount = "RequestCount"; - -// A mocking class to mock CheckTransport interface. -class MockCheckTransport { - public: - MOCK_METHOD3(Check, void(const CheckRequest&, CheckResponse*, DoneFunc)); - TransportCheckFunc GetFunc() { - return [this](const CheckRequest& request, CheckResponse* response, - DoneFunc on_done) -> CancelFunc { - Check(request, response, on_done); - return nullptr; - }; - } -}; - -class MixerClientImplTest : public ::testing::Test { - public: - MixerClientImplTest() { - quotas_.push_back({kRequestCount, 1}); - CreateClient(true /* check_cache */, true /* quota_cache */); - } - - void CreateClient(bool check_cache, bool quota_cache) { - MixerClientOptions options(CheckOptions(check_cache ? 1 : 0 /*entries */), - ReportOptions(1, 1000), - QuotaOptions(quota_cache ? 1 : 0 /* entries */, - 600000 /* expiration_ms */)); - options.check_options.network_fail_open = false; - options.env.check_transport = mock_check_transport_.GetFunc(); - client_ = CreateMixerClient(options); - } - - Attributes request_; - std::vector quotas_; - std::unique_ptr client_; - MockCheckTransport mock_check_transport_; - TransportCheckFunc empty_transport_; -}; - -TEST_F(MixerClientImplTest, TestSuccessCheck) { - EXPECT_CALL(mock_check_transport_, Check(_, _, _)) - .WillOnce(Invoke([](const CheckRequest& request, CheckResponse* response, - DoneFunc on_done) { - response->mutable_precondition()->set_valid_use_count(1000); - on_done(Status::OK); - })); - - // Not to test quota - std::vector empty_quotas; - CheckResponseInfo check_response_info; - client_->Check(request_, empty_quotas, empty_transport_, - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_TRUE(check_response_info.response_status.ok()); - - for (int i = 0; i < 10; i++) { - // Other calls should be cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, empty_quotas, empty_transport_, - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_TRUE(check_response_info1.response_status.ok()); - } - - Statistics stat; - client_->GetStatistics(&stat); - EXPECT_EQ(stat.total_check_calls, 11); - // The first check call is a remote blocking check call. - EXPECT_EQ(stat.total_remote_check_calls, 1); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 1); - // Empty quota does not trigger any quota call. - EXPECT_EQ(stat.total_quota_calls, 0); - EXPECT_EQ(stat.total_remote_quota_calls, 0); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 0); -} - -TEST_F(MixerClientImplTest, TestPerRequestTransport) { - // Global transport should not be called. - EXPECT_CALL(mock_check_transport_, Check(_, _, _)).Times(0); - - // For local pre-request transport. - MockCheckTransport local_check_transport; - EXPECT_CALL(local_check_transport, Check(_, _, _)) - .WillOnce(Invoke([](const CheckRequest& request, CheckResponse* response, - DoneFunc on_done) { - response->mutable_precondition()->set_valid_use_count(1000); - on_done(Status::OK); - })); - - // Not to test quota - std::vector empty_quotas; - CheckResponseInfo check_response_info; - client_->Check(request_, empty_quotas, local_check_transport.GetFunc(), - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_TRUE(check_response_info.response_status.ok()); - - for (int i = 0; i < 10; i++) { - // Other calls should be cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, empty_quotas, local_check_transport.GetFunc(), - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_TRUE(check_response_info1.response_status.ok()); - } - - Statistics stat; - client_->GetStatistics(&stat); - EXPECT_EQ(stat.total_check_calls, 11); - // The first check call is a remote blocking check call. - EXPECT_EQ(stat.total_remote_check_calls, 1); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 1); - // Empty quota does not trigger any quota call. - EXPECT_EQ(stat.total_quota_calls, 0); - EXPECT_EQ(stat.total_remote_quota_calls, 0); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 0); -} - -TEST_F(MixerClientImplTest, TestNoCheckCache) { - CreateClient(false /* check_cache */, true /* quota_cache */); - - int call_counts = 0; - EXPECT_CALL(mock_check_transport_, Check(_, _, _)) - .WillRepeatedly(Invoke([&](const CheckRequest& request, - CheckResponse* response, DoneFunc on_done) { - response->mutable_precondition()->set_valid_use_count(1000); - CheckResponse::QuotaResult quota_result; - quota_result.set_granted_amount(10); - quota_result.mutable_valid_duration()->set_seconds(10); - (*response->mutable_quotas())[kRequestCount] = quota_result; - call_counts++; - on_done(Status::OK); - })); - - CheckResponseInfo check_response_info; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_TRUE(check_response_info.response_status.ok()); - - for (int i = 0; i < 10; i++) { - // Other calls are not cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_TRUE(check_response_info1.response_status.ok()); - } - // Call count 11 since check is not cached. - EXPECT_EQ(call_counts, 11); - Statistics stat; - client_->GetStatistics(&stat); - // Because there is no check cache, we make remote blocking call every time. - EXPECT_EQ(stat.total_check_calls, 11); - EXPECT_EQ(stat.total_remote_check_calls, 11); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 11); - EXPECT_EQ(stat.total_quota_calls, 11); - EXPECT_EQ(stat.total_remote_quota_calls, 11); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 11); -} - -TEST_F(MixerClientImplTest, TestNoQuotaCache) { - CreateClient(true /* check_cache */, false /* quota_cache */); - - int call_counts = 0; - EXPECT_CALL(mock_check_transport_, Check(_, _, _)) - .WillRepeatedly(Invoke([&](const CheckRequest& request, - CheckResponse* response, DoneFunc on_done) { - response->mutable_precondition()->set_valid_use_count(1000); - CheckResponse::QuotaResult quota_result; - quota_result.set_granted_amount(10); - quota_result.mutable_valid_duration()->set_seconds(10); - (*response->mutable_quotas())[kRequestCount] = quota_result; - call_counts++; - on_done(Status::OK); - })); - - CheckResponseInfo check_response_info; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_TRUE(check_response_info.response_status.ok()); - - for (int i = 0; i < 10; i++) { - // Other calls should be cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_TRUE(check_response_info1.response_status.ok()); - } - // Call count 11 since quota is not cached. - EXPECT_EQ(call_counts, 11); - Statistics stat; - client_->GetStatistics(&stat); - // Because there is no quota cache, we make remote blocking call every time. - EXPECT_EQ(stat.total_check_calls, 11); - EXPECT_EQ(stat.total_remote_check_calls, 11); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 11); - EXPECT_EQ(stat.total_quota_calls, 11); - EXPECT_EQ(stat.total_remote_quota_calls, 11); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 11); -} - -TEST_F(MixerClientImplTest, TestSuccessCheckAndQuota) { - int call_counts = 0; - EXPECT_CALL(mock_check_transport_, Check(_, _, _)) - .WillRepeatedly(Invoke([&](const CheckRequest& request, - CheckResponse* response, DoneFunc on_done) { - response->mutable_precondition()->set_valid_use_count(1000); - CheckResponse::QuotaResult quota_result; - quota_result.set_granted_amount(10); - quota_result.mutable_valid_duration()->set_seconds(10); - (*response->mutable_quotas())[kRequestCount] = quota_result; - call_counts++; - on_done(Status::OK); - })); - - CheckResponseInfo check_response_info; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_TRUE(check_response_info.response_status.ok()); - - for (int i = 0; i < 10; i++) { - // Other calls should be cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_TRUE(check_response_info1.response_status.ok()); - } - // Call count should be less than 4 - EXPECT_LE(call_counts, 3); - Statistics stat; - client_->GetStatistics(&stat); - // Less than 4 remote calls are made for prefetching, and they are - // non-blocking remote calls. - EXPECT_EQ(stat.total_check_calls, 11); - EXPECT_LE(stat.total_remote_check_calls, 3); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 1); - EXPECT_EQ(stat.total_quota_calls, 11); - EXPECT_LE(stat.total_remote_quota_calls, 3); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 1); -} - -TEST_F(MixerClientImplTest, TestFailedCheckAndQuota) { - EXPECT_CALL(mock_check_transport_, Check(_, _, _)) - .WillOnce(Invoke([](const CheckRequest& request, CheckResponse* response, - DoneFunc on_done) { - response->mutable_precondition()->mutable_status()->set_code( - Code::FAILED_PRECONDITION); - response->mutable_precondition()->set_valid_use_count(100); - CheckResponse::QuotaResult quota_result; - quota_result.set_granted_amount(10); - quota_result.mutable_valid_duration()->set_seconds(10); - (*response->mutable_quotas())[kRequestCount] = quota_result; - on_done(Status::OK); - })); - - CheckResponseInfo check_response_info; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info](const CheckResponseInfo& info) { - check_response_info.response_status = info.response_status; - }); - EXPECT_ERROR_CODE(Code::FAILED_PRECONDITION, - check_response_info.response_status); - - for (int i = 0; i < 10; i++) { - // Other calls should be cached. - CheckResponseInfo check_response_info1; - client_->Check(request_, quotas_, empty_transport_, - [&check_response_info1](const CheckResponseInfo& info) { - check_response_info1.response_status = - info.response_status; - }); - EXPECT_ERROR_CODE(Code::FAILED_PRECONDITION, - check_response_info1.response_status); - } - Statistics stat; - client_->GetStatistics(&stat); - // The first call is a remote blocking call, which returns failed precondition - // in check response. Following calls only make check cache calls and return. - EXPECT_EQ(stat.total_check_calls, 11); - EXPECT_EQ(stat.total_remote_check_calls, 1); - EXPECT_EQ(stat.total_blocking_remote_check_calls, 1); - EXPECT_EQ(stat.total_quota_calls, 1); - EXPECT_EQ(stat.total_remote_quota_calls, 1); - EXPECT_EQ(stat.total_blocking_remote_quota_calls, 1); -} - -} // namespace -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/create_global_dictionary.py b/src/istio/mixerclient/create_global_dictionary.py deleted file mode 100755 index 99a3551d80d..00000000000 --- a/src/istio/mixerclient/create_global_dictionary.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/python -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys - -TOP = r"""/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/global_dictionary.h" - -namespace istio { -namespace mixerclient { -namespace { - -/* - * Automatically generated global dictionary from - * https://github.com/istio/api/blob/master/mixer/v1/global_dictionary.yaml - * by run: - * ./create_global_dictionary.py \ - * bazel-mixerclient/external/mixerapi_git/mixer/v1/global_dictionary.yaml \ - * > src/global_dictionary.cc - */ - -const std::vector kGlobalWords{ -""" - -BOTTOM = r"""}; - -} // namespace - -const std::vector& GetGlobalWords() { return kGlobalWords; } - -} // namespace mixerclient -} // namespace istio""" - -all_words = '' -with open(sys.argv[1]) as src_file: - for line in src_file: - if line.startswith("-"): - all_words += " \"" + line[1:].strip() + "\",\n" - -print TOP + all_words + BOTTOM - - diff --git a/src/istio/mixerclient/delta_update.cc b/src/istio/mixerclient/delta_update.cc deleted file mode 100644 index e4f02641671..00000000000 --- a/src/istio/mixerclient/delta_update.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "src/istio/mixerclient/delta_update.h" - -#include "google/protobuf/util/message_differencer.h" - -#include - -using ::google::protobuf::util::MessageDifferencer; -using ::istio::mixer::v1::Attributes_AttributeValue; - -namespace istio { -namespace mixerclient { -namespace { - -class DeltaUpdateImpl : public DeltaUpdate { - public: - // Start a update for a request. - void Start() override { - prev_set_.clear(); - for (const auto& it : prev_map_) { - prev_set_.insert(it.first); - } - } - - bool Check(int index, const Attributes_AttributeValue& value) override { - bool same = false; - const auto& it = prev_map_.find(index); - if (it != prev_map_.end()) { - if (MessageDifferencer::Equals(it->second, value)) { - same = true; - } - } - if (!same) { - prev_map_[index] = value; - } - prev_set_.erase(index); - return same; - } - - // "deleted" is not supported for now. If some attributes are missing, - // return false to indicate delta update is not supported. - bool Finish() override { return prev_set_.empty(); } - - private: - // The remaining attributes from previous. - std::set prev_set_; - - // The attribute map from previous. - std::map prev_map_; -}; - -// An optimization for non-delta update case. -class DeltaUpdateNoOpImpl : public DeltaUpdate { - public: - void Start() override {} - bool Check(int index, const Attributes_AttributeValue& value) override { - return false; - } - bool Finish() override { return true; } -}; - -} // namespace - -std::unique_ptr DeltaUpdate::Create() { - return std::unique_ptr(new DeltaUpdateImpl); -} - -std::unique_ptr DeltaUpdate::CreateNoOp() { - return std::unique_ptr(new DeltaUpdateNoOpImpl); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/delta_update.h b/src/istio/mixerclient/delta_update.h deleted file mode 100644 index 3ec16a65f5d..00000000000 --- a/src/istio/mixerclient/delta_update.h +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_DELTA_UPDATE_H -#define ISTIO_MIXERCLIENT_DELTA_UPDATE_H - -#include "mixer/v1/attributes.pb.h" - -#include - -namespace istio { -namespace mixerclient { - -// A class to support attribute delta update. -// It has previous attribute values and check -// for the current one. -class DeltaUpdate { - public: - virtual ~DeltaUpdate() {} - - // Start a new delta update - virtual void Start() = 0; - - // Check an attribute, return true if it is in the previous - // set with same value, so no need to send it again. - // Each attribute in the current set needs to call this method. - virtual bool Check( - int index, - const ::istio::mixer::v1::Attributes_AttributeValue& value) = 0; - - // Finish a delta update. - // Return false if delta update is not supported. - // For example, "deleted" is not supported, if some attributes are - // missing, delta update will not be supported. - virtual bool Finish() = 0; - - // Create an instance. - static std::unique_ptr Create(); - - // Create an no-op instance; an optimization for no delta update cases. - static std::unique_ptr CreateNoOp(); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_DELTA_UPDATE_H diff --git a/src/istio/mixerclient/delta_update_test.cc b/src/istio/mixerclient/delta_update_test.cc deleted file mode 100644 index b2fda95b297..00000000000 --- a/src/istio/mixerclient/delta_update_test.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/delta_update.h" -#include "gtest/gtest.h" - -using ::istio::mixer::v1::Attributes_AttributeValue; - -namespace istio { -namespace mixerclient { - -class DeltaUpdateTest : public ::testing::Test { - public: - void SetUp() { - string_map_value_ = StringMapValue({{"foo", "bar"}}); - update_ = DeltaUpdate::Create(); - - update_->Start(); - EXPECT_FALSE(update_->Check(1, Int64Value(1))); - EXPECT_FALSE(update_->Check(2, Int64Value(2))); - EXPECT_FALSE(update_->Check(3, string_map_value_)); - EXPECT_TRUE(update_->Finish()); - } - - ::istio::mixer::v1::Attributes_AttributeValue Int64Value(int64_t i) { - ::istio::mixer::v1::Attributes_AttributeValue v; - v.set_int64_value(i); - return v; - } - - ::istio::mixer::v1::Attributes_AttributeValue StringValue( - const std::string& str) { - ::istio::mixer::v1::Attributes_AttributeValue v; - v.set_string_value(str); - return v; - } - - ::istio::mixer::v1::Attributes_AttributeValue StringMapValue( - std::map&& string_map) { - ::istio::mixer::v1::Attributes_AttributeValue v; - auto entries = v.mutable_string_map_value()->mutable_entries(); - for (const auto& map_it : string_map) { - (*entries)[map_it.first] = map_it.second; - } - return v; - } - - std::unique_ptr update_; - Attributes_AttributeValue string_map_value_; -}; - -TEST_F(DeltaUpdateTest, TestUpdateNoDelete) { - update_->Start(); - // 1: value is the same. - EXPECT_TRUE(update_->Check(1, Int64Value(1))); - // 2: value is different. - EXPECT_FALSE(update_->Check(2, Int64Value(3))); - // 3: compare string map. - EXPECT_TRUE(update_->Check(3, string_map_value_)); - // 4: an new attribute. - EXPECT_FALSE(update_->Check(4, Int64Value(4))); - // No missing item - EXPECT_TRUE(update_->Finish()); -} - -TEST_F(DeltaUpdateTest, TestUpdateWithDelete) { - update_->Start(); - // 1: value is the same. - EXPECT_TRUE(update_->Check(1, Int64Value(1))); - - // 2: is missing - - // 3: compare string map - EXPECT_FALSE(update_->Check(3, StringMapValue({}))); - - // 4: an new attribute. - EXPECT_FALSE(update_->Check(4, Int64Value(4))); - - // There is a missing item - EXPECT_FALSE(update_->Finish()); -} - -TEST_F(DeltaUpdateTest, TestDifferentType) { - update_->Start(); - // 1 is differnt type. - EXPECT_FALSE(update_->Check(1, StringValue(""))); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/quota_cache.cc b/src/istio/mixerclient/quota_cache.cc deleted file mode 100644 index cfd97385d6c..00000000000 --- a/src/istio/mixerclient/quota_cache.cc +++ /dev/null @@ -1,279 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/quota_cache.h" -#include "include/istio/utils/protobuf.h" - -using namespace std::chrono; -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_AttributeValue; -using ::istio::mixer::v1::CheckRequest; -using ::istio::mixer::v1::CheckResponse; -using ::istio::prefetch::QuotaPrefetch; -using ::istio::quota_config::Requirement; - -namespace istio { -namespace mixerclient { - -QuotaCache::CacheElem::CacheElem(const std::string& name) : name_(name) { - prefetch_ = QuotaPrefetch::Create( - [this](int amount, QuotaPrefetch::DoneFunc fn, QuotaPrefetch::Tick t) { - Alloc(amount, fn); - }, - QuotaPrefetch::Options(), system_clock::now()); -} - -void QuotaCache::CacheElem::Alloc(int amount, QuotaPrefetch::DoneFunc fn) { - quota_->amount = amount; - quota_->best_effort = true; - quota_->response_func = - [fn](const Attributes&, - const CheckResponse::QuotaResult* result) -> bool { - int amount = -1; - milliseconds expire = duration_cast(minutes(1)); - if (result != nullptr) { - amount = result->granted_amount(); - if (result->has_valid_duration()) { - expire = utils::ToMilliseonds(result->valid_duration()); - } - } - fn(amount, expire, system_clock::now()); - return true; - }; -} - -void QuotaCache::CacheElem::Quota(int amount, CheckResult::Quota* quota) { - quota_ = quota; - if (prefetch_->Check(amount, system_clock::now())) { - quota->result = CheckResult::Quota::Passed; - } else { - quota->result = CheckResult::Quota::Rejected; - } - - // A hack that requires prefetch code to call transport Alloc() function - // within Check() call. - quota_ = nullptr; -} - -QuotaCache::CheckResult::CheckResult() : status_(Code::UNAVAILABLE, "") {} - -bool QuotaCache::CheckResult::IsCacheHit() const { - return status_.error_code() != Code::UNAVAILABLE; -} - -bool QuotaCache::CheckResult::BuildRequest(CheckRequest* request) { - int pending_count = 0; - std::string rejected_quota_names; - for (const auto& quota : quotas_) { - // TODO: return used quota amount to passed quotas. - if (quota.result == Quota::Rejected) { - if (!rejected_quota_names.empty()) { - rejected_quota_names += ","; - } - rejected_quota_names += quota.name; - } else if (quota.result == Quota::Pending) { - ++pending_count; - } - if (quota.response_func) { - CheckRequest::QuotaParams param; - param.set_amount(quota.amount); - param.set_best_effort(quota.best_effort); - (*request->mutable_quotas())[quota.name] = param; - } - } - if (!rejected_quota_names.empty()) { - status_ = - Status(Code::RESOURCE_EXHAUSTED, - std::string("Quota is exhausted for: ") + rejected_quota_names); - } else if (pending_count == 0) { - status_ = Status::OK; - } - return request->quotas().size() > 0; -} - -void QuotaCache::CheckResult::SetResponse(const Status& status, - const Attributes& attributes, - const CheckResponse& response) { - std::string rejected_quota_names; - for (const auto& quota : quotas_) { - if (quota.response_func) { - const CheckResponse::QuotaResult* result = nullptr; - if (status.ok()) { - const auto& quotas = response.quotas(); - const auto& it = quotas.find(quota.name); - if (it != quotas.end()) { - result = &it->second; - } else { - GOOGLE_LOG(ERROR) - << "Quota response did not have quota for: " << quota.name; - } - } - if (!quota.response_func(attributes, result)) { - if (!rejected_quota_names.empty()) { - rejected_quota_names += ","; - } - rejected_quota_names += quota.name; - } - } - } - if (!rejected_quota_names.empty()) { - status_ = - Status(Code::RESOURCE_EXHAUSTED, - std::string("Quota is exhausted for: ") + rejected_quota_names); - } else { - status_ = Status::OK; - } -} - -QuotaCache::QuotaCache(const QuotaOptions& options) : options_(options) { - if (options.num_entries > 0) { - cache_.reset(new QuotaLRUCache(options.num_entries)); - cache_->SetMaxIdleSeconds(options.expiration_ms / 1000.0); - } -} - -QuotaCache::~QuotaCache() { - // FlushAll() will remove all cache items. - FlushAll(); -} - -void QuotaCache::CheckCache(const Attributes& request, bool check_use_cache, - CheckResult::Quota* quota) { - // If check is not using cache, that check may be rejected. - // If quota cache is used, quota amount is already substracted from the cache. - // If the check is rejected, there is not easy way to add them back to cache. - // The workaround is not to use quota cache if check is not in the cache. - if (!cache_ || !check_use_cache) { - quota->best_effort = false; - quota->result = CheckResult::Quota::Pending; - quota->response_func = - [](const Attributes&, - const CheckResponse::QuotaResult* result) -> bool { - // nullptr means connection error, for quota, it is fail open for - // connection error. - return result == nullptr || result->granted_amount() > 0; - }; - return; - } - - std::lock_guard lock(cache_mutex_); - PerQuotaReferenced& quota_ref = quota_referenced_map_[quota->name]; - for (const auto& it : quota_ref.referenced_map) { - const Referenced& referenced = it.second; - std::string signature; - if (!referenced.Signature(request, quota->name, &signature)) { - continue; - } - QuotaLRUCache::ScopedLookup lookup(cache_.get(), signature); - if (lookup.Found()) { - CacheElem* cache_elem = lookup.value(); - cache_elem->Quota(quota->amount, quota); - return; - } - } - - if (!quota_ref.pending_item) { - quota_ref.pending_item.reset(new CacheElem(quota->name)); - } - quota_ref.pending_item->Quota(quota->amount, quota); - - auto saved_func = quota->response_func; - std::string quota_name = quota->name; - quota->response_func = [saved_func, quota_name, this]( - const Attributes& attributes, - const CheckResponse::QuotaResult* result) -> bool { - SetResponse(attributes, quota_name, result); - if (saved_func) { - return saved_func(attributes, result); - } - return true; - }; -} - -void QuotaCache::SetResponse(const Attributes& attributes, - const std::string& quota_name, - const CheckResponse::QuotaResult* result) { - if (result == nullptr) { - return; - } - - Referenced referenced; - if (!referenced.Fill(attributes, result->referenced_attributes())) { - return; - } - - std::string signature; - if (!referenced.Signature(attributes, quota_name, &signature)) { - GOOGLE_LOG(ERROR) << "Quota response referenced mismatchs with request"; - GOOGLE_LOG(ERROR) << "Request attributes: " << attributes.DebugString(); - GOOGLE_LOG(ERROR) << "Referenced attributes: " << referenced.DebugString(); - return; - } - - std::lock_guard lock(cache_mutex_); - QuotaLRUCache::ScopedLookup lookup(cache_.get(), signature); - if (lookup.Found()) { - // Not to override the existing cache entry. - return; - } - - PerQuotaReferenced& quota_ref = quota_referenced_map_[quota_name]; - std::string hash = referenced.Hash(); - if (quota_ref.referenced_map.find(hash) == quota_ref.referenced_map.end()) { - quota_ref.referenced_map[hash] = referenced; - GOOGLE_LOG(INFO) << "Add a new Referenced for quota cache: " << quota_name - << ", reference: " << referenced.DebugString(); - } - - cache_->Insert(signature, quota_ref.pending_item.release(), 1); -} - -void QuotaCache::Check(const Attributes& request, - const std::vector& quotas, bool use_cache, - CheckResult* result) { - for (const auto& requirement : quotas) { - CheckResult::Quota quota = {requirement.quota, requirement.charge}; - CheckCache(request, use_cache, "a); - result->quotas_.push_back(quota); - } -} - -// TODO: hookup with a timer object to call Flush() periodically. -// Be careful; some transport callback functions may be still using -// expired items, need to add ref_count into these callback functions. -Status QuotaCache::Flush() { - if (cache_) { - std::lock_guard lock(cache_mutex_); - cache_->RemoveExpiredEntries(); - } - - return Status::OK; -} - -// Flush out aggregated check requests, clear all cache items. -// Usually called at destructor. -Status QuotaCache::FlushAll() { - if (cache_) { - std::lock_guard lock(cache_mutex_); - cache_->RemoveAll(); - } - - return Status::OK; -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/quota_cache.h b/src/istio/mixerclient/quota_cache.h deleted file mode 100644 index ce7d1025f9e..00000000000 --- a/src/istio/mixerclient/quota_cache.h +++ /dev/null @@ -1,174 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Caches quota calls. - -#ifndef ISTIO_MIXERCLIENT_QUOTA_CACHE_H -#define ISTIO_MIXERCLIENT_QUOTA_CACHE_H - -#include -#include -#include - -#include "include/istio/mixerclient/client.h" -#include "include/istio/prefetch/quota_prefetch.h" -#include "include/istio/utils/simple_lru_cache.h" -#include "include/istio/utils/simple_lru_cache_inl.h" -#include "src/istio/mixerclient/referenced.h" - -namespace istio { -namespace mixerclient { - -// Cache Mixer Quota Attributes. -// This interface is thread safe. -class QuotaCache { - public: - QuotaCache(const QuotaOptions& options); - - virtual ~QuotaCache(); - - // A class to batch multiple quota requests. - // Its usage: - // cache->Quota(attributes, &result); - // send = result->BuildRequest(&request); - // if (cache->IsCacheHit()) return result->Result(); - // If send is true, make a remote call, on response. - // result->SetResponse(status, response); - // return result->Result(); - class CheckResult { - public: - CheckResult(); - - // Build CheckRequest::quotas fields, return true if remote quota call - // is required. - bool BuildRequest(::istio::mixer::v1::CheckRequest* request); - - bool IsCacheHit() const; - - ::google::protobuf::util::Status status() const { return status_; } - - void SetResponse(const ::google::protobuf::util::Status& status, - const ::istio::mixer::v1::Attributes& attributes, - const ::istio::mixer::v1::CheckResponse& response); - - private: - friend class QuotaCache; - // Hold pending quota data needed to talk to server. - struct Quota { - std::string name; - int64_t amount; - bool best_effort; - - enum Result { - Pending = 0, - Passed, - Rejected, - }; - Result result; - - // The function to set the quota response from server. - using OnResponseFunc = std::function; - OnResponseFunc response_func; - }; - - ::google::protobuf::util::Status status_; - - // The list of pending quota needed to talk to server. - std::vector quotas_; - }; - - // Check quota cache for a request, result will be stored in CacaheResult. - void Check(const ::istio::mixer::v1::Attributes& request, - const std::vector<::istio::quota_config::Requirement>& quotas, - bool use_cache, CheckResult* result); - - private: - // Check quota cache. - void CheckCache(const ::istio::mixer::v1::Attributes& request, bool use_cache, - CheckResult::Quota* quota); - - // Invalidates expired check responses. - // Called at time specified by GetNextFlushInterval(). - ::google::protobuf::util::Status Flush(); - - // Flushes out all cached check responses; clears all cache items. - // Usually called at destructor. - ::google::protobuf::util::Status FlushAll(); - - // The cache element for each quota metric. - class CacheElem { - public: - CacheElem(const std::string& name); - - // Use the prefetch object to check the quota. - void Quota(int amount, CheckResult::Quota* quota); - - // The quota name. - const std::string& quota_name() const { return name_; } - - private: - // The quota allocation call. - void Alloc(int amount, prefetch::QuotaPrefetch::DoneFunc fn); - - std::string name_; - - // A temporary pending quota result. - CheckResult::Quota* quota_; - - // The prefetch object. - std::unique_ptr prefetch_; - }; - - // Per quota Referenced data. - struct PerQuotaReferenced { - // Pending CacheElem for all cache miss requests. - // This item will be added to the cache after response. - std::unique_ptr pending_item; - - // Referenced map keyed with their hashes - std::unordered_map referenced_map; - }; - - // Set a quota response. - void SetResponse( - const ::istio::mixer::v1::Attributes& attributes, - const std::string& quota_name, - const ::istio::mixer::v1::CheckResponse::QuotaResult* result); - - // A map from quota name to PerQuotaReferenced. - std::unordered_map quota_referenced_map_; - - // Key is the signature of the Attributes. Value is the CacheElem. - // It is a LRU cache with MaxIdelTime as response_expiration_time. - using QuotaLRUCache = utils::SimpleLRUCache; - - // The quota options. - QuotaOptions options_; - - // Mutex guarding the access of cache_ and quota_referenced_map_ - std::mutex cache_mutex_; - - // The cache that maps from key to prefetch object - std::unique_ptr cache_; - - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(QuotaCache); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_QUOTA_CACHE_H diff --git a/src/istio/mixerclient/quota_cache_test.cc b/src/istio/mixerclient/quota_cache_test.cc deleted file mode 100644 index 9d613db8874..00000000000 --- a/src/istio/mixerclient/quota_cache_test.cc +++ /dev/null @@ -1,313 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/quota_cache.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" -#include "src/istio/mixerclient/status_test_util.h" - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::CheckRequest; -using ::istio::mixer::v1::CheckResponse; -using ::istio::mixer::v1::ReferencedAttributes; -using ::istio::quota_config::Requirement; -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace mixerclient { -namespace { - -const std::string kQuotaName = "RequestCount"; - -class QuotaCacheTest : public ::testing::Test { - public: - void SetUp() { - QuotaOptions options; - cache_ = std::unique_ptr(new QuotaCache(options)); - ASSERT_TRUE((bool)(cache_)); - - quotas_.push_back({kQuotaName, 1}); - } - - void TestRequest(const Attributes& request, bool pass, - const CheckResponse& response) { - QuotaCache::CheckResult result; - cache_->Check(request, quotas_, true, &result); - - CheckRequest request_pb; - result.BuildRequest(&request_pb); - - EXPECT_TRUE(result.IsCacheHit()); - if (pass) { - EXPECT_TRUE(result.status().ok()); - } else { - EXPECT_FALSE(result.status().ok()); - } - - result.SetResponse(Status::OK, request, response); - } - - Attributes request_; - std::vector quotas_; - std::unique_ptr cache_; -}; - -TEST_F(QuotaCacheTest, TestEmptyRequest) { - // Quota is not required. - std::vector empty_list; - QuotaCache::CheckResult result; - cache_->Check(request_, empty_list, true, &result); - - CheckRequest request; - EXPECT_FALSE(result.BuildRequest(&request)); - EXPECT_TRUE(result.IsCacheHit()); - EXPECT_OK(result.status()); - EXPECT_EQ(request.quotas().size(), 0); -} - -TEST_F(QuotaCacheTest, TestDisabledCache) { - // A disabled cache - QuotaOptions options(0, 1000); - cache_ = std::unique_ptr(new QuotaCache(options)); - ASSERT_TRUE((bool)(cache_)); - - QuotaCache::CheckResult result; - cache_->Check(request_, quotas_, true, &result); - - CheckRequest request; - EXPECT_TRUE(result.BuildRequest(&request)); - EXPECT_FALSE(result.IsCacheHit()); - - EXPECT_EQ(request.quotas().size(), 1); - EXPECT_EQ(request.quotas().begin()->first, kQuotaName); - EXPECT_EQ(request.quotas().begin()->second.amount(), 1); - EXPECT_EQ(request.quotas().begin()->second.best_effort(), false); - - CheckResponse response; - CheckResponse::QuotaResult quota_result; - quota_result.set_granted_amount(1); - (*response.mutable_quotas())[kQuotaName] = quota_result; - result.SetResponse(Status::OK, request_, response); - EXPECT_OK(result.status()); -} - -TEST_F(QuotaCacheTest, TestNotUseCache) { - QuotaCache::CheckResult result; - cache_->Check(request_, quotas_, false, &result); - - CheckRequest request; - EXPECT_TRUE(result.BuildRequest(&request)); - EXPECT_FALSE(result.IsCacheHit()); - - EXPECT_EQ(request.quotas().size(), 1); - EXPECT_EQ(request.quotas().begin()->first, kQuotaName); - EXPECT_EQ(request.quotas().begin()->second.amount(), 1); - EXPECT_EQ(request.quotas().begin()->second.best_effort(), false); - - CheckResponse response; - CheckResponse::QuotaResult quota_result; - // granted_amount = 0 - quota_result.set_granted_amount(0); - (*response.mutable_quotas())[kQuotaName] = quota_result; - result.SetResponse(Status::OK, request_, response); - EXPECT_ERROR_CODE(Code::RESOURCE_EXHAUSTED, result.status()); -} - -TEST_F(QuotaCacheTest, TestUseCache) { - QuotaCache::CheckResult result; - cache_->Check(request_, quotas_, true, &result); - - CheckRequest request; - EXPECT_TRUE(result.BuildRequest(&request)); - - // Prefetch always allow the first call. - EXPECT_TRUE(result.IsCacheHit()); - EXPECT_OK(result.status()); - - // Then try to prefetch some. - EXPECT_EQ(request.quotas().size(), 1); - EXPECT_EQ(request.quotas().begin()->first, kQuotaName); - // Prefetch amount should be > 1 - EXPECT_GT(request.quotas().begin()->second.amount(), 1); - EXPECT_EQ(request.quotas().begin()->second.best_effort(), true); - - CheckResponse response; - result.SetResponse(Status::OK, request_, response); - EXPECT_OK(result.status()); -} - -TEST_F(QuotaCacheTest, TestUseCacheRejected) { - CheckResponse response; - CheckResponse::QuotaResult quota_result; - // Not more quota. - quota_result.set_granted_amount(0); - (*response.mutable_quotas())[kQuotaName] = quota_result; - - int rejected = 0; - for (int i = 0; i < 10; i++) { - QuotaCache::CheckResult result; - cache_->Check(request_, quotas_, true, &result); - - CheckRequest request; - result.BuildRequest(&request); - - // Prefetch always allow the first call. - EXPECT_TRUE(result.IsCacheHit()); - if (!result.status().ok()) { - ++rejected; - } - - result.SetResponse(Status::OK, request_, response); - } - // Only the first one allowed, the rest should be rejected. - EXPECT_EQ(rejected, 9); -} - -TEST_F(QuotaCacheTest, TestInvalidQuotaReferenced) { - // If quota result Referenced is invalid (wrong word index), - // its cache item stays in pending. - // Other cache miss requests with same quota name will use pending - // item. - CheckResponse response; - CheckResponse::QuotaResult quota_result; - // Not more quota. - quota_result.set_granted_amount(0); - auto match = - quota_result.mutable_referenced_attributes()->add_attribute_matches(); - match->set_condition(ReferencedAttributes::ABSENCE); - match->set_name(10000); // global index is too big - (*response.mutable_quotas())[kQuotaName] = quota_result; - - Attributes attr(request_); - utils::AttributesBuilder builder(&attr); - builder.AddString("source.name", "user1"); - // response has invalid referenced, cache item still in pending. - TestRequest(attr, true, response); - - builder.AddString("source.name", "user2"); - // it is a cache miss, use pending request. - // Previous request has used up token, this request will be rejected. - TestRequest(attr, false, response); -} - -TEST_F(QuotaCacheTest, TestMismatchedReferenced) { - // If quota result Referenced is mismatched with request data. - // its cache item stays in pending. - // Other cache miss requests with same quota name will use pending - // item. - CheckResponse response; - CheckResponse::QuotaResult quota_result; - // Not more quota. - quota_result.set_granted_amount(0); - auto match = - quota_result.mutable_referenced_attributes()->add_attribute_matches(); - match->set_condition(ReferencedAttributes::ABSENCE); - match->set_name(2); // "source.name" should be absence (mismatch) - (*response.mutable_quotas())[kQuotaName] = quota_result; - - Attributes attr(request_); - utils::AttributesBuilder builder(&attr); - builder.AddString("source.name", "user1"); - // Since respones has mismatched Referenced, cache item still in pending. - // Prefetch always allow the first call. - TestRequest(attr, true, response); - - // second request with different users still use the pending request. - builder.AddString("source.name", "user2"); - // it is a cache miss, use pending request. - // Previous request has used up token, this request will be rejected. - TestRequest(attr, false, response); -} - -TEST_F(QuotaCacheTest, TestOneReferencedWithTwoKeys) { - // Quota needs to use source.name as cache key. - // First source.name is exhaused, and second one is with quota. - CheckResponse response; - CheckResponse::QuotaResult quota_result; - // Not more quota. - quota_result.set_granted_amount(0); - auto match = - quota_result.mutable_referenced_attributes()->add_attribute_matches(); - match->set_condition(ReferencedAttributes::EXACT); - match->set_name(2); // "source.name" should be used - (*response.mutable_quotas())[kQuotaName] = quota_result; - - Attributes attr1(request_); - utils::AttributesBuilder(&attr1).AddString("source.name", "user1"); - Attributes attr2(request_); - utils::AttributesBuilder(&attr2).AddString("source.name", "user2"); - - // cache item is updated with 0 token in the pool. - // it will be saved into cache key with user1. - TestRequest(attr1, true, response); - - // user2 still have quota. - quota_result.set_granted_amount(10); - (*response.mutable_quotas())[kQuotaName] = quota_result; - TestRequest(attr2, true, response); - - // user1 will not have quota - TestRequest(attr1, false, response); - - // user2 will have quota - TestRequest(attr2, true, response); -} - -TEST_F(QuotaCacheTest, TestTwoReferencedWith) { - CheckResponse::QuotaResult quota_result1; - // Not more quota. - quota_result1.set_granted_amount(0); - auto match = - quota_result1.mutable_referenced_attributes()->add_attribute_matches(); - match->set_condition(ReferencedAttributes::EXACT); - match->set_name(2); // "source.name" should be used - CheckResponse response1; - (*response1.mutable_quotas())[kQuotaName] = quota_result1; - - CheckResponse::QuotaResult quota_result2; - quota_result2.set_granted_amount(10); - match = - quota_result2.mutable_referenced_attributes()->add_attribute_matches(); - match->set_condition(ReferencedAttributes::EXACT); - match->set_name(3); // "source.uid" should be used - CheckResponse response2; - (*response2.mutable_quotas())[kQuotaName] = quota_result2; - - Attributes attr1(request_); - utils::AttributesBuilder(&attr1).AddString("source.name", "name"); - Attributes attr2(request_); - utils::AttributesBuilder(&attr2).AddString("source.uid", "uid"); - - // name request with 0 granted response - TestRequest(attr1, true, response1); - - // uid request with 10 granted response - TestRequest(attr2, true, response2); - - // user1 will not have quota - TestRequest(attr1, false, response1); - - // user2 will have quota - TestRequest(attr2, true, response2); -} - -} // namespace -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/referenced.cc b/src/istio/mixerclient/referenced.cc deleted file mode 100644 index afc51f72527..00000000000 --- a/src/istio/mixerclient/referenced.cc +++ /dev/null @@ -1,313 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/referenced.h" - -#include "global_dictionary.h" - -#include -#include -#include -#include - -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_AttributeValue; -using ::istio::mixer::v1::ReferencedAttributes; - -namespace istio { -namespace mixerclient { -namespace { -const char kDelimiter[] = "\0"; -const int kDelimiterLength = 1; -const std::string kWordDelimiter = ":"; - -// Decode dereferences index into str using global and local word lists. -// Decode returns false if it is unable to Decode. -bool Decode(int idx, const std::vector &global_words, - const ReferencedAttributes &reference, std::string *str) { - if (idx >= 0) { - if ((unsigned int)idx >= global_words.size()) { - GOOGLE_LOG(ERROR) << "Global word index is too big: " << idx - << " >= " << global_words.size(); - return false; - } - *str = global_words[idx]; - } else { - // per-message index is negative, its format is: - // per_message_idx = -(array_idx + 1) - idx = -idx - 1; - if (idx >= reference.words_size()) { - GOOGLE_LOG(ERROR) << "Per message word index is too big: " << idx - << " >= " << reference.words_size(); - return false; - } - *str = reference.words(idx); - } - - return true; -} - -} // namespace - -// Updates hasher with keys -void Referenced::UpdateHash(const std::vector &keys, - utils::MD5 *hasher) { - // keys are already sorted during Fill - for (const AttributeRef &key : keys) { - hasher->Update(key.name); - hasher->Update(kDelimiter, kDelimiterLength); - if (!key.map_key.empty()) { - hasher->Update(key.map_key); - hasher->Update(kDelimiter, kDelimiterLength); - } - } -} - -bool Referenced::Fill(const Attributes &attributes, - const ReferencedAttributes &reference) { - const std::vector &global_words = GetGlobalWords(); - const auto &attributes_map = attributes.attributes(); - - for (const auto &match : reference.attribute_matches()) { - AttributeRef ar; - if (!Decode(match.name(), global_words, reference, &ar.name)) { - return false; - } - - const auto it = attributes_map.find(ar.name); - if (it != attributes_map.end()) { - const Attributes_AttributeValue &value = it->second; - if (value.value_case() == Attributes_AttributeValue::kStringMapValue) { - if (!Decode(match.map_key(), global_words, reference, &ar.map_key)) { - return false; - } - } - } - - if (match.condition() == ReferencedAttributes::ABSENCE) { - absence_keys_.push_back(ar); - } else if (match.condition() == ReferencedAttributes::EXACT) { - exact_keys_.push_back(ar); - } else if (match.condition() == ReferencedAttributes::REGEX) { - // Don't support REGEX yet, return false to no caching the response. - GOOGLE_LOG(ERROR) << "Received REGEX in ReferencedAttributes for " - << ar.name; - return false; - } - } - - std::sort(absence_keys_.begin(), absence_keys_.end()); - std::sort(exact_keys_.begin(), exact_keys_.end()); - - return true; -} - -bool Referenced::Signature(const Attributes &attributes, - const std::string &extra_key, - std::string *signature) const { - if (!CheckAbsentKeys(attributes) || !CheckExactKeys(attributes)) { - return false; - } - - CalculateSignature(attributes, extra_key, signature); - return true; -} - -bool Referenced::CheckAbsentKeys(const Attributes &attributes) const { - const auto &attributes_map = attributes.attributes(); - for (std::size_t i = 0; i < absence_keys_.size(); ++i) { - const auto &key = absence_keys_[i]; - const auto it = attributes_map.find(key.name); - if (it == attributes_map.end()) { - continue; - } - - const Attributes_AttributeValue &value = it->second; - // If an "absence" key exists for a non StringMap attribute, return false - // for mis-match. - if (value.value_case() != Attributes_AttributeValue::kStringMapValue) { - return false; - } - - std::string map_key = key.map_key; - const auto &smap = value.string_map_value().entries(); - // Since absence_keys_ are sorted by key.name, - // continue processing stringMaps until a new name is found. - do { - // if subkey is found, it is a violation of "absence" constrain. - if (smap.find(map_key) != smap.end()) { - return false; - } - // Break loop if at the end or at different key - if (i + 1 == absence_keys_.size() || - absence_keys_[i + 1].name != key.name) { - break; - } - - map_key = absence_keys_[++i].map_key; - } while (true); - } - return true; -} - -bool Referenced::CheckExactKeys(const Attributes &attributes) const { - const auto &attributes_map = attributes.attributes(); - for (std::size_t i = 0; i < exact_keys_.size(); ++i) { - const auto &key = exact_keys_[i]; - const auto it = attributes_map.find(key.name); - // If an "exact" attribute not present, return false for mismatch. - if (it == attributes_map.end()) { - return false; - } - - const Attributes_AttributeValue &value = it->second; - if (value.value_case() == Attributes_AttributeValue::kStringMapValue) { - std::string map_key = key.map_key; - const auto &smap = value.string_map_value().entries(); - // Since exact_keys_ are sorted by key.name, - // continue processing stringMaps until a new name is found. - do { - const auto sub_it = smap.find(map_key); - // exact match of map_key is missing - if (sub_it == smap.end()) { - return false; - } - - // break loop if at the end or keyname changes. - if (i + 1 == exact_keys_.size() || - exact_keys_[i + 1].name != key.name) { - break; - } - - map_key = exact_keys_[++i].map_key; - } while (true); - } - } - return true; -} - -void Referenced::CalculateSignature(const Attributes &attributes, - const std::string &extra_key, - std::string *signature) const { - const auto &attributes_map = attributes.attributes(); - - utils::MD5 hasher; - for (std::size_t i = 0; i < exact_keys_.size(); ++i) { - const auto &key = exact_keys_[i]; - const auto it = attributes_map.find(key.name); - - hasher.Update(it->first); - hasher.Update(kDelimiter, kDelimiterLength); - - const Attributes_AttributeValue &value = it->second; - switch (value.value_case()) { - case Attributes_AttributeValue::kStringValue: - hasher.Update(value.string_value()); - break; - case Attributes_AttributeValue::kBytesValue: - hasher.Update(value.bytes_value()); - break; - case Attributes_AttributeValue::kInt64Value: { - auto data = value.int64_value(); - hasher.Update(&data, sizeof(data)); - } break; - case Attributes_AttributeValue::kDoubleValue: { - auto data = value.double_value(); - hasher.Update(&data, sizeof(data)); - } break; - case Attributes_AttributeValue::kBoolValue: { - auto data = value.bool_value(); - hasher.Update(&data, sizeof(data)); - } break; - case Attributes_AttributeValue::kTimestampValue: { - auto seconds = value.timestamp_value().seconds(); - auto nanos = value.timestamp_value().nanos(); - hasher.Update(&seconds, sizeof(seconds)); - hasher.Update(kDelimiter, kDelimiterLength); - hasher.Update(&nanos, sizeof(nanos)); - } break; - case Attributes_AttributeValue::kDurationValue: { - auto seconds = value.duration_value().seconds(); - auto nanos = value.duration_value().nanos(); - hasher.Update(&seconds, sizeof(seconds)); - hasher.Update(kDelimiter, kDelimiterLength); - hasher.Update(&nanos, sizeof(nanos)); - } break; - case Attributes_AttributeValue::kStringMapValue: { - std::string map_key = key.map_key; - const auto &smap = value.string_map_value().entries(); - // Since exact_keys_ are sorted by key.name, - // continue processing stringMaps until a new name is found. - do { - const auto sub_it = smap.find(map_key); - - hasher.Update(sub_it->first); - hasher.Update(kDelimiter, kDelimiterLength); - hasher.Update(sub_it->second); - hasher.Update(kDelimiter, kDelimiterLength); - - // break loop if at the end or keyname changes. - if (i + 1 == exact_keys_.size() || - exact_keys_[i + 1].name != key.name) { - break; - } - - map_key = exact_keys_[++i].map_key; - } while (true); - } break; - case Attributes_AttributeValue::VALUE_NOT_SET: - break; - } - hasher.Update(kDelimiter, kDelimiterLength); - } - hasher.Update(extra_key); - - *signature = hasher.Digest(); -} - -std::string Referenced::Hash() const { - utils::MD5 hasher; - - // keys are sorted during Fill - UpdateHash(absence_keys_, &hasher); - hasher.Update(kWordDelimiter); - UpdateHash(exact_keys_, &hasher); - - return hasher.Digest(); -} - -std::string Referenced::DebugString() const { - std::stringstream ss; - ss << "Absence-keys: "; - for (const auto &key : absence_keys_) { - ss << key.name; - if (!key.map_key.empty()) { - ss << "[" + key.map_key + "]"; - } - ss << ", "; - } - ss << "Exact-keys: "; - for (const auto &key : exact_keys_) { - ss << key.name; - if (!key.map_key.empty()) { - ss << "[" + key.map_key + "]"; - } - ss << ", "; - } - return ss.str(); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/referenced.h b/src/istio/mixerclient/referenced.h deleted file mode 100644 index ec95680dd7b..00000000000 --- a/src/istio/mixerclient/referenced.h +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_REFERENCED_H_ -#define ISTIO_MIXERCLIENT_REFERENCED_H_ - -#include - -#include "include/istio/utils/md5.h" -#include "mixer/v1/check.pb.h" - -namespace istio { -namespace mixerclient { - -// The object to store referenced attributes used by Mixer server. -// Mixer client cache should only use referenced attributes -// in its cache (for both Check cache and quota cache). -class Referenced { - public: - // Fill the object from the protobuf from Check response. - // Return false if any attribute names could not be decoded from client - // global dictionary. - bool Fill(const ::istio::mixer::v1::Attributes &attributes, - const ::istio::mixer::v1::ReferencedAttributes &reference); - - // Calculate a cache signature for the attributes. - // Return false if attributes are mismatched, such as "absence" attributes - // present - // or "exact" match attributes don't present. - bool Signature(const ::istio::mixer::v1::Attributes &attributes, - const std::string &extra_key, std::string *signature) const; - - // A hash value to identify an instance. - std::string Hash() const; - - // For debug logging only. - std::string DebugString() const; - - private: - // Return true if all absent keys are not in the attributes. - bool CheckAbsentKeys(const ::istio::mixer::v1::Attributes &attributes) const; - - // Return true if all exact keys are in the attributes. - bool CheckExactKeys(const ::istio::mixer::v1::Attributes &attributes) const; - - // Do the actual signature calculation. - void CalculateSignature(const ::istio::mixer::v1::Attributes &attributes, - const std::string &extra_key, - std::string *signature) const; - - // Holds reference to an attribute and potentially a map key - struct AttributeRef { - // name of the attribute - std::string name; - // only used if attribute is a stringMap - std::string map_key; - - // make vector sortable - bool operator<(const AttributeRef &b) const { - int cmp = name.compare(b.name); - if (cmp == 0) { - return map_key.compare(b.map_key) < 0; - } - - return cmp < 0; - }; - }; - - // The keys should be absence. - std::vector absence_keys_; - - // The keys should match exactly. - std::vector exact_keys_; - - // Updates hasher with keys - static void UpdateHash(const std::vector &keys, - utils::MD5 *hasher); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_REFERENCED_H_ diff --git a/src/istio/mixerclient/referenced_test.cc b/src/istio/mixerclient/referenced_test.cc deleted file mode 100644 index 3eb934468a5..00000000000 --- a/src/istio/mixerclient/referenced_test.cc +++ /dev/null @@ -1,306 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/referenced.h" - -#include "include/istio/utils/attributes_builder.h" -#include "include/istio/utils/md5.h" - -#include "google/protobuf/text_format.h" -#include "gtest/gtest.h" - -using ::google::protobuf::TextFormat; -using ::istio::mixer::v1::Attributes; - -namespace istio { -namespace mixerclient { -namespace { - -const char kReferencedText[] = R"( -words: "bool-key" -words: "bytes-key" -words: "string-key" -words: "double-key" -words: "int-key" -words: "time-key" -words: "duration-key" -words: "string-map-key" -words: "User-Agent" -words: "If-Match" -attribute_matches { - name: 9, - condition: ABSENCE, -} -attribute_matches { - name: 10, - condition: ABSENCE, -} -attribute_matches { - name: -1, - condition: EXACT, -} -attribute_matches { - name: -2, - condition: EXACT, -} -attribute_matches { - name: -3, - condition: EXACT, -} -attribute_matches { - name: -4, - condition: EXACT, -} -attribute_matches { - name: -5, - condition: EXACT, -} -attribute_matches { - name: -6, - condition: EXACT, -} -attribute_matches { - name: -7, - condition: EXACT, -} -attribute_matches { - name: -8, - map_key: -10, - condition: EXACT, -} -attribute_matches { - name: -8, - map_key: -9, - condition: ABSENCE, -} -)"; - -const char kAttributesText[] = R"( -attributes { - key: "string-map-key" - value { - string_map_value { - entries { - key: "User-Agent" - value: "chrome60" - } - entries { - key: "path" - value: "/books" - } - } - } -} -)"; - -// Global index (positive) is too big -const char kReferencedFailText1[] = R"( -attribute_matches { - name: 10000, - condition: EXACT, -} -)"; - -// Per message index (negative) is too big -const char kReferencedFailText2[] = R"( -words: "bool-key" -words: "bytes-key" -attribute_matches { - name: -10, - condition: ABSENCE, -} -)"; - -const char kStringMapReferencedText[] = R"( -words: "map-key1" -words: "map-key2" -words: "map-key3" -words: "exact-subkey4" -words: "exact-subkey5" -words: "absence-subkey6" -words: "absence-subkey7" -attribute_matches { - name: -1, - condition: EXACT, -} -attribute_matches { - name: -2, - map_key: -4, - condition: EXACT, -} -attribute_matches { - name: -2, - map_key: -5, - condition: EXACT, -} -attribute_matches { - name: -2, - map_key: -6, - condition: ABSENCE, -} -attribute_matches { - name: -2, - map_key: -7, - condition: ABSENCE, -} -attribute_matches { - name: -3, - condition: ABSENCE, -} -)"; - -TEST(ReferencedTest, FillSuccessTest) { - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kReferencedText, &pb)); - - ::istio::mixer::v1::Attributes attrs; - ASSERT_TRUE(TextFormat::ParseFromString(kAttributesText, &attrs)); - - Referenced referenced; - EXPECT_TRUE(referenced.Fill(attrs, pb)); - - EXPECT_EQ(referenced.DebugString(), - "Absence-keys: string-map-key[User-Agent], target.name, " - "target.service, Exact-keys: bool-key, bytes-key, double-key, " - "duration-key, int-key, string-key, string-map-key[If-Match], " - "time-key, "); - - EXPECT_EQ(utils::MD5::DebugString(referenced.Hash()), - "602d5bbd45b623c3560d2bdb6104f3ab"); -} - -TEST(ReferencedTest, FillFail1Test) { - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kReferencedFailText1, &pb)); - - ::istio::mixer::v1::Attributes attrs; - Referenced referenced; - EXPECT_FALSE(referenced.Fill(attrs, pb)); -} - -TEST(ReferencedTest, FillFail2Test) { - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kReferencedFailText2, &pb)); - ::istio::mixer::v1::Attributes attrs; - - Referenced referenced; - EXPECT_FALSE(referenced.Fill(attrs, pb)); -} - -TEST(ReferencedTest, NegativeSignature1Test) { - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kReferencedText, &pb)); - ::istio::mixer::v1::Attributes attrs; - ASSERT_TRUE(TextFormat::ParseFromString(kAttributesText, &attrs)); - Referenced referenced; - EXPECT_TRUE(referenced.Fill(attrs, pb)); - - std::string signature; - - Attributes attributes1; - // "target.service" should be absence. - utils::AttributesBuilder(&attributes1).AddString("target.service", "foo"); - EXPECT_FALSE(referenced.Signature(attributes1, "", &signature)); - - Attributes attributes2; - // many keys should exist. - utils::AttributesBuilder(&attributes2).AddString("bytes-key", "foo"); - EXPECT_FALSE(referenced.Signature(attributes2, "", &signature)); -} - -TEST(ReferencedTest, OKSignature1Test) { - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kReferencedText, &pb)); - - Attributes attributes; - utils::AttributesBuilder builder(&attributes); - builder.AddString("string-key", "this is a string value"); - builder.AddBytes("bytes-key", "this is a bytes value"); - builder.AddDouble("double-key", 99.9); - builder.AddInt64("int-key", 35); - builder.AddBool("bool-key", true); - - std::chrono::time_point time0; - std::chrono::seconds secs(5); - builder.AddTimestamp("time-key", time0); - builder.AddDuration( - "duration-key", - std::chrono::duration_cast(secs)); - - std::map string_map = {{"If-Match", "value1"}, - {"key2", "value2"}}; - builder.AddStringMap("string-map-key", std::move(string_map)); - - Referenced referenced; - EXPECT_TRUE(referenced.Fill(attributes, pb)); - - std::string signature; - EXPECT_TRUE(referenced.Signature(attributes, "extra", &signature)); - - EXPECT_EQ(utils::MD5::DebugString(signature), - "751b028b2e2c230ef9c4e59ac556ca04"); -} - -TEST(ReferencedTest, StringMapReferencedTest) { - std::map string_map_base = { - {"subkey3", "subvalue3"}, - {"exact-subkey4", "subvalue4"}, - {"exact-subkey5", "subvalue5"}, - }; - ::istio::mixer::v1::Attributes attrs; - utils::AttributesBuilder(&attrs).AddString("map-key1", "value1"); - utils::AttributesBuilder(&attrs).AddStringMap("map-key2", - std::move(string_map_base)); - - ::istio::mixer::v1::ReferencedAttributes pb; - ASSERT_TRUE(TextFormat::ParseFromString(kStringMapReferencedText, &pb)); - Referenced referenced; - EXPECT_TRUE(referenced.Fill(attrs, pb)); - - std::string signature; - EXPECT_TRUE(referenced.Signature(attrs, "extra", &signature)); - EXPECT_EQ(utils::MD5::DebugString(signature), - "bc055468af1a0d4d03ec7f6fa2265b9b"); - - // negative test: map-key3 must absence - ::istio::mixer::v1::Attributes attr1(attrs); - utils::AttributesBuilder(&attr1).AddString("map-key3", "this"); - EXPECT_FALSE(referenced.Signature(attr1, "extra", &signature)); - - // negative test: map-key1 must exist - ::istio::mixer::v1::Attributes attr2(attrs); - attr2.mutable_attributes()->erase("map-key1"); - EXPECT_FALSE(referenced.Signature(attr2, "extra", &signature)); - - // Negative tests: have a absent sub-key - std::map string_map3(string_map_base); - string_map3["absence-subkey6"] = "subvalue6"; - ::istio::mixer::v1::Attributes attr3(attrs); - utils::AttributesBuilder(&attr3).AddStringMap("map-key2", - std::move(string_map3)); - EXPECT_FALSE(referenced.Signature(attr3, "extra", &signature)); - - // Negative tests: miss exact sub-key - std::map string_map4(string_map_base); - string_map4.erase("exact-subkey4"); - ::istio::mixer::v1::Attributes attr4(attrs); - utils::AttributesBuilder(&attr4).AddStringMap("map-key2", - std::move(string_map4)); - EXPECT_FALSE(referenced.Signature(attr4, "extra", &signature)); -} - -} // namespace -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/report_batch.cc b/src/istio/mixerclient/report_batch.cc deleted file mode 100644 index a5a5a6c821b..00000000000 --- a/src/istio/mixerclient/report_batch.cc +++ /dev/null @@ -1,97 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/report_batch.h" -#include "include/istio/utils/protobuf.h" - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::ReportRequest; -using ::istio::mixer::v1::ReportResponse; - -namespace istio { -namespace mixerclient { - -ReportBatch::ReportBatch(const ReportOptions& options, - TransportReportFunc transport, - TimerCreateFunc timer_create, - AttributeCompressor& compressor) - : options_(options), - transport_(transport), - timer_create_(timer_create), - compressor_(compressor), - total_report_calls_(0), - total_remote_report_calls_(0) {} - -ReportBatch::~ReportBatch() { Flush(); } - -void ReportBatch::Report(const Attributes& request) { - std::lock_guard lock(mutex_); - ++total_report_calls_; - if (!batch_compressor_) { - batch_compressor_ = compressor_.CreateBatchCompressor(); - } - - if (!batch_compressor_->Add(request)) { - FlushWithLock(); - - batch_compressor_ = compressor_.CreateBatchCompressor(); - batch_compressor_->Add(request); - } - - if (batch_compressor_->size() >= options_.max_batch_entries) { - FlushWithLock(); - } else { - if (batch_compressor_->size() == 1 && timer_create_) { - if (!timer_) { - timer_ = timer_create_([this]() { Flush(); }); - } - timer_->Start(options_.max_batch_time_ms); - } - } -} - -void ReportBatch::FlushWithLock() { - if (!batch_compressor_) { - return; - } - - ++total_remote_report_calls_; - std::unique_ptr request = batch_compressor_->Finish(); - batch_compressor_.reset(); - if (timer_) { - timer_->Stop(); - } - - ReportResponse* response = new ReportResponse; - transport_(*request, response, [this, response](const Status& status) { - delete response; - if (!status.ok()) { - GOOGLE_LOG(ERROR) << "Mixer Report failed with: " << status.ToString(); - if (utils::InvalidDictionaryStatus(status)) { - compressor_.ShrinkGlobalDictionary(); - } - } - }); -} - -void ReportBatch::Flush() { - std::lock_guard lock(mutex_); - FlushWithLock(); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/report_batch.h b/src/istio/mixerclient/report_batch.h deleted file mode 100644 index 156129ee00a..00000000000 --- a/src/istio/mixerclient/report_batch.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_MIXERCLIENT_REPORT_BATCH_H -#define ISTIO_MIXERCLIENT_REPORT_BATCH_H - -#include "include/istio/mixerclient/client.h" -#include "src/istio/mixerclient/attribute_compressor.h" - -#include -#include - -namespace istio { -namespace mixerclient { - -// Report batch, this interface is thread safe. -class ReportBatch { - public: - ReportBatch(const ReportOptions& options, TransportReportFunc transport, - TimerCreateFunc timer_create, AttributeCompressor& compressor); - - virtual ~ReportBatch(); - - // Make batched report call. - void Report(const ::istio::mixer::v1::Attributes& request); - - // Flush out batched reports. - void Flush(); - - uint64_t total_report_calls() const { return total_report_calls_; } - uint64_t total_remote_report_calls() const { - return total_remote_report_calls_; - } - - private: - void FlushWithLock(); - - // The quota options. - ReportOptions options_; - - // The quota transport - TransportReportFunc transport_; - - // timer create func - TimerCreateFunc timer_create_; - - // Attribute compressor. - AttributeCompressor& compressor_; - - // Mutex guarding the access of batch data; - std::mutex mutex_; - - // timer to flush out batched data. - std::unique_ptr timer_; - - // batched report compressor - std::unique_ptr batch_compressor_; - - std::atomic_int_fast64_t total_report_calls_; - std::atomic_int_fast64_t total_remote_report_calls_; - - GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ReportBatch); -}; - -} // namespace mixerclient -} // namespace istio - -#endif // ISTIO_MIXERCLIENT_REPORT_BATCH_H diff --git a/src/istio/mixerclient/report_batch_test.cc b/src/istio/mixerclient/report_batch_test.cc deleted file mode 100644 index aec33ab251b..00000000000 --- a/src/istio/mixerclient/report_batch_test.cc +++ /dev/null @@ -1,155 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/mixerclient/report_batch.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "include/istio/utils/attributes_builder.h" - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::ReportRequest; -using ::istio::mixer::v1::ReportResponse; -using ::testing::Invoke; -using ::testing::_; - -namespace istio { -namespace mixerclient { - -// A mocking class to mock ReportTransport interface. -class MockReportTransport { - public: - MOCK_METHOD3(Report, void(const ReportRequest&, ReportResponse*, DoneFunc)); - TransportReportFunc GetFunc() { - return [this](const ReportRequest& request, ReportResponse* response, - DoneFunc on_done) -> CancelFunc { - Report(request, response, on_done); - return nullptr; - }; - } -}; - -class MockTimer : public Timer { - public: - void Stop() override {} - void Start(int interval_ms) override {} - std::function cb_; -}; - -class ReportBatchTest : public ::testing::Test { - public: - ReportBatchTest() : mock_timer_(nullptr), compressor_({}) { - batch_.reset(new ReportBatch(ReportOptions(3, 1000), - mock_report_transport_.GetFunc(), - GetTimerFunc(), compressor_)); - } - - TimerCreateFunc GetTimerFunc() { - return [this](std::function cb) -> std::unique_ptr { - mock_timer_ = new MockTimer; - mock_timer_->cb_ = cb; - return std::unique_ptr(mock_timer_); - }; - } - - MockReportTransport mock_report_transport_; - MockTimer* mock_timer_; - AttributeCompressor compressor_; - std::unique_ptr batch_; -}; - -TEST_F(ReportBatchTest, TestBatchDisabled) { - // max_batch_entries = 0 or 1 to disable batch - batch_.reset(new ReportBatch(ReportOptions(1, 1000), - mock_report_transport_.GetFunc(), nullptr, - compressor_)); - - // Expect report transport to be called. - EXPECT_CALL(mock_report_transport_, Report(_, _, _)) - .WillOnce( - Invoke([](const ReportRequest& request, ReportResponse* response, - DoneFunc on_done) { on_done(Status::OK); })); - - Attributes report; - batch_->Report(report); -} - -TEST_F(ReportBatchTest, TestBatchReport) { - int report_call_count = 0; - EXPECT_CALL(mock_report_transport_, Report(_, _, _)) - .WillRepeatedly(Invoke([&](const ReportRequest& request, - ReportResponse* response, DoneFunc on_done) { - report_call_count++; - on_done(Status::OK); - })); - - Attributes report; - for (int i = 0; i < 10; ++i) { - batch_->Report(report); - } - EXPECT_EQ(report_call_count, 3); - - batch_->Flush(); - EXPECT_EQ(report_call_count, 4); -} - -TEST_F(ReportBatchTest, TestNoDeltaUpdate) { - int report_call_count = 0; - EXPECT_CALL(mock_report_transport_, Report(_, _, _)) - .WillRepeatedly(Invoke([&](const ReportRequest& request, - ReportResponse* response, DoneFunc on_done) { - report_call_count++; - on_done(Status::OK); - })); - - Attributes report; - utils::AttributesBuilder(&report).AddString("key", "value"); - batch_->Report(report); - EXPECT_EQ(report_call_count, 0); - - // Erase a key, so delta update fail to push the batched result. - report.mutable_attributes()->erase("key"); - batch_->Report(report); - EXPECT_EQ(report_call_count, 1); - - batch_->Flush(); - EXPECT_EQ(report_call_count, 2); -} - -TEST_F(ReportBatchTest, TestBatchReportWithTimeout) { - int report_call_count = 0; - EXPECT_CALL(mock_report_transport_, Report(_, _, _)) - .WillRepeatedly(Invoke([&](const ReportRequest& request, - ReportResponse* response, DoneFunc on_done) { - report_call_count++; - on_done(Status::OK); - })); - - Attributes report; - batch_->Report(report); - EXPECT_EQ(report_call_count, 0); - - EXPECT_TRUE(mock_timer_ != nullptr); - EXPECT_TRUE(mock_timer_->cb_); - mock_timer_->cb_(); - EXPECT_EQ(report_call_count, 1); - - batch_->Flush(); - EXPECT_EQ(report_call_count, 1); -} - -} // namespace mixerclient -} // namespace istio diff --git a/src/istio/mixerclient/status_test_util.h b/src/istio/mixerclient/status_test_util.h deleted file mode 100644 index 7828f9538b1..00000000000 --- a/src/istio/mixerclient/status_test_util.h +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_UTILS_STATUS_TEST_UTIL_H__ -#define ISTIO_UTILS_STATUS_TEST_UTIL_H__ - -#include "gmock/gmock.h" -#include "google/protobuf/stubs/status.h" -#include "gtest/gtest.h" - -// Macros for testing the results of functions that return -// ::google::protobuf::util::Status. - -// EXPECT_OK is defined in google/protobuf/stubs/status.h -//#define EXPECT_OK(statement) EXPECT_EQ(::google::protobuf::util::Status::OK, -//(statement)) -#define ASSERT_OK(statement) \ - ASSERT_EQ(::google::protobuf::util::Status::OK, (statement)) - -// Test that a util::Status is not OK. NOTE: It is preferable to use -// {ASSERT,EXPECT}_ERROR_SUBSTR() and check for a specific error string. -#define EXPECT_NOT_OK(cmd) \ - EXPECT_NE(::google::protobuf::util::Status::OK, (cmd)) -#define ASSERT_NOT_OK(cmd) \ - ASSERT_NE(::google::protobuf::util::Status::OK, (cmd)) - -namespace google { -namespace util { -namespace status_macros { - -// StatusErrorCodeMatcher is a gMock matcher that tests that a util::Status -// has a certain error code and ErrorSpace. -// -// Specifying the ErrorSpace is optional - it can be inferred from an -// enum error code type using ErrorCodeOptions from status_macros.h. -// -// Example usage: -// EXPECT_THAT(transaction->Commit(), -// HasErrorCode(NOT_FOUND)); -class StatusErrorCodeMatcher : public ::testing::MatcherInterface< - const ::google::protobuf::util::Status&> { - public: - StatusErrorCodeMatcher(int error_code) : error_code_(error_code) {} - - virtual bool MatchAndExplain(const ::google::protobuf::util::Status& status, - ::testing::MatchResultListener* listener) const { - if (status.ok()) { - // Code 0 always means OK, and the ErrorSpace is discarded. - return error_code_ == 0; - } else { - return status.error_code() == error_code_; - } - } - virtual void DescribeTo(::std::ostream* os) const { - *os << "util::Status has error code " << error_code_; - } - virtual void DescribeNegationTo(::std::ostream* os) const { - *os << "util::Status does not have error code " << error_code_; - } - - private: - const int error_code_; -}; - -inline ::testing::Matcher HasErrorCode( - int error_code) { - return ::testing::MakeMatcher(new StatusErrorCodeMatcher(error_code)); -} - -// Test that a util::Status has a specific error code, in the right ErrorSpace -// as defined by ErrorCodeOptions. -#define EXPECT_ERROR_CODE(code, cmd) \ - EXPECT_THAT((cmd), ::google::util::status_macros::HasErrorCode(code)) -#define ASSERT_ERROR_CODE(code, cmd) \ - ASSERT_THAT((cmd), ::google::util::status_macros::HasErrorCode(code)) - -} // namespace status_macros -} // namespace util -} // namespace google - -#endif // ISTIO_UTILS_STATUS_TEST_UTIL_H__ diff --git a/src/istio/prefetch/BUILD b/src/istio/prefetch/BUILD deleted file mode 100644 index ded66da7c2e..00000000000 --- a/src/istio/prefetch/BUILD +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "quota_prefetch_lib", - srcs = [ - "circular_queue.h", - "quota_prefetch.cc", - "time_based_counter.cc", - "time_based_counter.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//include/istio/prefetch:headers_lib", - ], -) - -cc_test( - name = "circular_queue_test", - size = "small", - srcs = ["circular_queue_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":quota_prefetch_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "time_based_counter_test", - size = "small", - srcs = ["time_based_counter_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":quota_prefetch_lib", - "//external:googletest_main", - ], -) - -cc_test( - name = "quota_prefetch_test", - size = "small", - srcs = ["quota_prefetch_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":quota_prefetch_lib", - "//external:googletest_main", - ], -) diff --git a/src/istio/prefetch/README.md b/src/istio/prefetch/README.md deleted file mode 100644 index 22705a6ae9c..00000000000 --- a/src/istio/prefetch/README.md +++ /dev/null @@ -1,31 +0,0 @@ - -## Introduction - -A c++ library for a rate limiting prefetch algorithm. - -The rate limiting feature is for a system wanted to limit request rate. For example, a proxy wants to limit request rate to protect the backend server. The exceeded requests will be rejected by the proxy and will not reach the backend server. - -If a system has multiple proxy instances, rate limiting could be local or global. If local, each running instance enforces its own limit. If global, all running instances are subjected to a global limit. Global rate limiting is more useful than local. For global rate limiting, usually there is a rate limiting server to enforce the global limits, each proxy instance needs to call the server to check the limits. - -If each proxy instance is calling the rate limiting server for each request it is processing, it will greatly increase the request latency by adding a remote call. It is a good idea for the proxy to prefetch some tokens so not every request needs to make a remote call to check rate limits. - -Here presents a prefetch algorithm for that purpose. - -This code presents a rate limiting prefetch algorithm. It can achieve: -* All rate limiting decisions are done at local, not need to wait for remote call. -* It works for both big rate limiting window, such as 1 minute, or small window, such as 1 second. - - -## Algorithm - -Basic idea is: -* Use a predict window to count number of requests, use that to determine prefetch amount. -* There is a pool to store prefetch tokens from the rate limiting server. -* When the available tokens in the pool is less than half of desired amount, trigger a new prefetch. -* If a prefetch is negative (requested amount is not fully granted), need to wait for a period time before next prefetch. - -There are three parameters in this algorithm: -* predictWindow: the time to count the requests, use that to determine prefetch amount -* minPrefetch: the minimum prefetch amount -* closeWaitWindow: the wait time for the next prefetch if last prefetch is negative. - diff --git a/src/istio/prefetch/circular_queue.h b/src/istio/prefetch/circular_queue.h deleted file mode 100644 index 4605a2b4efe..00000000000 --- a/src/istio/prefetch/circular_queue.h +++ /dev/null @@ -1,97 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_PREFETCH_CIRCULAR_QUEUE_H_ -#define ISTIO_PREFETCH_CIRCULAR_QUEUE_H_ - -#include -#include - -namespace istio { -namespace prefetch { - -// Define a circular FIFO queue -// Supported classes should support copy operator. -template -class CircularQueue { - public: - explicit CircularQueue(int size); - - // Push an item to the tail - void Push(const T& v); - - // Pop up an item from the head - void Pop(); - - // Allow modifying the head item. - T* Head(); - - // Calls the fn function for each element from head to tail. - void Iterate(std::function fn); - - private: - std::vector nodes_; - int head_; - int tail_; - int count_; -}; - -template -CircularQueue::CircularQueue(int size) - : nodes_(size), head_(0), tail_(0), count_(0) {} - -template -void CircularQueue::Push(const T& v) { - if (head_ == tail_ && count_ > 0) { - size_t size = nodes_.size(); - nodes_.resize(size * 2); - for (int i = 0; i <= head_; i++) { - // Use the copy operator of class T - nodes_[size + i] = nodes_[i]; - } - tail_ += size; - } - nodes_[tail_] = v; - tail_ = (tail_ + 1) % nodes_.size(); - count_++; -} - -template -void CircularQueue::Pop() { - if (count_ == 0) return; - head_ = (head_ + 1) % nodes_.size(); - count_--; -} - -template -T* CircularQueue::Head() { - if (count_ == 0) return nullptr; - return &nodes_[head_]; -} - -template -void CircularQueue::Iterate(std::function fn) { - if (count_ == 0) return; - int i = head_; - while (i != tail_) { - if (!fn(nodes_[i])) return; - i = (i + 1) % nodes_.size(); - } -} - -} // namespace prefetch -} // namespace istio - -#endif // ISTIO_PREFETCH_CIRCULAR_QUEUE_H_ diff --git a/src/istio/prefetch/circular_queue_test.cc b/src/istio/prefetch/circular_queue_test.cc deleted file mode 100644 index db05a93f8b9..00000000000 --- a/src/istio/prefetch/circular_queue_test.cc +++ /dev/null @@ -1,73 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/prefetch/circular_queue.h" -#include "gtest/gtest.h" - -namespace istio { -namespace prefetch { -namespace { - -void ASSERT_RESULT(CircularQueue& q, const std::vector& expected) { - std::vector v; - q.Iterate([&](int& i) -> bool { - v.push_back(i); - return true; - }); - ASSERT_EQ(v, expected); -} - -TEST(CircularQueueTest, TestNotResize) { - CircularQueue q(5); - q.Push(1); - q.Push(2); - q.Push(3); - ASSERT_RESULT(q, {1, 2, 3}); - - q.Pop(); - q.Pop(); - q.Push(4); - q.Push(5); - q.Push(6); - ASSERT_RESULT(q, {3, 4, 5, 6}); -} - -TEST(CircularQueueTest, TestResize1) { - CircularQueue q(3); - for (int i = 1; i < 6; i++) { - q.Push(i); - } - ASSERT_RESULT(q, {1, 2, 3, 4, 5}); -} - -TEST(CircularQueueTest, TestResize2) { - CircularQueue q(3); - - // move head and tail - q.Push(1); - q.Push(2); - q.Push(3); - q.Pop(); - q.Pop(); - - for (int i = 4; i < 10; i++) { - q.Push(i); - } - ASSERT_RESULT(q, {3, 4, 5, 6, 7, 8, 9}); -} - -} // namespace -} // namespace prefetch -} // namespace istio diff --git a/src/istio/prefetch/quota_prefetch.cc b/src/istio/prefetch/quota_prefetch.cc deleted file mode 100644 index ec244d2bc72..00000000000 --- a/src/istio/prefetch/quota_prefetch.cc +++ /dev/null @@ -1,325 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/prefetch/quota_prefetch.h" -#include "src/istio/prefetch/circular_queue.h" -#include "src/istio/prefetch/time_based_counter.h" - -#include - -using namespace std::chrono; - -// Turn this on to debug for quota_prefetch_test.cc -// Not for debugging in production. -#if 0 -#include -#define LOG(t) \ - std::cerr << "(" \ - << duration_cast(t.time_since_epoch()).count() \ - << "):" -#else -// Pipe to stringstream to disable logging. -#include -std::ostringstream os; -#define LOG(t) \ - os.clear(); \ - os -#endif - -namespace istio { -namespace prefetch { -namespace { - -// Default predict window size in milliseconds. -const int kPredictWindowInMs = 1000; - -// Default min prefetch amount. -const int kMinPrefetchAmount = 10; - -// Default close wait window in milliseconds. -const int kCloseWaitWindowInMs = 500; - -// Initiail Circular Queue size for prefetch pool. -const int kInitQueueSize = 10; - -// TimeBasedCounter window size -const int kTimeBasedWindowSize = 20; - -// Maximum expiration for prefetch amount. -// It is only used when a prefetch amount is added to the pool -// before it is granted. Usually is 1 minute. -const int kMaxExpirationInMs = 60000; - -// The implementation class to hide internal implementation detail. -class QuotaPrefetchImpl : public QuotaPrefetch { - public: - // The slot id type. - typedef uint64_t SlotId; - - // The struture to store granted amount. - struct Slot { - // available amount - int available; - // the time the amount will be expired. - Tick expire_time; - // the always increment ID to detect if a Slot has been re-cycled. - SlotId id; - }; - - // The mode. - enum Mode { - OPEN = 0, - CLOSE, - }; - - QuotaPrefetchImpl(TransportFunc transport, const Options& options, Tick t) - : queue_(kInitQueueSize), - counter_(kTimeBasedWindowSize, options.predict_window, t), - mode_(OPEN), - inflight_count_(0), - transport_(transport), - options_(options), - next_slot_id_(0) {} - - bool Check(int amount, Tick t) override; - - private: - // Count available token - int CountAvailable(Tick t); - // Check available count is bigger than minimum - int CheckMinAvailable(int min, Tick t); - // Check to see if need to do a prefetch. - void AttemptPrefetch(int amount, Tick t); - // Make a prefetch call. - void Prefetch(int req_amount, bool use_not_granted, Tick t); - // Add the amount to the queue, and return slot id. - SlotId Add(int amount, Tick expiration); - // Substract the amount from the queue. - // Return the amount that could not be substracted. - int Substract(int delta, Tick t); - // On quota allocation response. - void OnResponse(SlotId slot_id, int req_amount, int resp_amount, - milliseconds expiration, Tick t); - // Find the slot by id. - Slot* FindSlotById(SlotId id); - - // The mutex guarding all member variables. - std::mutex mutex_; - // The FIFO queue to store prefetched amount. - CircularQueue queue_; - // The counter to count number of requests in the pass window. - TimeBasedCounter counter_; - // The current mode. - Mode mode_; - // Last prefetch time. - Tick last_prefetch_time_; - // inflight request count; - int inflight_count_; - // The transport to allocate quota. - TransportFunc transport_; - // Save the options. - Options options_; - // next slot id - SlotId next_slot_id_; -}; - -int QuotaPrefetchImpl::CountAvailable(Tick t) { - int avail = 0; - queue_.Iterate([&](Slot& slot) -> bool { - if (t < slot.expire_time) { - avail += slot.available; - } - return true; - }); - return avail; -} - -int QuotaPrefetchImpl::CheckMinAvailable(int min, Tick t) { - int avail = 0; - queue_.Iterate([&](Slot& slot) -> bool { - if (t < slot.expire_time) { - avail += slot.available; - if (avail >= min) return false; - } - return true; - }); - return avail >= min; -} - -void QuotaPrefetchImpl::AttemptPrefetch(int amount, Tick t) { - if (mode_ == CLOSE && (inflight_count_ > 0 || - (duration_cast(t - last_prefetch_time_) < - options_.close_wait_window))) { - return; - } - - int avail = CountAvailable(t); - int pass_count = counter_.Count(t); - int desired = std::max(pass_count, options_.min_prefetch_amount); - if ((avail < desired / 2 && inflight_count_ == 0) || avail < amount) { - bool use_not_granted = (avail == 0 && mode_ == OPEN); - Prefetch(std::max(amount, desired), use_not_granted, t); - } -} - -void QuotaPrefetchImpl::Prefetch(int req_amount, bool use_not_granted, Tick t) { - SlotId slot_id = 0; - if (use_not_granted) { - // add the prefetch amount to available queue before it is granted. - slot_id = Add(req_amount, t + milliseconds(kMaxExpirationInMs)); - } - - LOG(t) << "Prefetch: " << req_amount << ", id: " << slot_id << std::endl; - - last_prefetch_time_ = t; - ++inflight_count_; - transport_(req_amount, - [this, slot_id, req_amount](int resp_amount, - milliseconds expiration, Tick t1) { - OnResponse(slot_id, req_amount, resp_amount, expiration, t1); - }, - t); -} - -QuotaPrefetchImpl::Slot* QuotaPrefetchImpl::FindSlotById(SlotId id) { - Slot* found = nullptr; - queue_.Iterate([&](Slot& slot) -> bool { - if (slot.id == id) { - found = &slot; - return false; - } - return true; - }); - return found; -} - -QuotaPrefetchImpl::SlotId QuotaPrefetchImpl::Add(int amount, Tick expire_time) { - SlotId id = ++next_slot_id_; - queue_.Push(Slot{amount, expire_time, id}); - return id; -} - -int QuotaPrefetchImpl::Substract(int delta, Tick t) { - Slot* n = queue_.Head(); - while (n != nullptr && delta > 0) { - if (t < n->expire_time) { - if (n->available > 0) { - int d = std::min(n->available, delta); - n->available -= d; - delta -= d; - } - if (n->available > 0) { - return 0; - } - } else { - if (n->available > 0) { - LOG(t) << "Expired:" << n->available << std::endl; - } - } - queue_.Pop(); - n = queue_.Head(); - } - return delta; -} - -void QuotaPrefetchImpl::OnResponse(SlotId slot_id, int req_amount, - int resp_amount, milliseconds expiration, - Tick t) { - std::lock_guard lock(mutex_); - --inflight_count_; - - LOG(t) << "OnResponse: req:" << req_amount << ", resp: " << resp_amount - << ", expire: " << expiration.count() << ", id: " << slot_id - << std::endl; - - // resp_amount of -1 indicates any network failures. - // Use fail open policy to handle any netowrk failures. - if (resp_amount == -1) { - resp_amount = req_amount; - expiration = milliseconds(kMaxExpirationInMs); - } - - Slot* slot = nullptr; - if (slot_id != 0) { - // The prefetched amount was added to the available queue - slot = FindSlotById(slot_id); - if (resp_amount < req_amount) { - int delta = req_amount - resp_amount; - // Substract it from its own request node. - if (slot != nullptr) { - int d = std::min(slot->available, delta); - slot->available -= d; - delta -= d; - } - if (delta > 0) { - // Substract it from other prefetched amounts - Substract(delta, t); - } - } - // Adjust the expiration - if (slot != nullptr && slot->available > 0) { - slot->expire_time = t + expiration; - } - } else { - // prefetched amount was NOT added to the pool yet. - if (resp_amount > 0) { - Add(resp_amount, t + expiration); - } - } - - if (resp_amount == req_amount) { - mode_ = OPEN; - } else { - mode_ = CLOSE; - } -} - -bool QuotaPrefetchImpl::Check(int amount, Tick t) { - std::lock_guard lock(mutex_); - - AttemptPrefetch(amount, t); - counter_.Inc(amount, t); - bool ret; - if (amount == 1) { - ret = Substract(amount, t) == 0; - } else { - ret = CheckMinAvailable(amount, t); - if (ret) { - Substract(amount, t); - } - } - if (!ret) { - LOG(t) << "Rejected amount: " << amount << std::endl; - } - return ret; -} - -} // namespace - -// Constructor with default values. -QuotaPrefetch::Options::Options() - : predict_window(kPredictWindowInMs), - min_prefetch_amount(kMinPrefetchAmount), - close_wait_window(kCloseWaitWindowInMs) {} - -std::unique_ptr QuotaPrefetch::Create(TransportFunc transport, - const Options& options, - Tick t) { - return std::unique_ptr( - new QuotaPrefetchImpl(transport, options, t)); -} - -} // namespace prefetch -} // namespace istio diff --git a/src/istio/prefetch/quota_prefetch_test.cc b/src/istio/prefetch/quota_prefetch_test.cc deleted file mode 100644 index 81c4fd0e76c..00000000000 --- a/src/istio/prefetch/quota_prefetch_test.cc +++ /dev/null @@ -1,422 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/prefetch/quota_prefetch.h" -#include "gtest/gtest.h" - -#include -#include - -using namespace std::chrono; -using Tick = ::istio::prefetch::QuotaPrefetch::Tick; -using DoneFunc = ::istio::prefetch::QuotaPrefetch::DoneFunc; - -namespace istio { -namespace prefetch { -namespace { - -// A rate limit server interface. -class RateServer { - public: - RateServer(int rate, milliseconds window) : rate_(rate), window_(window) {} - virtual ~RateServer() {} - - virtual int Alloc(int amount, milliseconds* expire, Tick t) = 0; - - protected: - int rate_; - milliseconds window_; -}; - -// A rolling window rate limit server. -class RollingWindow : public RateServer { - public: - RollingWindow(int rate, milliseconds window, Tick t) - : RateServer(rate, window), expire_time_(t) {} - - int Alloc(int amount, milliseconds* expire, Tick t) override { - if (t >= expire_time_) { - avail_ = rate_; - expire_time_ = t + window_; - } - int granted = std::min(amount, avail_); - avail_ -= granted; - *expire = duration_cast(expire_time_ - t); - return granted; - } - - private: - int avail_; - Tick expire_time_; -}; - -// A time based, as time advance, available tokens are added -// according to the rate. -class TimeBased : public RateServer { - public: - TimeBased(int rate, milliseconds window, Tick t) : RateServer(rate, window) { - allowance_ = rate_; - last_check_ = t; - } - - int Alloc(int amount, milliseconds* expire, Tick t) override { - milliseconds d = duration_cast(t - last_check_); - // Add the token according to the advanced time. - allowance_ += d.count() * rate_ / window_.count(); - if (allowance_ > rate_) { - allowance_ = rate_; - } - last_check_ = t; - int granted = std::min(amount, allowance_); - allowance_ -= granted; - // always expires at window time. - *expire = window_; - return granted; - } - - private: - int allowance_; - Tick last_check_; -}; - -// Delay a call with a delay. -class Delay { - public: - typedef std::function Func; - Delay() : delay_(0) {} - - void set_delay(milliseconds delay) { delay_ = delay; } - - void Call(Tick t, Func fn) { - list_.emplace_back(std::make_pair(t + delay_, fn)); - } - - void OnTimer(Tick t) { - while (!list_.empty() && t >= list_.front().first) { - list_.front().second(t); - list_.pop_front(); - } - } - - private: - milliseconds delay_; - std::list> list_; -}; - -struct TestData { - int rate; - milliseconds window; - // 3 tests with traffic as: rate - delta, rate and rate + delta. - int delta; -}; - -struct TestResult { - // margin is abs(actual_rate - expected_rate) / expected_rate. - // margin1 is for traffic = rate - delta - float margin1; - // margin1 is for traffic = rate - float margin2; - // margin1 is for traffic = rate + delta - float margin3; - // margin1 is for traffic = rate /2 - float margin4; - // margin1 is for traffic = 10 * rate - float margin5; -}; - -// The response delay. -const milliseconds kResponseDelay(200); -// The test duration in team of number of rate windows. -const int kTestDuration = 10; - -// Per minute window rate. -const TestData kPerMinuteWindow( - {.rate = 1200, // 1200 rpm, 20 rps, > 10 minPrefetch - .window = milliseconds(60000), // per minute window. - .delta = 100}); - -const TestData kPerSecondWindow( - {.rate = 20, // 20 rps, > 10 minPrefetch - .window = milliseconds(1000), // per second window. - .delta = 2}); - -class QuotaPrefetchTest : public ::testing::Test { - public: - QuotaPrefetch::TransportFunc GetTransportFunc() { - return [this](int amount, DoneFunc fn, Tick t) { - milliseconds expire; - int granted = rate_server_->Alloc(amount, &expire, t); - delay_.Call(t, - [fn, granted, expire](Tick t1) { fn(granted, expire, t1); }); - }; - } - - // Run the traffic in the window, return passed number. - int RunSingleClient(QuotaPrefetch& client, int rate, milliseconds window, - Tick t) { - milliseconds d = window / rate; - int passed = 0; - for (int i = 0; i < rate; ++i, t += d) { - if (client.Check(1, t)) { - ++passed; - } - delay_.OnTimer(t); - } - return passed; - } - - int RunTwoClients(QuotaPrefetch& client1, QuotaPrefetch& client2, int rate1, - int rate2, milliseconds window, Tick t) { - int rate = rate1 + rate2; - milliseconds d = window / rate; - int passed = 0; - int n1 = 0; - int n2 = 0; - for (int i = 0; i < rate; ++i, t += d) { - if (float(n1) / rate1 <= float(n2) / rate2) { - if (client1.Check(1, t)) { - ++passed; - } - ++n1; - } else { - if (client2.Check(1, t)) { - ++passed; - } - ++n2; - } - delay_.OnTimer(t); - } - return passed; - } - - void RunSingleTest(QuotaPrefetch& client, const TestData& data, int traffic, - float result, Tick t) { - int expected = data.rate * kTestDuration; - if (expected > traffic) expected = traffic; - int passed = - RunSingleClient(client, traffic, data.window * kTestDuration, t); - float margin = float(std::abs(passed - expected)) / expected; - std::cerr << "===RunTest margin: " << margin << ", expected: " << expected - << ", actual: " << passed << std::endl; - EXPECT_LE(margin, result); - } - - // Run 3 single client tests: - // one below the rate, one exact the rame and one above the rate. - void TestSingleClient(bool rolling_window, const TestData& data, - const TestResult& result) { - Tick t; - QuotaPrefetch::Options options; - auto client = QuotaPrefetch::Create(GetTransportFunc(), options, t); - if (rolling_window) { - rate_server_ = std::unique_ptr( - new RollingWindow(data.rate, data.window, t)); - } else { - rate_server_ = - std::unique_ptr(new TimeBased(data.rate, data.window, t)); - } - delay_.set_delay(kResponseDelay); - - // Send below the limit traffic: rate - delta - int traffic = (data.rate - data.delta) * kTestDuration; - RunSingleTest(*client, data, traffic, result.margin1, t); - - t += data.window * kTestDuration; - // The traffic is the same as the limit: rate - traffic = data.rate * kTestDuration; - RunSingleTest(*client, data, traffic, result.margin2, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate + delta - traffic = (data.rate + data.delta) * kTestDuration; - RunSingleTest(*client, data, traffic, result.margin3, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate / 2 - traffic = (data.rate / 2) * kTestDuration; - RunSingleTest(*client, data, traffic, result.margin4, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate * 10 - traffic = (data.rate * 10) * kTestDuration; - RunSingleTest(*client, data, traffic, result.margin5, t); - } - - void RunTwoClientTest(QuotaPrefetch& client1, QuotaPrefetch& client2, - const TestData& data, int traffic, float result, - Tick t) { - int expected = data.rate * kTestDuration; - if (expected > traffic) expected = traffic; - // one client is 3/4 and the other is 1/4 - int passed = RunTwoClients(client1, client2, traffic * 3 / 4, traffic / 4, - data.window * kTestDuration, t); - float margin = float(std::abs(passed - expected)) / expected; - std::cerr << "===RunTest margin: " << margin << ", expected: " << expected - << ", actual: " << passed << std::endl; - EXPECT_LE(margin, result); - } - - // Run 3 single client tests: - // one below the rate, one exact the rame and one above the rate. - void TestTwoClients(bool rolling_window, const TestData& data, - const TestResult& result) { - Tick t; - QuotaPrefetch::Options options; - auto client1 = QuotaPrefetch::Create(GetTransportFunc(), options, t); - auto client2 = QuotaPrefetch::Create(GetTransportFunc(), options, t); - if (rolling_window) { - rate_server_ = std::unique_ptr( - new RollingWindow(data.rate, data.window, t)); - } else { - rate_server_ = - std::unique_ptr(new TimeBased(data.rate, data.window, t)); - } - delay_.set_delay(kResponseDelay); - - // Send below the limit traffic: rate - delta - int traffic = (data.rate - data.delta) * kTestDuration; - RunTwoClientTest(*client1, *client2, data, traffic, result.margin1, t); - - t += data.window * kTestDuration; - // The traffic is the same as the limit: rate - traffic = data.rate * kTestDuration; - RunTwoClientTest(*client1, *client2, data, traffic, result.margin2, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate + delta - traffic = (data.rate + data.delta) * kTestDuration; - RunTwoClientTest(*client1, *client2, data, traffic, result.margin3, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate / 2 - traffic = (data.rate / 2) * kTestDuration; - RunTwoClientTest(*client1, *client2, data, traffic, result.margin4, t); - - t += data.window * kTestDuration; - // Send higher than the limit traffic: rate * 10 - traffic = (data.rate * 10) * kTestDuration; - RunTwoClientTest(*client1, *client2, data, traffic, result.margin5, t); - } - - std::unique_ptr rate_server_; - Delay delay_; -}; - -TEST_F(QuotaPrefetchTest, TestBigRollingWindow) { - TestSingleClient(true, // use rolling window, - kPerMinuteWindow, - {.margin1 = 0.0, - .margin2 = 0.006, - .margin3 = 0.0015, - .margin4 = 0.0, - .margin5 = 0.06}); -} - -TEST_F(QuotaPrefetchTest, TestSmallRollingWindow) { - TestSingleClient(true, // use rolling window, - kPerSecondWindow, - {.margin1 = 0.26, - .margin2 = 0.23, - .margin3 = 0.25, - .margin4 = 0.04, - .margin5 = 0.23}); -} - -TEST_F(QuotaPrefetchTest, TestBigTimeBased) { - TestSingleClient(false, // use time based. - kPerMinuteWindow, - {.margin1 = 0.0, - .margin2 = 0.0, - .margin3 = 0.08, - .margin4 = 0.0, - .margin5 = 0.1}); -} - -TEST_F(QuotaPrefetchTest, TestSmallTimeBased) { - TestSingleClient(false, // use time based - kPerSecondWindow, - {.margin1 = 0.0, - .margin2 = 0.0, - .margin3 = 0.035, - .margin4 = 0.03, - .margin5 = 0.23}); -} - -TEST_F(QuotaPrefetchTest, TestTwoClientBigRollingWindow) { - TestTwoClients(true, // use rolling window, - kPerMinuteWindow, - {.margin1 = 0.0, - .margin2 = 0.006, - .margin3 = 0.0015, - .margin4 = 0.001, - .margin5 = 0.057}); -} - -TEST_F(QuotaPrefetchTest, TestTwoClientSmallRollingWindow) { - TestTwoClients(true, // use rolling window, - kPerSecondWindow, - {.margin1 = 0.33, - .margin2 = 0.30, - .margin3 = 0.30, - .margin4 = 0.14, - .margin5 = 0.22}); -} - -TEST_F(QuotaPrefetchTest, TestTwoClientBigTimeBased) { - TestTwoClients(false, // use time based - kPerMinuteWindow, - {.margin1 = 0.0, - .margin2 = 0.0, - .margin3 = 0.055, - .margin4 = 0.0, - .margin5 = 0.0005}); -} - -TEST_F(QuotaPrefetchTest, TestTwoClientSmallTimeBased) { - TestTwoClients(false, // use time based - kPerSecondWindow, - {.margin1 = 0.062, - .margin2 = 0.14, - .margin3 = 0.15, - .margin4 = 0.05, - .margin5 = 0.17}); -} - -TEST_F(QuotaPrefetchTest, TestNotEnoughAmount) { - Tick t; - QuotaPrefetch::Options options; - auto client = QuotaPrefetch::Create(GetTransportFunc(), options, t); - rate_server_ = - std::unique_ptr(new RollingWindow(5, milliseconds(1000), t)); - - // First one is always true, use it to trigger prefetch - EXPECT_TRUE(client->Check(1, t)); - // Alloc response is called OnTimer. - delay_.OnTimer(t); - - // Only 4 tokens remain, so asking for 5 should fail. - t += milliseconds(1); - EXPECT_FALSE(client->Check(5, t)); - delay_.OnTimer(t); - - // Since last one fails, still has 4 tokens. - t += milliseconds(1); - EXPECT_TRUE(client->Check(4, t)); - delay_.OnTimer(t); -} - -} // namespace -} // namespace prefetch -} // namespace istio diff --git a/src/istio/prefetch/time_based_counter.cc b/src/istio/prefetch/time_based_counter.cc deleted file mode 100644 index 4a658521946..00000000000 --- a/src/istio/prefetch/time_based_counter.cc +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/prefetch/time_based_counter.h" - -using namespace std::chrono; - -namespace istio { -namespace prefetch { - -TimeBasedCounter::TimeBasedCounter(int window_size, milliseconds duration, - Tick t) - : slots_(window_size), - slot_duration_(duration / window_size), - count_(0), - tail_(0), - last_time_(t) {} - -void TimeBasedCounter::Clear(Tick t) { - last_time_ = t; - for (size_t i = 0; i < slots_.size(); i++) { - slots_[i] = 0; - } - tail_ = count_ = 0; -} - -void TimeBasedCounter::Roll(Tick t) { - auto d = duration_cast(t - last_time_); - uint32_t n = uint32_t(d.count() / slot_duration_.count()); - if (n >= slots_.size()) { - Clear(t); - return; - } - - for (uint32_t i = 0; i < n; i++) { - tail_ = (tail_ + 1) % slots_.size(); - count_ -= slots_[tail_]; - slots_[tail_] = 0; - last_time_ += slot_duration_; - } -} - -void TimeBasedCounter::Inc(int n, Tick t) { - Roll(t); - slots_[tail_] += n; - count_ += n; -} - -int TimeBasedCounter::Count(Tick t) { - Roll(t); - return count_; -} - -} // namespace prefetch -} // namespace istio diff --git a/src/istio/prefetch/time_based_counter.h b/src/istio/prefetch/time_based_counter.h deleted file mode 100644 index 862e24e97e6..00000000000 --- a/src/istio/prefetch/time_based_counter.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_PREFETCH_TIME_BASED_COUNTER_H_ -#define ISTIO_PREFETCH_TIME_BASED_COUNTER_H_ - -#include -#include - -namespace istio { -namespace prefetch { - -// Define a counter for the count in a time based window. -// Each count is associated with a time stamp. The count outside -// of the window will not be counted. -class TimeBasedCounter { - public: - // Define a time stamp type. - // Ideally, Now() timestamp should be used inside the functions. - // But for easy unit_test, pass the time in. - // The input time should be always increasing. - typedef std::chrono::time_point Tick; - - TimeBasedCounter(int window_size, std::chrono::milliseconds duration, Tick t); - - // Add n count to the counter. - void Inc(int n, Tick t); - - // Get the count. - int Count(Tick t); - - private: - // Clear the whole window - void Clear(Tick t); - // Roll the window - void Roll(Tick t); - - std::vector slots_; - std::chrono::milliseconds slot_duration_; - int count_; - int tail_; - Tick last_time_; -}; - -} // namespace prefetch -} // namespace istio - -#endif // ISTIO_CLIENT_PREFETCH_TIME_BASED_COUNTER_H_ diff --git a/src/istio/prefetch/time_based_counter_test.cc b/src/istio/prefetch/time_based_counter_test.cc deleted file mode 100644 index 56ff0eb137c..00000000000 --- a/src/istio/prefetch/time_based_counter_test.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/prefetch/time_based_counter.h" -#include "gtest/gtest.h" - -namespace istio { -namespace prefetch { -namespace { - -std::chrono::time_point FakeTime(int t) { - return std::chrono::time_point( - std::chrono::milliseconds(t)); -} - -TEST(TimeBasedCounterTest, Test1) { - TimeBasedCounter c(3, std::chrono::milliseconds(3), FakeTime(0)); - c.Inc(1, FakeTime(4)); - c.Inc(1, FakeTime(5)); - c.Inc(1, FakeTime(7)); - - // Current slots are 6, 7, 8. and 4 and 5 are out. - ASSERT_EQ(c.Count(FakeTime(8)), 1); -} - -} // namespace -} // namespace prefetch -} // namespace istio diff --git a/src/istio/quota_config/BUILD b/src/istio/quota_config/BUILD deleted file mode 100644 index 42c1f7a97b7..00000000000 --- a/src/istio/quota_config/BUILD +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "config_parser_lib", - srcs = [ - "config_parser_impl.cc", - "config_parser_impl.h", - ], - visibility = ["//visibility:public"], - deps = [ - "//external:mixer_client_config_cc_proto", - "//include/istio/quota_config:headers_lib", - ], -) - -cc_test( - name = "config_parser_impl_test", - size = "small", - srcs = ["config_parser_impl_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":config_parser_lib", - "//external:googletest_main", - "//include/istio/utils:headers_lib", - ], -) diff --git a/src/istio/quota_config/config_parser_impl.cc b/src/istio/quota_config/config_parser_impl.cc deleted file mode 100644 index d1786132afc..00000000000 --- a/src/istio/quota_config/config_parser_impl.cc +++ /dev/null @@ -1,112 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "src/istio/quota_config/config_parser_impl.h" - -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::Attributes_AttributeValue; -using ::istio::mixer::v1::config::client::AttributeMatch; -using ::istio::mixer::v1::config::client::QuotaRule; -using ::istio::mixer::v1::config::client::QuotaSpec; -using ::istio::mixer::v1::config::client::StringMatch; - -namespace istio { -namespace quota_config { - -ConfigParserImpl::ConfigParserImpl(const QuotaSpec& spec_pb) - : spec_pb_(spec_pb) { - // Build regex map - for (const auto& rule : spec_pb_.rules()) { - for (const auto& match : rule.match()) { - for (const auto& map_it : match.clause()) { - const auto& match = map_it.second; - if (match.match_type_case() == StringMatch::kRegex) { - regex_map_[match.regex()] = std::regex(match.regex()); - } - } - } - } -} - -void ConfigParserImpl::GetRequirements( - const Attributes& attributes, std::vector* results) const { - for (const auto& rule : spec_pb_.rules()) { - bool matched = false; - for (const auto& match : rule.match()) { - if (MatchAttributes(match, attributes)) { - matched = true; - break; - } - } - // If not match, applies to all requests. - if (matched || rule.match_size() == 0) { - for (const auto& quota : rule.quotas()) { - results->push_back({quota.quota(), quota.charge()}); - } - } - } -} - -bool ConfigParserImpl::MatchAttributes(const AttributeMatch& match, - const Attributes& attributes) const { - const auto& attributes_map = attributes.attributes(); - for (const auto& map_it : match.clause()) { - // map is attribute_name to StringMatch. - const std::string& name = map_it.first; - const auto& match = map_it.second; - - // Check if required attribure exists with string type. - const auto& it = attributes_map.find(name); - if (it == attributes_map.end() || - it->second.value_case() != Attributes_AttributeValue::kStringValue) { - return false; - } - const std::string& value = it->second.string_value(); - - switch (match.match_type_case()) { - case StringMatch::kExact: - if (value != match.exact()) { - return false; - } - break; - case StringMatch::kPrefix: - if (value.length() < match.prefix().length() || - value.compare(0, match.prefix().length(), match.prefix()) != 0) { - return false; - } - break; - case StringMatch::kRegex: { - const auto& reg_it = regex_map_.find(match.regex()); - // All regex should be pre-build. - GOOGLE_CHECK(reg_it != regex_map_.end()); - if (!std::regex_match(value, reg_it->second)) { - return false; - } - } break; - default: - // match_type not set case, an empty StringMatch, ignore it. - break; - } - } - return true; -} - -std::unique_ptr ConfigParser::Create( - const ::istio::mixer::v1::config::client::QuotaSpec& spec_pb) { - return std::unique_ptr(new ConfigParserImpl(spec_pb)); -} - -} // namespace quota_config -} // namespace istio diff --git a/src/istio/quota_config/config_parser_impl.h b/src/istio/quota_config/config_parser_impl.h deleted file mode 100644 index 4f318e53fc5..00000000000 --- a/src/istio/quota_config/config_parser_impl.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ISTIO_QUOTA_CONFIG_CONFIG_PARSER_IMPL_H_ -#define ISTIO_QUOTA_CONFIG_CONFIG_PARSER_IMPL_H_ - -#include "include/istio/quota_config/config_parser.h" - -#include -#include - -namespace istio { -namespace quota_config { - -// An object to implement ConfigParser interface. -class ConfigParserImpl : public ConfigParser { - public: - ConfigParserImpl( - const ::istio::mixer::v1::config::client::QuotaSpec& spec_pb); - - // Get quota requirements for a attribute set. - void GetRequirements(const ::istio::mixer::v1::Attributes& attributes, - std::vector* results) const override; - - private: - // Check one attribute match. - bool MatchAttributes( - const ::istio::mixer::v1::config::client::AttributeMatch& match, - const ::istio::mixer::v1::Attributes& attributes) const; - // the spec proto. - const ::istio::mixer::v1::config::client::QuotaSpec& spec_pb_; - - // Stored regex objects. - std::unordered_map regex_map_; -}; - -} // namespace quota_config -} // namespace istio - -#endif // ISTIO_QUOTA_CONFIG_CONFIG_PARSER_IMPL_H_ diff --git a/src/istio/quota_config/config_parser_impl_test.cc b/src/istio/quota_config/config_parser_impl_test.cc deleted file mode 100644 index 1250d0a85c2..00000000000 --- a/src/istio/quota_config/config_parser_impl_test.cc +++ /dev/null @@ -1,176 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/quota_config/config_parser.h" -#include "include/istio/utils/attributes_builder.h" - -#include "google/protobuf/text_format.h" -#include "gtest/gtest.h" - -using ::google::protobuf::TextFormat; -using ::istio::mixer::v1::Attributes; -using ::istio::mixer::v1::config::client::QuotaSpec; -using ::istio::utils::AttributesBuilder; - -namespace istio { -namespace quota_config { -namespace { - -const char kQuotaEmptyMatch[] = R"( -rules { - quotas { - quota: "quota1" - charge: 1 - } - quotas { - quota: "quota2" - charge: 2 - } -} -)"; - -const char kQuotaMatch[] = R"( -rules { - match { - clause { - key: "request.http_method" - value { - exact: "GET" - } - } - clause { - key: "request.path" - value { - prefix: "/books" - } - } - } - match { - clause { - key: "api.operation" - value { - exact: "get_books" - } - } - } - quotas { - quota: "quota-name" - charge: 1 - } -} -)"; - -const char kQuotaRegexMatch[] = R"( -rules { - match { - clause { - key: "request.path" - value { - regex: "/shelves/.*/books" - } - } - } - quotas { - quota: "quota-name" - charge: 1 - } -} -)"; - -// Define similar data structure for quota requirement -// But this one has operator== for comparison so that EXPECT_EQ -// can directly use its vector. -struct Quota { - std::string quota; - int64_t charge; - - bool operator==(const Quota& v) const { - return quota == v.quota && charge == v.charge; - } -}; - -// A short name for the vector. -using QV = std::vector; - -// Converts the vector of Requirement to vector of Quota -std::vector GetRequirements(const ConfigParser& parser, - const Attributes& attributes) { - std::vector requirements; - parser.GetRequirements(attributes, &requirements); - std::vector v; - for (const auto& it : requirements) { - v.push_back({it.quota, it.charge}); - } - return v; -} - -TEST(ConfigParserTest, TestEmptyMatch) { - QuotaSpec quota_spec; - ASSERT_TRUE(TextFormat::ParseFromString(kQuotaEmptyMatch, "a_spec)); - auto parser = ConfigParser::Create(quota_spec); - - Attributes attributes; - // If match clause is empty, it matches all requests. - ASSERT_EQ(GetRequirements(*parser, attributes), - QV({{"quota1", 1}, {"quota2", 2}})); -} - -TEST(ConfigParserTest, TestMatch) { - QuotaSpec quota_spec; - ASSERT_TRUE(TextFormat::ParseFromString(kQuotaMatch, "a_spec)); - auto parser = ConfigParser::Create(quota_spec); - - Attributes attributes; - AttributesBuilder builder(&attributes); - ASSERT_EQ(GetRequirements(*parser, attributes), QV()); - - // Wrong http_method - builder.AddString("request.http_method", "POST"); - builder.AddString("request.path", "/books/1"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV()); - - // Matched - builder.AddString("request.http_method", "GET"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV({{"quota-name", 1}})); - - attributes.mutable_attributes()->clear(); - // Wrong api.operation - builder.AddString("api.operation", "get_shelves"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV()); - - // Matched - builder.AddString("api.operation", "get_books"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV({{"quota-name", 1}})); -} - -TEST(ConfigParserTest, TestRegexMatch) { - QuotaSpec quota_spec; - ASSERT_TRUE(TextFormat::ParseFromString(kQuotaRegexMatch, "a_spec)); - auto parser = ConfigParser::Create(quota_spec); - - Attributes attributes; - AttributesBuilder builder(&attributes); - // Not match - builder.AddString("request.path", "/shelves/1/bar"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV()); - - // match - builder.AddString("request.path", "/shelves/10/books"); - ASSERT_EQ(GetRequirements(*parser, attributes), QV({{"quota-name", 1}})); -} - -} // namespace -} // namespace quota_config -} // namespace istio diff --git a/src/istio/utils/BUILD b/src/istio/utils/BUILD deleted file mode 100644 index 8fb99afb5f2..00000000000 --- a/src/istio/utils/BUILD +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -licenses(["notice"]) - -cc_library( - name = "utils_lib", - srcs = [ - "protobuf.cc", - "status.cc", - ], - visibility = ["//visibility:public"], - deps = [ - "//external:protobuf", - "//include/istio/utils:headers_lib", - ], -) - -cc_library( - name = "md5_lib", - srcs = ["md5.cc"], - visibility = ["//visibility:public"], - deps = [ - "//external:boringssl_crypto", - "//include/istio/utils:headers_lib", - ], -) - -cc_test( - name = "simple_lru_cache_test", - size = "small", - srcs = ["simple_lru_cache_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - "//external:googletest_main", - "//include/istio/utils:simple_lru_cache", - ], -) - -cc_test( - name = "md5_test", - size = "small", - srcs = ["md5_test.cc"], - linkopts = [ - "-lm", - "-lpthread", - ], - linkstatic = 1, - deps = [ - ":md5_lib", - "//external:googletest_main", - ], -) diff --git a/src/istio/utils/md5.cc b/src/istio/utils/md5.cc deleted file mode 100644 index e1c3b89d341..00000000000 --- a/src/istio/utils/md5.cc +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/utils/md5.h" -#include - -namespace istio { -namespace utils { - -MD5::MD5() : finalized_(false) { MD5_Init(&ctx_); } - -MD5& MD5::Update(const void* data, size_t size) { - // Not update after finalized. - assert(!finalized_); - MD5_Update(&ctx_, data, size); - return *this; -} - -std::string MD5::Digest() { - if (!finalized_) { - MD5_Final(digest_, &ctx_); - finalized_ = true; - } - return std::string(reinterpret_cast(digest_), kDigestLength); -} - -std::string MD5::DebugString(const std::string& digest) { - assert(digest.size() == kDigestLength); - char buf[kDigestLength * 2 + 1]; - char* p = buf; - for (int i = 0; i < kDigestLength; i++, p += 2) { - sprintf(p, "%02x", (unsigned char)digest[i]); - } - *p = 0; - return std::string(buf, kDigestLength * 2); -} - -std::string MD5::operator()(const void* data, size_t size) { - return Update(data, size).Digest(); -} - -} // namespace utils -} // namespace istio diff --git a/src/istio/utils/md5_test.cc b/src/istio/utils/md5_test.cc deleted file mode 100644 index b8279217167..00000000000 --- a/src/istio/utils/md5_test.cc +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/utils/md5.h" -#include "gtest/gtest.h" - -namespace istio { -namespace utils { -namespace { - -TEST(MD5Test, TestPriableGigest) { - static const char data[] = "Test Data"; - ASSERT_EQ("0a22b2ac9d829ff3605d81d5ae5e9d16", - MD5::DebugString(MD5()(data, sizeof(data)))); -} - -TEST(MD5Test, TestGigestEqual) { - static const char data1[] = "Test Data1"; - static const char data2[] = "Test Data2"; - auto d1 = MD5()(data1, sizeof(data1)); - auto d11 = MD5()(data1, sizeof(data1)); - auto d2 = MD5()(data2, sizeof(data2)); - ASSERT_EQ(d11, d1); - ASSERT_NE(d1, d2); -} - -} // namespace -} // namespace utils -} // namespace istio diff --git a/src/istio/utils/protobuf.cc b/src/istio/utils/protobuf.cc deleted file mode 100644 index cca92bf66dd..00000000000 --- a/src/istio/utils/protobuf.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/utils/protobuf.h" - -using namespace std::chrono; - -using ::google::protobuf::Duration; -using ::google::protobuf::Timestamp; - -using ::google::protobuf::util::Status; -using ::google::protobuf::util::error::Code; - -namespace istio { -namespace utils { -namespace { - -// The error string prefix for Invalid dictionary. -const char* kInvalidDictionaryErrorPrefix = - "Request could not be processed due to invalid"; - -} // namespace - -// Convert timestamp from time_point to Timestamp -Timestamp CreateTimestamp(system_clock::time_point tp) { - Timestamp time_stamp; - long long nanos = duration_cast(tp.time_since_epoch()).count(); - - time_stamp.set_seconds(nanos / 1000000000); - time_stamp.set_nanos(nanos % 1000000000); - return time_stamp; -} - -Duration CreateDuration(nanoseconds value) { - Duration duration; - duration.set_seconds(value.count() / 1000000000); - duration.set_nanos(value.count() % 1000000000); - return duration; -} - -milliseconds ToMilliseonds(const Duration& duration) { - return duration_cast(seconds(duration.seconds())) + - duration_cast(nanoseconds(duration.nanos())); -} - -bool InvalidDictionaryStatus(const Status& status) { - return status.error_code() == Code::INVALID_ARGUMENT && - status.error_message().starts_with(kInvalidDictionaryErrorPrefix); -} - -} // namespace utils -} // namespace istio diff --git a/src/istio/utils/simple_lru_cache_test.cc b/src/istio/utils/simple_lru_cache_test.cc deleted file mode 100644 index 568f31ea87a..00000000000 --- a/src/istio/utils/simple_lru_cache_test.cc +++ /dev/null @@ -1,1112 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Tests of SimpleLRUCache - -#include "include/istio/utils/simple_lru_cache.h" -#include "include/istio/utils/simple_lru_cache_inl.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using ::testing::HasSubstr; -using ::testing::NotNull; - -namespace istio { -namespace utils { - -// Keep track of whether or not specific values are in the cache -static const int kElems = 100; -static const int kCacheSize = 10; -static bool in_cache[kElems]; - -namespace { - -// Blocks until SimpleCycleTimer::Now() returns a new value. -void TickClock() { - int64_t start = SimpleCycleTimer::Now(); - const int kMaxAttempts = 10; - int num_attempts = 0; - do { - // sleep one microsecond. - usleep(1); - } while (++num_attempts < kMaxAttempts && SimpleCycleTimer::Now() == start); - // Unable to tick the clock - assert(num_attempts < kMaxAttempts); -} - -} // namespace - -// Value type -struct TestValue { - int label; // Index into "in_cache" - explicit TestValue(int l) : label(l) {} - - protected: - // Make sure that TestCache can delete TestValue when declared as friend. - friend class SimpleLRUCache; - friend class TestCache; - ~TestValue() {} -}; - -class TestCache : public SimpleLRUCache { - public: - explicit TestCache(int64_t size, bool check_in_cache = true) - : SimpleLRUCache(size), check_in_cache_(check_in_cache) {} - - protected: - virtual void RemoveElement(const int& key, TestValue* v) { - if (v && check_in_cache_) { - assert(in_cache[v->label]); - std::cout << " Evict:" << v->label; - in_cache[v->label] = false; - } - delete v; - } - - const bool check_in_cache_; -}; - -class SimpleLRUCacheTest : public ::testing::Test { - protected: - SimpleLRUCacheTest() {} - virtual ~SimpleLRUCacheTest() {} - - virtual void SetUp() { - for (int i = 0; i < kElems; ++i) in_cache[i] = false; - } - - virtual void TearDown() { - if (cache_) cache_->Clear(); - for (int i = 0; i < kElems; i++) { - assert(!in_cache[i]); - } - } - - void TestInOrderEvictions(int cache_size); - void TestSetMaxSize(); - void TestOverfullEvictionPolicy(); - void TestRemoveUnpinned(); - void TestExpiration(bool lru, bool release_quickly); - void TestLargeExpiration(bool lru, double timeout); - - std::unique_ptr cache_; -}; - -TEST_F(SimpleLRUCacheTest, IteratorDefaultConstruct) { - TestCache::const_iterator default_unused; -} - -TEST_F(SimpleLRUCacheTest, Iteration) { - int count = 0; - cache_.reset(new TestCache(kCacheSize)); - - // fill the cache, evict some items, ensure i can iterate over all remaining - for (int i = 0; i < kElems; ++i) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - for (TestCache::const_iterator pos = cache_->begin(); pos != cache_->end(); - ++pos) { - ++count; - ASSERT_EQ(pos->first, pos->second->label); - ASSERT_TRUE(in_cache[pos->second->label]); - } - ASSERT_EQ(count, kCacheSize); - ASSERT_EQ(cache_->Entries(), kCacheSize); - cache_->Clear(); - - // iterate over the cache w/o filling the cache to capacity first - for (int i = 0; i < kCacheSize / 2; ++i) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - count = 0; - for (TestCache::const_iterator pos = cache_->begin(); pos != cache_->end(); - ++pos) { - ++count; - ASSERT_EQ(pos->first, pos->second->label); - ASSERT_TRUE(in_cache[pos->second->label]); - } - ASSERT_EQ(count, kCacheSize / 2); - ASSERT_EQ(cache_->Entries(), kCacheSize / 2); -} - -TEST_F(SimpleLRUCacheTest, StdCopy) { - cache_.reset(new TestCache(kCacheSize)); - for (int i = 0; i < kElems; ++i) { - in_cache[i] = true; - cache_->InsertPinned(i, new TestValue(i), 1); - } - // All entries are pinned, they are all in cache - ASSERT_EQ(cache_->Entries(), kElems); - ASSERT_EQ(cache_->PinnedSize(), kElems); - // Non have been removed, so Defer size is 0 - ASSERT_EQ(cache_->DeferredEntries(), 0); - - std::vector> to_release; - std::copy(cache_->begin(), cache_->end(), std::back_inserter(to_release)); - for (const auto& entry : to_release) { - cache_->Release(entry.first, entry.second); - } - - // After all of them un-pinned - ASSERT_EQ(cache_->Entries(), kCacheSize); - ASSERT_EQ(cache_->PinnedSize(), 0); - ASSERT_EQ(cache_->DeferredEntries(), 0); -} - -void SimpleLRUCacheTest::TestInOrderEvictions(int cache_size) { - for (int i = 0; i < kElems; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - - if (i >= cache_size) { - ASSERT_TRUE(!in_cache[i - cache_size]); - } - } -} - -TEST_F(SimpleLRUCacheTest, InOrderEvictions) { - cache_.reset(new TestCache(kCacheSize)); - TestInOrderEvictions(kCacheSize); -} - -TEST_F(SimpleLRUCacheTest, InOrderEvictionsWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - cache_->SetMaxIdleSeconds(2000); - TestInOrderEvictions(kCacheSize); -} - -TEST_F(SimpleLRUCacheTest, InOrderEvictionsWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - cache_->SetAgeBasedEviction(2000); - TestInOrderEvictions(kCacheSize); -} - -void SimpleLRUCacheTest::TestSetMaxSize() { - int cache_size = cache_->MaxSize(); - - // Fill the cache exactly and verify all values are present. - for (int i = 0; i < cache_size; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - EXPECT_EQ(cache_size, cache_->Size()); - int elems = cache_size; - for (int i = 0; i < elems; i++) { - ASSERT_TRUE(in_cache[i]) << i; - } - - // Double the size; all values should still be present. - cache_size *= 2; - ASSERT_LE(cache_size, kElems); - cache_->SetMaxSize(cache_size); - EXPECT_EQ(elems, cache_->Size()); - for (int i = 0; i < elems; i++) { - ASSERT_TRUE(in_cache[i]) << i; - } - - // Fill the cache to the new size and ensure all values are present. - for (int i = elems; i < cache_size; i++) { - ASSERT_TRUE(!cache_->Lookup(i)) << i; - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - EXPECT_EQ(cache_size, cache_->Size()); - elems = cache_size; - for (int i = 0; i < cache_size; i++) { - ASSERT_TRUE(in_cache[i]) << i; - } - - // Cut the size to half of the original size, elements should be evicted. - cache_size /= 4; - ASSERT_GT(cache_size, 0); - cache_->SetMaxSize(cache_size); - EXPECT_EQ(cache_size, cache_->Size()); - for (int i = 0; i < elems; i++) { - if (i < elems - cache_size) { - ASSERT_TRUE(!in_cache[i]) << i; - } else { - ASSERT_TRUE(in_cache[i]) << i; - } - } - - // Clear the cache and run the in order evictions test with the final size. - cache_->Clear(); - TestInOrderEvictions(cache_size); - EXPECT_EQ(cache_size, cache_->Size()); -} - -TEST_F(SimpleLRUCacheTest, SetMaxSize) { - cache_.reset(new TestCache(kCacheSize)); - TestSetMaxSize(); -} - -TEST_F(SimpleLRUCacheTest, SetMaxSizeWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - cache_->SetMaxIdleSeconds(2000); - TestSetMaxSize(); -} - -TEST_F(SimpleLRUCacheTest, SetMaxSizeWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - cache_->SetAgeBasedEviction(2000); - TestSetMaxSize(); -} - -TEST_F(SimpleLRUCacheTest, VoidValues) { - // - // This naive code may double-pin at Lookup() the second time - // around if GetThing() returns 0 (which may be ok): - // - // Thing* thing = cache.Lookup(key); - // if (!thing) { - // thing = GetThing(key); - // cache.InsertPinned(key, thing, 1); - // } - // UseThing(thing); - // cache.Release(key, thing); - // - // One cannot distinguish between "not present" and "nullptr value" using - // return value from Lookup(), so let's do it with StillInUse(). - // - - cache_.reset(new TestCache(1)); - - cache_->InsertPinned(5, 0, 1); - cache_->Release(5, 0); - - if (cache_->StillInUse(5, 0)) { - // Released, but still in there - // This path is executed given Dec 2007 implementation - - // Lookup pins 5, even though it returns nullptr - ASSERT_TRUE(nullptr == cache_->Lookup(5)); - } else { - // Not in there, let's insert it - // This path is not executed given Dec 2007 implementation - cache_->InsertPinned(5, 0, 1); - } - - ASSERT_EQ(1, cache_->PinnedSize()); - cache_->Release(5, 0); - ASSERT_EQ(0, cache_->PinnedSize()); - - cache_->Clear(); -} - -void SimpleLRUCacheTest::TestOverfullEvictionPolicy() { - // Fill with elements that should stick around if used over and over - for (int i = 0; i < kCacheSize; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - - for (int i = kCacheSize; i < kElems; i++) { - // Access all of the elements that should stick around - for (int j = 0; j < kCacheSize; j++) { - TestValue* v = cache_->Lookup(j); - ASSERT_TRUE(v != nullptr); - cache_->Release(j, v); - } - - // Insert new value - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - ASSERT_TRUE(in_cache[i]); - if (i > kCacheSize) { - ASSERT_TRUE(!in_cache[i - 1]); - } - } -} - -TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicy) { - cache_.reset(new TestCache(kCacheSize + 1)); - TestOverfullEvictionPolicy(); -} - -TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize + 1)); - // Here we are not testing idle eviction, just that LRU eviction - // still works correctly when the cache is overfull. - cache_->SetMaxIdleSeconds(2000); - TestOverfullEvictionPolicy(); -} - -TEST_F(SimpleLRUCacheTest, OverfullEvictionPolicyWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - // With age-based eviction usage is ignored and instead the oldest inserted - // element is evicted when cahce becomes overfull. - cache_->SetAgeBasedEviction(2000); - - for (int i = 0; i < kCacheSize; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - - // Access all of the elements in the reverse order. - for (int j = kCacheSize - 1; j >= 0; j--) { - TestCache::ScopedLookup lv(cache_.get(), j); - ASSERT_TRUE(lv.value() != nullptr); - } - - // Key 0 was accessed most recently, yet new value evicts it because it is - // the oldest one. - ASSERT_TRUE(!cache_->Lookup(kCacheSize)); - TestValue* v = new TestValue(kCacheSize); - in_cache[kCacheSize] = true; - cache_->Insert(kCacheSize, v, 1); - ASSERT_TRUE(in_cache[kCacheSize]); - ASSERT_TRUE(!in_cache[0]); -} - -TEST_F(SimpleLRUCacheTest, Update) { - cache_.reset(new TestCache(kCacheSize, false)); // Don't check in_cache. - // Insert some values. - for (int i = 0; i < kCacheSize; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - cache_->Insert(i, v, 1); - } - // Update them. - for (int i = 0; i < kCacheSize; i++) { - TestCache::ScopedLookup lookup(cache_.get(), i); - ASSERT_TRUE(lookup.Found()); - EXPECT_TRUE(lookup.value()->label == i); - lookup.value()->label = -i; - } - // Read them back. - for (int i = 0; i < kCacheSize; i++) { - TestCache::ScopedLookup lookup(cache_.get(), i); - ASSERT_TRUE(lookup.Found()); - EXPECT_TRUE(lookup.value()->label == -i); - } - // Flush them out. - for (int i = 0; i < kCacheSize; i++) { - TestValue* v = new TestValue(i); - cache_->Insert(i + kCacheSize, v, 1); - } - // Original values are gone. - for (int i = 0; i < kCacheSize; i++) { - TestCache::ScopedLookup lookup(cache_.get(), i + kCacheSize); - ASSERT_TRUE(lookup.Found()); - TestCache::ScopedLookup lookup2(cache_.get(), i); - ASSERT_TRUE(!lookup2.Found()); - } -} - -TEST_F(SimpleLRUCacheTest, Pinning) { - static const int kPinned = kCacheSize + 4; - cache_.reset(new TestCache(kCacheSize)); - for (int i = 0; i < kElems; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - if (i < kPinned) { - cache_->InsertPinned(i, v, 1); - } else { - cache_->Insert(i, v, 1); - } - } - for (int i = 0; i < kPinned; i++) { - ASSERT_TRUE(in_cache[i]); - TestValue* v = cache_->Lookup(i); - ASSERT_TRUE(v != nullptr); - cache_->Release(i, v); // For initial InsertPinned - cache_->Release(i, v); // For the previous Lookup - } -} - -TEST_F(SimpleLRUCacheTest, Remove) { - cache_.reset(new TestCache(kCacheSize)); - for (int i = 0; i < kElems; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - - // Remove previous element, but leave "0" alone - if (i > 1) { - const int key = i - 1; - int prev_entries = cache_->Entries(); - if ((key % 2) == 0) { // test normal removal - cache_->Remove(key); - } else { // test different removal status - TestValue* const v2 = cache_->Lookup(key); - ASSERT_TRUE(v2) << ": key=" << key; - cache_->Remove(key); - ASSERT_TRUE(cache_->StillInUse(key)) << ": " << key; - cache_->Remove(key); - ASSERT_TRUE(cache_->StillInUse(key)) << ": " << key; - - cache_->Release(key, v2); - } - ASSERT_EQ(cache_->Entries(), prev_entries - 1); - ASSERT_TRUE(!in_cache[key]); - ASSERT_TRUE(!cache_->StillInUse(key)) << ": " << key; - } - } - ASSERT_TRUE(in_cache[0]); - ASSERT_TRUE(cache_->StillInUse(0)); -} - -void SimpleLRUCacheTest::TestRemoveUnpinned() { - for (int i = 0; i < kCacheSize; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - - TestValue* const val = cache_->Lookup(1); - ASSERT_TRUE(val); - cache_->RemoveUnpinned(); - ASSERT_EQ(cache_->Entries(), 1); - // Check that only value 1 is still in the cache - for (int i = 0; i < kCacheSize; i++) { - if (i != 1) { - ASSERT_TRUE(!in_cache[i]); - } - } - ASSERT_TRUE(in_cache[1]); - cache_->Release(1, val); - cache_->RemoveUnpinned(); - ASSERT_EQ(cache_->Entries(), 0); - ASSERT_TRUE(!in_cache[1]); -} - -TEST_F(SimpleLRUCacheTest, RemoveUnpinned) { - cache_.reset(new TestCache(kCacheSize)); - TestRemoveUnpinned(); -} - -TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithIdleEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - // Here we are not testing idle eviction, just that RemoveUnpinned - // works correctly with it enabled. - cache_->SetMaxIdleSeconds(2000); - TestRemoveUnpinned(); -} - -TEST_F(SimpleLRUCacheTest, RemoveUnpinnedWithAgeBasedEvictionEnabled) { - cache_.reset(new TestCache(kCacheSize)); - // Here we are not testing age-based eviction, just that RemoveUnpinned - // works correctly with it enabled. - cache_->SetAgeBasedEviction(2000); - TestRemoveUnpinned(); -} - -TEST_F(SimpleLRUCacheTest, MultiInsert) { - cache_.reset(new TestCache(kCacheSize)); - for (int i = 0; i < kElems; i++) { - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(0, v, 1); - if (i > 0) { - ASSERT_TRUE(!in_cache[i - 1]); // Older entry must have been evicted - } - } -} - -TEST_F(SimpleLRUCacheTest, MultiInsertPinned) { - cache_.reset(new TestCache(kCacheSize)); - TestValue* list[kElems]; - for (int i = 0; i < kElems; i++) { - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->InsertPinned(0, v, 1); - list[i] = v; - } - for (int i = 0; i < kElems; i++) { - ASSERT_TRUE(in_cache[i]); - ASSERT_TRUE(cache_->StillInUse(0, list[i])); - } - for (int i = 0; i < kElems; i++) { - cache_->Release(0, list[i]); - } -} - -void SimpleLRUCacheTest::TestExpiration(bool lru, bool release_quickly) { - cache_.reset(new TestCache(kCacheSize)); - if (lru) { - cache_->SetMaxIdleSeconds(0.2); // 200 milliseconds - } else { - cache_->SetAgeBasedEviction(0.2); // 200 milliseconds - } - for (int i = 0; i < kCacheSize; i++) { - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - for (int i = 0; i < kCacheSize; i++) ASSERT_TRUE(in_cache[i]); - - usleep(110 * 1000); - - TestValue* v1 = cache_->Lookup(0); - ASSERT_TRUE(v1 != nullptr); - if (release_quickly) { - cache_->Release(0, v1); - v1 = nullptr; - } - for (int i = 0; i < kCacheSize; i++) ASSERT_TRUE(in_cache[i]); - - // Sleep more: should cause expiration of everything we - // haven't touched, and the one we touched if age-based. - usleep(110 * 1000); - - // Nothing gets expired until we call one of the cache methods. - for (int i = 0; i < kCacheSize; i++) ASSERT_TRUE(in_cache[i]); - - // It's now 220 ms since element 0 was created, and - // 110 ms since we last looked at it. If we configured - // the cache in LRU mode it should still be there, but - // if we configured it in age-based mode it should be gone. - // This is true even if the element was checked out: it should - // be on the defer_ list, not the table_ list as it is expired. - // Whether or not the element was pinned shouldn't matter: - // it should be expired either way in AgeBased mode, - // and not expired either way in lru mode. - TestValue* v2 = cache_->Lookup(0); - ASSERT_EQ(v2 == nullptr, !lru); - - // In either case all the other elements should now be gone. - for (int i = 1; i < kCacheSize; i++) ASSERT_TRUE(!in_cache[i]); - - // Clean up - bool cleaned_up = false; - if (v1 != nullptr) { - cache_->Release(0, v1); - cleaned_up = true; - } - if (v2 != nullptr) { - cache_->Release(0, v2); - cleaned_up = true; - } - if (cleaned_up) { - cache_->Remove(0); - } -} - -TEST_F(SimpleLRUCacheTest, ExpirationLRUShortHeldPins) { - TestExpiration(true /* lru */, true /* release_quickly */); -} -TEST_F(SimpleLRUCacheTest, ExpirationLRULongHeldPins) { - TestExpiration(true /* lru */, false /* release_quickly */); -} -TEST_F(SimpleLRUCacheTest, ExpirationAgeBasedShortHeldPins) { - TestExpiration(false /* lru */, true /* release_quickly */); -} -TEST_F(SimpleLRUCacheTest, ExpirationAgeBasedLongHeldPins) { - TestExpiration(false /* lru */, false /* release_quickly */); -} - -void SimpleLRUCacheTest::TestLargeExpiration(bool lru, double timeout) { - // Make sure that setting a large timeout doesn't result in overflow and - // cache entries expiring immediately. - cache_.reset(new TestCache(kCacheSize)); - if (lru) { - cache_->SetMaxIdleSeconds(timeout); - } else { - cache_->SetAgeBasedEviction(timeout); - } - for (int i = 0; i < kCacheSize; i++) { - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - for (int i = 0; i < kCacheSize; i++) { - TestCache::ScopedLookup lookup(cache_.get(), i); - ASSERT_TRUE(lookup.Found()) << "Entry " << i << " not found"; - } -} - -TEST_F(SimpleLRUCacheTest, InfiniteExpirationLRU) { - TestLargeExpiration(true /* lru */, std::numeric_limits::infinity()); -} - -TEST_F(SimpleLRUCacheTest, InfiniteExpirationAgeBased) { - TestLargeExpiration(false /* lru */, std::numeric_limits::infinity()); -} - -static double GetBoundaryTimeout() { - // Search for the smallest timeout value that will result in overflow when - // converted to an integral number of cycles. - const double seconds_to_cycles = SimpleCycleTimer::Frequency(); - double seconds = static_cast(std::numeric_limits::max()) / - seconds_to_cycles; - // Because of floating point rounding, we are not certain that the previous - // computation will result in precisely the right value. So, jitter the value - // until we know we found the correct value. First, look for a value that we - // know will not result in overflow. - while ((seconds * seconds_to_cycles) >= std::numeric_limits::max()) { - seconds = std::nextafter(seconds, -std::numeric_limits::infinity()); - } - // Now, look for the first value that will result in overflow. - while ((seconds * seconds_to_cycles) < std::numeric_limits::max()) { - seconds = std::nextafter(seconds, std::numeric_limits::infinity()); - } - return seconds; -} - -TEST_F(SimpleLRUCacheTest, LargeExpirationLRU) { - TestLargeExpiration(true /* lru */, GetBoundaryTimeout()); -} - -TEST_F(SimpleLRUCacheTest, LargeExpirationAgeBased) { - TestLargeExpiration(false /* lru */, GetBoundaryTimeout()); -} - -TEST_F(SimpleLRUCacheTest, UpdateSize) { - // Create a cache larger than kCacheSize, to give us some overhead to - // change the objects' sizes. We don't want an UpdateSize operation - // to force a GC and throw off our ASSERT_TRUE()s down below. - cache_.reset(new TestCache(kCacheSize * 2)); - for (int i = 0; i < kCacheSize; i++) { - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - ASSERT_EQ(cache_->Entries(), kCacheSize); - - // *** Check the basic operations *** - // We inserted kCacheSize items, each of size 1. - // So the total should be kCacheSize, with none deferred and none pinned. - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // Now lock a value -- total should be the same, but one should be pinned. - TestValue* found = cache_->Lookup(0); - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 1); - - // Now [try to] remove the locked value. - // This should leave zero pinned, but one deferred. - cache_->Remove(0); - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 1); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // Now release the locked value. Both the deferred and pinned should be - // zero, and the total size should be one less than the total before. - cache_->Release(0, found); - found = nullptr; - - ASSERT_EQ(cache_->Size(), (kCacheSize - 1)); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // *** Okay, math works. Now try changing the sizes in mid-stream. *** - - // Chane one item to have a size of two. The should bring the total - // back up to kCacheSize. - cache_->UpdateSize(1, nullptr, 2); - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // What if we pin a value, and then change its size? - - // Pin [2]; total is still kCacheSize, pinned is one -- just like before ... - found = cache_->Lookup(2); - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 1); - - // Update that item to be of size two ... - cache_->UpdateSize(2, found, 2); - - // ... and the total should be one greater, and pinned should be two. - ASSERT_EQ(cache_->Size(), (kCacheSize + 1)); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 2); - - // Okay, remove it; pinned should go to zero, Deferred should go to two. - cache_->Remove(2); - - ASSERT_EQ(cache_->Size(), (kCacheSize + 1)); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 2); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // Now, change it again. Let's change it back to size one-- - // the total should go back to kCacheSize, and Deferred should - // drop to one. - cache_->UpdateSize(2, found, 1); - - ASSERT_EQ(cache_->Size(), kCacheSize); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 1); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // Release it. Total should drop by one, Deferred goes to zero. - cache_->Release(2, found); - found = nullptr; - - ASSERT_EQ(cache_->Size(), (kCacheSize - 1)); - ASSERT_EQ(cache_->MaxSize(), kCacheSize * 2); - ASSERT_EQ(cache_->DeferredSize(), 0); - ASSERT_EQ(cache_->PinnedSize(), 0); - - // So far we've disposed of 2 entries. - ASSERT_EQ(cache_->Entries(), kCacheSize - 2); - - // Now blow the cache up from the inside: resize an entry to an enormous size. - // This will push everything out except the entry itself because it's pinned. - TestValue* v = new TestValue(0); - in_cache[0] = true; - cache_->InsertPinned(0, v, 1); - ASSERT_EQ(cache_->Entries(), kCacheSize - 1); - cache_->UpdateSize(0, v, kCacheSize * 3); - ASSERT_EQ(cache_->Entries(), 1); - ASSERT_EQ(cache_->Size(), kCacheSize * 3); - // The entry is disposed of as soon as it is released. - cache_->Release(0, v); - ASSERT_EQ(cache_->Entries(), 0); - ASSERT_EQ(cache_->Size(), 0); -} - -TEST_F(SimpleLRUCacheTest, DontUpdateEvictionOrder) { - cache_.reset(new TestCache(kCacheSize)); - int64_t original_start, original_end; - - SimpleLRUCacheOptions options; - options.set_update_eviction_order(false); - - // Fully populate the cache and keep track of the time range for this - // population. - original_start = SimpleCycleTimer::Now(); - TickClock(); - for (int i = 0; i < kCacheSize; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - cache_->Insert(i, new TestValue(i), 1); - in_cache[i] = true; - } - TickClock(); - original_end = SimpleCycleTimer::Now(); - - // At each step validate the current state of the cache and then insert - // a new element. - for (int step = 0; step < kElems - kCacheSize; ++step) { - // Look from end to beginning (the reverse the order of insertion). This - // makes sure nothing changes cache ordering. - for (int this_elem = kElems - 1; this_elem >= 0; this_elem--) { - if (!in_cache[this_elem]) { - ASSERT_EQ(-1, cache_->GetLastUseTime(this_elem)); - } else if (this_elem < kCacheSize) { - // All elements < kCacheSize were part of the original insertion. - ASSERT_GT(cache_->GetLastUseTime(this_elem), original_start); - ASSERT_LT(cache_->GetLastUseTime(this_elem), original_end); - } else { - // All elements >= kCacheSize are newer. - ASSERT_GT(cache_->GetLastUseTime(this_elem), original_end); - } - - TestValue* value = cache_->LookupWithOptions(this_elem, options); - TestCache::ScopedLookup scoped_lookup(cache_.get(), this_elem, options); - if (in_cache[this_elem]) { - ASSERT_TRUE(value != nullptr); - ASSERT_EQ(this_elem, value->label); - ASSERT_TRUE(scoped_lookup.value() != nullptr); - ASSERT_EQ(this_elem, scoped_lookup.value()->label); - cache_->ReleaseWithOptions(this_elem, value, options); - } else { - ASSERT_TRUE(value == nullptr); - ASSERT_TRUE(scoped_lookup.value() == nullptr); - } - } - - // Insert TestValue(kCacheSize + step) which should evict the TestValue with - // label step. - cache_->Insert(kCacheSize + step, new TestValue(kCacheSize + step), 1); - in_cache[kCacheSize + step] = true; - in_cache[step] = false; - } -} - -TEST_F(SimpleLRUCacheTest, ScopedLookup) { - cache_.reset(new TestCache(kElems)); - for (int i = 0; i < kElems; i++) { - ASSERT_TRUE(!cache_->Lookup(i)); - TestValue* v = new TestValue(i); - in_cache[i] = true; - cache_->Insert(i, v, 1); - } - ASSERT_EQ(cache_->PinnedSize(), 0); - { - typedef TestCache::ScopedLookup ScopedLookup; - // Test two successful lookups - ScopedLookup lookup1(cache_.get(), 1); - ASSERT_TRUE(lookup1.Found()); - ASSERT_EQ(cache_->PinnedSize(), 1); - - ScopedLookup lookup2(cache_.get(), 2); - ASSERT_TRUE(lookup2.Found()); - ASSERT_EQ(cache_->PinnedSize(), 2); - - // Test a lookup of an elem not in the cache. - ScopedLookup lookup3(cache_.get(), kElems + 1); - ASSERT_TRUE(!lookup3.Found()); - ASSERT_EQ(cache_->PinnedSize(), 2); - } - // Make sure the destructors released properly. - ASSERT_EQ(cache_->PinnedSize(), 0); -} - -TEST_F(SimpleLRUCacheTest, AgeOfLRUItemInMicroseconds) { - // Make sure empty cache returns zero. - cache_.reset(new TestCache(kElems)); - ASSERT_EQ(cache_->AgeOfLRUItemInMicroseconds(), 0); - - // Make sure non-empty cache doesn't return zero. - TestValue* v = new TestValue(1); - in_cache[1] = true; - cache_->Insert(1, v, 1); - TickClock(); // must let at least 1us go by - ASSERT_NE(cache_->AgeOfLRUItemInMicroseconds(), 0); - - // Make sure "oldest" ages as time goes by. - int64_t oldest = cache_->AgeOfLRUItemInMicroseconds(); - TickClock(); - ASSERT_GT(cache_->AgeOfLRUItemInMicroseconds(), oldest); - - // Make sure new addition doesn't count as "oldest". - oldest = cache_->AgeOfLRUItemInMicroseconds(); - TickClock(); - v = new TestValue(2); - in_cache[2] = true; - cache_->Insert(2, v, 1); - ASSERT_GT(cache_->AgeOfLRUItemInMicroseconds(), oldest); - - // Make sure removal of oldest drops to next oldest. - oldest = cache_->AgeOfLRUItemInMicroseconds(); - cache_->Remove(1); - ASSERT_LT(cache_->AgeOfLRUItemInMicroseconds(), oldest); - - // Make sure that empty cache one again returns zero. - cache_->Remove(2); - TickClock(); - ASSERT_EQ(cache_->AgeOfLRUItemInMicroseconds(), 0); -} - -TEST_F(SimpleLRUCacheTest, GetLastUseTime) { - cache_.reset(new TestCache(kElems)); - int64_t now, last; - - // Make sure nonexistent key returns -1 - ASSERT_EQ(cache_->GetLastUseTime(1), -1); - - // Make sure existent key returns something > last and < now - last = SimpleCycleTimer::Now(); - TickClock(); - in_cache[1] = true; - TestValue* v = new TestValue(1); - cache_->Insert(1, v, 1); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetLastUseTime(1), last); - ASSERT_LT(cache_->GetLastUseTime(1), now); - - // Make sure next element > stored time and < now - in_cache[2] = true; - v = new TestValue(2); - cache_->Insert(2, v, 1); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetLastUseTime(2), cache_->GetLastUseTime(1)); - ASSERT_LT(cache_->GetLastUseTime(2), now); - - // Make sure last use doesn't change after Lookup - last = cache_->GetLastUseTime(1); - v = cache_->Lookup(1); - ASSERT_EQ(cache_->GetLastUseTime(1), last); - - // Make sure last use changes after Release, and is > last use of 2 < now - TickClock(); - cache_->Release(1, v); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetLastUseTime(1), cache_->GetLastUseTime(2)); - ASSERT_LT(cache_->GetLastUseTime(1), now); - - // Make sure Insert updates last use, > last use of 1 < now - v = new TestValue(3); - cache_->Insert(2, v, 1); - in_cache[3] = true; - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetLastUseTime(2), cache_->GetLastUseTime(1)); - ASSERT_LT(cache_->GetLastUseTime(2), now); - - // Make sure iterator returns the same value as GetLastUseTime - for (TestCache::const_iterator it = cache_->begin(); it != cache_->end(); - ++it) { - ASSERT_EQ(it.last_use_time(), cache_->GetLastUseTime(it->first)); - } - - // Make sure after Remove returns -1 - cache_->Remove(2); - ASSERT_EQ(cache_->GetLastUseTime(2), -1); -} - -TEST_F(SimpleLRUCacheTest, GetInsertionTime) { - cache_.reset(new TestCache(kElems)); - int64_t now, last; - - cache_->SetAgeBasedEviction(-1); - - // Make sure nonexistent key returns -1 - ASSERT_EQ(cache_->GetInsertionTime(1), -1); - - // Make sure existent key returns something > last and < now - last = SimpleCycleTimer::Now(); - TickClock(); - in_cache[1] = true; - TestValue* v = new TestValue(1); - cache_->Insert(1, v, 1); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetInsertionTime(1), last); - ASSERT_LT(cache_->GetInsertionTime(1), now); - - // Make sure next element > time of el. 1 and < now - in_cache[2] = true; - v = new TestValue(2); - cache_->Insert(2, v, 1); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetInsertionTime(2), cache_->GetInsertionTime(1)); - ASSERT_LT(cache_->GetInsertionTime(2), now); - - // Make sure insertion time doesn't change after Lookup - last = cache_->GetInsertionTime(1); - v = cache_->Lookup(1); - ASSERT_EQ(cache_->GetInsertionTime(1), last); - - // Make sure insertion time doesn't change after Release - TickClock(); - cache_->Release(1, v); - ASSERT_EQ(cache_->GetInsertionTime(1), last); - - // Make sure Insert updates time, > insertion time of 2 < now - in_cache[3] = true; - v = new TestValue(3); - cache_->Insert(1, v, 1); - TickClock(); - now = SimpleCycleTimer::Now(); - ASSERT_GT(cache_->GetInsertionTime(1), cache_->GetInsertionTime(2)); - ASSERT_LT(cache_->GetInsertionTime(1), now); - - // Make sure iterator returns the same value as GetInsertionTime - for (TestCache::const_iterator it = cache_->begin(); it != cache_->end(); - ++it) { - ASSERT_EQ(it.insertion_time(), cache_->GetInsertionTime(it->first)); - } - - // Make sure after Remove returns -1 - cache_->Remove(2); - ASSERT_EQ(cache_->GetInsertionTime(2), -1); -} - -std::string StringPrintf(void* p, int pin, int defer) { - std::stringstream ss; - ss << std::hex << p << std::dec << ": pin: " << pin; - ss << ", is_deferred: " << defer; - return ss.str(); -} - -TEST_F(SimpleLRUCacheTest, DebugOutput) { - cache_.reset(new TestCache(kCacheSize, false /* check_in_cache */)); - TestValue* v1 = new TestValue(0); - cache_->InsertPinned(0, v1, 1); - TestValue* v2 = new TestValue(0); - cache_->InsertPinned(0, v2, 1); - TestValue* v3 = new TestValue(0); - cache_->Insert(0, v3, 1); - - std::string s; - cache_->DebugOutput(&s); - EXPECT_THAT(s, HasSubstr(StringPrintf(v1, 1, 1))); - EXPECT_THAT(s, HasSubstr(StringPrintf(v2, 1, 1))); - EXPECT_THAT(s, HasSubstr(StringPrintf(v3, 0, 0))); - - cache_->Release(0, v1); - cache_->Release(0, v2); -} - -TEST_F(SimpleLRUCacheTest, LookupWithoutEvictionOrderUpdateAndRemove) { - cache_.reset(new TestCache(kCacheSize, false /* check_in_cache */)); - - for (int i = 0; i < 3; ++i) { - cache_->Insert(i, new TestValue(0), 1); - } - - SimpleLRUCacheOptions no_update_options; - no_update_options.set_update_eviction_order(false); - TestValue* value = cache_->LookupWithOptions(1, no_update_options); - // Remove the second element before calling ReleaseWithOptions. Since we used - // update_eviction_order = false for the LookupWithOptions call the value was - // not removed from the LRU. Remove() is responsible for taking the value out - // of the LRU. - cache_->Remove(1); - // ReleaseWithOptions will now delete the pinned value. - cache_->ReleaseWithOptions(1, value, no_update_options); - - // When using ASan these lookups verify that the LRU has not been corrupted. - EXPECT_THAT(TestCache::ScopedLookup(cache_.get(), 0).value(), NotNull()); - EXPECT_THAT(TestCache::ScopedLookup(cache_.get(), 2).value(), NotNull()); -} - -} // namespace utils -} // namespace istio diff --git a/src/istio/utils/status.cc b/src/istio/utils/status.cc deleted file mode 100644 index b92f3554202..00000000000 --- a/src/istio/utils/status.cc +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "include/istio/utils/status.h" -#include "google/protobuf/stubs/status.h" - -using StatusCode = ::google::protobuf::util::error::Code; - -namespace istio { -namespace utils { - -// Convert Status::code to HTTP code -int StatusHttpCode(int code) { - // Map Canonical codes to HTTP status codes. This is based on the mapping - // defined by the protobuf http error space. - switch (code) { - case StatusCode::OK: - return 200; - case StatusCode::CANCELLED: - return 499; - case StatusCode::UNKNOWN: - return 500; - case StatusCode::INVALID_ARGUMENT: - return 400; - case StatusCode::DEADLINE_EXCEEDED: - return 504; - case StatusCode::NOT_FOUND: - return 404; - case StatusCode::ALREADY_EXISTS: - return 409; - case StatusCode::PERMISSION_DENIED: - return 403; - case StatusCode::RESOURCE_EXHAUSTED: - return 429; - case StatusCode::FAILED_PRECONDITION: - return 400; - case StatusCode::ABORTED: - return 409; - case StatusCode::OUT_OF_RANGE: - return 400; - case StatusCode::UNIMPLEMENTED: - return 501; - case StatusCode::INTERNAL: - return 500; - case StatusCode::UNAVAILABLE: - return 503; - case StatusCode::DATA_LOSS: - return 500; - case StatusCode::UNAUTHENTICATED: - return 401; - default: - return 500; - } -} - -} // namespace utils -} // namespace istio diff --git a/test/backend/echo/README.md b/test/backend/echo/README.md deleted file mode 100644 index 8df2854c5f3..00000000000 --- a/test/backend/echo/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Simple echo server for proxy testing. - -golang implementation of echo server. diff --git a/test/backend/echo/echo.go b/test/backend/echo/echo.go deleted file mode 100644 index 34802ea1074..00000000000 --- a/test/backend/echo/echo.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2016 Istio Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -// An example implementation of Echo backend in go. - -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "net/http" - "strconv" - "strings" -) - -var ( - port = flag.Int("port", 8080, "default http port") - - requests = 0 - data = 0 - lds_n = 0 -) - -// Test LDS update with command -// envoy -c envoy_lds.conf --service-cluster cluster --service-node node -const listener1 = `{ - "listeners": [ - { - "address": "tcp://0.0.0.0:9090", - "name": "server-http", - "bind_to_port": true, - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "generate_request_id": true, - "stat_prefix": "ingress_http", - "route_config": { - "virtual_hosts": [ - { - "name": "backend", - "domains": ["*"], - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "service1", - "opaque_config": { - "mixer_control": "on", - "mixer_forward": "off" - } - } - ] - } - ] - }, - "access_log": [ - { - "path": "/dev/stdout" - } - ], - "filters": [ - { - "type": "decoder", - "name": "mixer", - "config": { - "mixer_attributes": { - "target.uid": "POD222", - "target.service": "foo.svc.cluster.local" - }, - "random_string": "AAAAA", - "quota_name": "RequestCount", - "quota_amount": "1" - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ] - } - } - ] - } - ] -} -` - -func lds_handler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("content-type", "application/json") - w.WriteHeader(http.StatusOK) - - // Generates a new config to cause Envoy to update the listener - lds_n++ - w.Write([]byte(strings.Replace(listener1, "AAAAA", strconv.Itoa(lds_n), -1))) -} - -func handler(w http.ResponseWriter, r *http.Request) { - fmt.Printf("%v %v %v %v\n", r.Method, r.URL, r.Proto, r.RemoteAddr) - for name, headers := range r.Header { - for _, h := range headers { - fmt.Printf("%v: %v\n", name, h) - } - } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // echo back the Content-Type and Content-Length in the response - for _, k := range []string{"Content-Type", "Content-Length"} { - if v := r.Header.Get(k); v != "" { - w.Header().Set(k, v) - } - } - w.WriteHeader(http.StatusOK) - w.Write(body) - - requests++ - data += len(body) - fmt.Printf("Requests Requests: %v Data: %v\n", requests, data) -} - -func main() { - flag.Parse() - - fmt.Printf("Listening on port %v\n", *port) - - http.HandleFunc("/", handler) - http.HandleFunc("/v1/listeners/cluster/node", lds_handler) - http.ListenAndServe(":"+strconv.Itoa(*port), nil) -} diff --git a/test/envoye2e/basic_flow/basic_test.go b/test/envoye2e/basic_flow/basic_test.go new file mode 100644 index 00000000000..bf88cb7b3f1 --- /dev/null +++ b/test/envoye2e/basic_flow/basic_test.go @@ -0,0 +1,255 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "istio.io/proxy/test/envoye2e" + "istio.io/proxy/test/envoye2e/driver" +) + +var ProtocolOptions = []struct { + Name string + Quic bool +}{ + { + Name: "h2", + Quic: false, + }, + { + Name: "quic", + Quic: true, + }, +} + +func TestBasicTCPFlow(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "ConnectionCount": "10", + "DisableDirectResponse": "true", + }, envoye2e.ProxyE2ETests) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{driver.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{driver.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{driver.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{driver.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + &driver.Repeat{ + N: 10, + Step: &driver.TCPConnection{}, + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "envoy_tcp_downstream_cx_total": &driver.PartialStat{Metric: "testdata/metric/basic_flow_server_tcp_connection.yaml.tmpl"}, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "envoy_tcp_downstream_cx_total": &driver.PartialStat{Metric: "testdata/metric/basic_flow_client_tcp_connection.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestBasicHTTP(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + driver.Get(params.Ports.ClientPort, "hello, world!"), + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestBasicHTTPwithTLS(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ClientTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + driver.Get(params.Ports.ClientPort, "hello, world!"), + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +// Tests with a single combined proxy hosting inbound/outbound listeners +func TestBasicHTTPGateway(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "server", Version: "0", + Clusters: []string{driver.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{ + driver.LoadTestData("testdata/listener/client.yaml.tmpl"), + driver.LoadTestData("testdata/listener/server.yaml.tmpl"), + }, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + driver.Get(params.Ports.ClientPort, "hello, world!"), + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +var ConnectServer = &driver.Update{ + Node: "server", Version: "{{ .N }}", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_inbound.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/terminate_connect.yaml.tmpl"), + driver.LoadTestData("testdata/listener/server.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/server.yaml.tmpl"), + }, +} + +func TestBasicCONNECT(t *testing.T) { + for _, options := range ProtocolOptions { + t.Run(options.Name, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["quic"] = strconv.FormatBool(options.Quic) + params.Vars["EnableTunnelEndpointMetadata"] = "true" + params.Vars["EnableOriginalDstPortOverride"] = "true" + + updateClient := &driver.Update{ + Node: "client", Version: "{{ .N }}", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/client.yaml.tmpl"), + driver.LoadTestData("testdata/listener/internal_outbound.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + } + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + updateClient, ConnectServer, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + driver.Get(params.Ports.ClientPort, "hello, world!"), + // xDS load generator: + // &driver.Repeat{ + // Duration: time.Second * 20, + // Step: &driver.Scenario{ + // []driver.Step{ + // &driver.Sleep{10000 * time.Millisecond}, + // updateClient, updateServer, + // // may need short delay so we don't eat all the CPU + // }, + // }, + // }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestPassthroughCONNECT(t *testing.T) { + for _, options := range ProtocolOptions { + t.Run(options.Name, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["quic"] = strconv.FormatBool(options.Quic) + params.Vars["EnableOriginalDstPortOverride"] = "true" + + updateClient := &driver.Update{ + Node: "client", Version: "{{ .N }}", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/tcp_passthrough.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/client_passthrough.yaml.tmpl"), + driver.LoadTestData("testdata/listener/tcp_passthrough.yaml.tmpl"), + driver.LoadTestData("testdata/listener/internal_outbound.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + } + + req := driver.Get(params.Ports.ClientPort, "hello, world!") + req.Authority = fmt.Sprintf("127.0.0.2:%d", params.Ports.ServerPort) + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + updateClient, ConnectServer, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + req, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/test/envoye2e/driver/check.go b/test/envoye2e/driver/check.go new file mode 100644 index 00000000000..251dad56fad --- /dev/null +++ b/test/envoye2e/driver/check.go @@ -0,0 +1,141 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "fmt" + "io" + "log" + "net/http" + "net/http/httputil" + "time" +) + +const ( + DefaultTimeout = 10 * time.Second + None = "-" + Any = "*" +) + +// HTTPCall sends a HTTP request to a localhost port, and then check the response code, and response headers. +type HTTPCall struct { + // Method + Method string + // Authority override + Authority string + // URL path + Path string + // Port specifies the port in 127.0.0.1:PORT + Port uint16 + // Body is the expected body + Body string + // RequestHeaders to send with the request + RequestHeaders map[string]string + // ResponseCode to expect + ResponseCode int + // ResponseHeaders to expect + ResponseHeaders map[string]string + // Timeout (must be set to avoid the default) + Timeout time.Duration + // DisableRedirect prevents the client from following redirects and returns the original response. + DisableRedirect bool + // IP address override instead of 127.0.0.1 + IP string +} + +func Get(port uint16, body string) *HTTPCall { + return &HTTPCall{ + Method: http.MethodGet, + Port: port, + Body: body, + } +} + +func (g *HTTPCall) Run(p *Params) error { + ip := "127.0.0.1" + if g.IP != "" { + ip = g.IP + } + url := fmt.Sprintf("http://%s:%d%v", ip, g.Port, g.Path) + if g.Timeout == 0 { + g.Timeout = DefaultTimeout + } + req, err := http.NewRequest(g.Method, url, nil) + if err != nil { + return err + } + for key, val := range g.RequestHeaders { + header, err := p.Fill(val) + if err != nil { + panic(err) + } + req.Header.Add(key, header) + } + if len(g.Authority) > 0 { + req.Host = g.Authority + } + dump, _ := httputil.DumpRequest(req, false) + log.Printf("HTTP request:\n%s", string(dump)) + + client := &http.Client{Timeout: g.Timeout} + if g.DisableRedirect { + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + resp, err := client.Do(req) + if err != nil { + return err + } + code := resp.StatusCode + wantCode := 200 + if g.ResponseCode != 0 { + wantCode = g.ResponseCode + } + if code != wantCode { + return fmt.Errorf("error code for :%d: %d", g.Port, code) + } + + bodyBytes, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + body := string(bodyBytes) + if g.Body != "" && g.Body != body { + return fmt.Errorf("got body %q, want %q", body, g.Body) + } + + for key, val := range g.ResponseHeaders { + got := resp.Header.Get(key) + switch val { + case Any: + if got == "" { + return fmt.Errorf("got response header %q, want any", got) + } + case None: + if got != "" { + return fmt.Errorf("got response header %q, want none", got) + } + default: + if got != val { + return fmt.Errorf("got response header %q, want %q", got, val) + } + } + } + + return nil +} +func (g *HTTPCall) Cleanup() {} diff --git a/test/envoye2e/driver/envoy.go b/test/envoye2e/driver/envoy.go new file mode 100644 index 00000000000..1aa199698de --- /dev/null +++ b/test/envoye2e/driver/envoy.go @@ -0,0 +1,277 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "time" + + _ "github.com/cncf/xds/go/udpa/type/v1" // Preload proto definitions + bootstrapv3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/open_telemetry/v3" // Preload proto definitions + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/http_inspector/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/tls_inspector/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/stat_sinks/open_telemetry/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/internal_upstream/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/raw_buffer/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" + + "istio.io/proxy/test/envoye2e/env" +) + +// Envoy starts up a Envoy process locally. +type Envoy struct { + // template for the bootstrap + Bootstrap string + + // Istio proxy version to download. + // This could be either a patch version (x.y.z), or a minor version (x.y), or master. + // When minor version or master is provided, proxy binary will downloaded based on + // the latest proxy SHA that istio minor version branch points to. + // DownloadVersion will be ignored if proxy binary already exists at the + // default bazel-bin location, or ENVOY_PATH env var is set. + DownloadVersion string + + // standard error for the Envoy process (defaults to os.Stderr). + Stderr io.Writer + // standard out for the Envoy process (defaults to os.Stdout). + Stdout io.Writer + + // Value used to set the --concurrency flag when starting envoy. + Concurrency uint32 + + tmpFile string + cmd *exec.Cmd + adminPort uint32 + + done chan error +} + +var _ Step = &Envoy{} + +// Run starts a Envoy process. +func (e *Envoy) Run(p *Params) error { + bootstrap, err := p.Fill(e.Bootstrap) + if err != nil { + return err + } + log.Printf("envoy bootstrap:\n%s\n", bootstrap) + + var node string + e.adminPort, node, err = getAdminPortAndNode(bootstrap) + if err != nil { + return err + } + log.Printf("admin port %d", e.adminPort) + + tmp, err := os.CreateTemp(os.TempDir(), "envoy-bootstrap-*.yaml") + if err != nil { + return err + } + if _, err := tmp.Write([]byte(bootstrap)); err != nil { + return err + } + e.tmpFile = tmp.Name() + + debugLevel, ok := os.LookupEnv("ENVOY_DEBUG") + if !ok { + debugLevel = "info" + } + concurrency := "1" + if e.Concurrency > 1 { + concurrency = fmt.Sprint(e.Concurrency) + } + args := []string{ + "--log-format", "[" + node + ` %T.%e][%t][%l][%n] [%g:%#] %v`, + "-c", e.tmpFile, + "-l", debugLevel, + "--concurrency", concurrency, + "--disable-hot-restart", + "--drain-time-s", "4", // this affects how long draining listenrs are kept alive + } + envoyPath := filepath.Join(env.GetDefaultEnvoyBinOrDie(), "envoy") + if path, exists := os.LookupEnv("ENVOY_PATH"); exists { + envoyPath = path + } else if _, err := os.Stat(envoyPath); os.IsNotExist(err) && e.DownloadVersion != "" { + envoyPath, err = downloadEnvoy(e.DownloadVersion) + if err != nil { + return fmt.Errorf("failed to download Envoy binary %v", err) + } + } + stderr := io.Writer(os.Stderr) + if e.Stderr != nil { + stderr = e.Stderr + } + stdout := io.Writer(os.Stdout) + if e.Stdout != nil { + stdout = e.Stdout + } + cmd := exec.Command(envoyPath, args...) + cmd.Stderr = stderr + cmd.Stdout = stdout + cmd.Dir = BazelWorkspace() + + log.Printf("envoy cmd %v", cmd.Args) + e.cmd = cmd + if err = cmd.Start(); err != nil { + return err + } + e.done = make(chan error, 1) + go func() { + err := e.cmd.Wait() + if err != nil { + log.Printf("envoy process error: %v\n", err) + if strings.Contains(err.Error(), "segmentation fault") { + panic(err) + } + } + e.done <- err + }() + + url := fmt.Sprintf("http://127.0.0.1:%v/ready", e.adminPort) + return env.WaitForHTTPServer(url) +} + +// Cleanup stops the Envoy process. +func (e *Envoy) Cleanup() { + log.Printf("stop envoy ...\n") + defer os.Remove(e.tmpFile) + if e.cmd != nil { + url := fmt.Sprintf("http://127.0.0.1:%v/quitquitquit", e.adminPort) + _, _, _ = env.HTTPPost(url, "", "") + select { + case <-time.After(3 * time.Second): + log.Println("envoy killed as timeout reached") + log.Println(e.cmd.Process.Kill()) + case <-e.done: + log.Printf("stop envoy ... done\n") + } + } +} + +func getAdminPortAndNode(bootstrap string) (port uint32, node string, err error) { + pb := &bootstrapv3.Bootstrap{} + if err = ReadYAML(bootstrap, pb); err != nil { + return + } + if pb.Admin == nil || pb.Admin.Address == nil { + err = fmt.Errorf("missing admin section in bootstrap: %v", bootstrap) + return + } + socket, ok := pb.Admin.Address.Address.(*core.Address_SocketAddress) + if !ok { + err = fmt.Errorf("missing socket in bootstrap: %v", bootstrap) + return + } + portValue, ok := socket.SocketAddress.PortSpecifier.(*core.SocketAddress_PortValue) + if !ok { + err = fmt.Errorf("missing port in bootstrap: %v", bootstrap) + return + } + node = pb.Node.Id + port = portValue.PortValue + return +} + +// downloads env based on the given branch name. Return location of downloaded envoy. +func downloadEnvoy(ver string) (string, error) { + var proxyDepURL string + if regexp.MustCompile(`[0-9]+\.[0-9]+\.[0-9]+`).MatchString(ver) { + // this is a patch version string + proxyDepURL = fmt.Sprintf("https://raw.githubusercontent.com/istio/istio/%v/istio.deps", ver) + } else if regexp.MustCompile(`[0-9]+\.[0-9]+`).MatchString(ver) { + // this is a minor version string + proxyDepURL = fmt.Sprintf("https://raw.githubusercontent.com/istio/istio/release-%v/istio.deps", ver) + } else if ver == "master" { + proxyDepURL = "https://raw.githubusercontent.com/istio/istio/master/istio.deps" + } else { + return "", fmt.Errorf("envoy version %v is neither minor version nor patch version", ver) + } + resp, err := http.Get(proxyDepURL) + if err != nil { + return "", fmt.Errorf("cannot get envoy sha from %v: %v", proxyDepURL, err) + } + defer resp.Body.Close() + istioDeps, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("cannot read body of istio deps: %v", err) + } + + var deps []interface{} + if err := json.Unmarshal(istioDeps, &deps); err != nil { + return "", err + } + proxySHA := "" + for _, d := range deps { + if dm, ok := d.(map[string]interface{}); ok && dm["repoName"].(string) == "proxy" { + proxySHA = dm["lastStableSHA"].(string) + } + } + if proxySHA == "" { + return "", errors.New("cannot identify proxy SHA to download") + } + + dir := fmt.Sprintf("%s/%s", os.TempDir(), "istio-proxy") + dst := fmt.Sprintf("%v/envoy-%v", dir, proxySHA) + if _, err := os.Stat(dst); err == nil { + // If the desired envoy binary is already downloaded, skip downloading and return. + return dst, nil + } + + // clean up the tmp dir before downloading to remove stale envoy binary. + _ = os.RemoveAll(dir) + + // make temp directory to put downloaded envoy binary. + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", err + } + + envoyURL := fmt.Sprintf("https://storage.googleapis.com/istio-build/proxy/envoy-alpha-%v.tar.gz", proxySHA) + downloadCmd := exec.Command("bash", "-c", fmt.Sprintf("curl -fLSs %v | tar xz", envoyURL)) + downloadCmd.Stderr = os.Stderr + downloadCmd.Stdout = os.Stdout + err = downloadCmd.Run() + if err != nil { + return "", fmt.Errorf("fail to run envoy download command: %v", err) + } + src := "usr/local/bin/envoy" + if _, err := os.Stat(src); err != nil { + return "", fmt.Errorf("fail to find downloaded envoy: %v", err) + } + defer os.RemoveAll("usr/") + + cpCmd := exec.Command("cp", "-n", src, dst) + cpCmd.Stderr = os.Stderr + cpCmd.Stdout = os.Stdout + if err := cpCmd.Run(); err != nil { + return "", fmt.Errorf("fail to copy envoy binary from %v to %v: %v", src, dst, err) + } + + return dst, nil +} diff --git a/test/envoye2e/driver/extensionserver.go b/test/envoye2e/driver/extensionserver.go new file mode 100644 index 00000000000..7c9dfeb2200 --- /dev/null +++ b/test/envoye2e/driver/extensionserver.go @@ -0,0 +1,67 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "context" + + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + extensionservice "github.com/envoyproxy/go-control-plane/envoy/service/extension/v3" + "github.com/envoyproxy/go-control-plane/pkg/cache/v3" + "github.com/envoyproxy/go-control-plane/pkg/server/v3" +) + +const ( + // APIType for extension configs. + APIType = "type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig" +) + +// ExtensionServer is the main server instance. +type ExtensionServer struct { + server.Server + server.CallbackFuncs + cache *cache.LinearCache +} + +var _ extensionservice.ExtensionConfigDiscoveryServiceServer = &ExtensionServer{} + +func NewExtensionServer(ctx context.Context) *ExtensionServer { + out := &ExtensionServer{} + out.cache = cache.NewLinearCache(APIType) + out.Server = server.NewServer(ctx, out.cache, out) + return out +} + +func (es *ExtensionServer) StreamExtensionConfigs(stream extensionservice.ExtensionConfigDiscoveryService_StreamExtensionConfigsServer) error { + return es.Server.StreamHandler(stream, APIType) +} + +func (es *ExtensionServer) DeltaExtensionConfigs(stream extensionservice.ExtensionConfigDiscoveryService_DeltaExtensionConfigsServer) error { + return es.Server.DeltaStreamHandler(stream, APIType) +} + +func (es *ExtensionServer) FetchExtensionConfigs(ctx context.Context, req *discovery.DiscoveryRequest) (*discovery.DiscoveryResponse, error) { + req.TypeUrl = APIType + return es.Server.Fetch(ctx, req) +} + +func (es *ExtensionServer) Update(config *core.TypedExtensionConfig) error { + return es.cache.UpdateResource(config.Name, config) +} + +func (es *ExtensionServer) Delete(name string) error { + return es.cache.DeleteResource(name) +} diff --git a/test/envoye2e/driver/grpc.go b/test/envoye2e/driver/grpc.go new file mode 100644 index 00000000000..ee85bcec536 --- /dev/null +++ b/test/envoye2e/driver/grpc.go @@ -0,0 +1,126 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "context" + "fmt" + "log" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + "istio.io/proxy/test/envoye2e/env" + "istio.io/proxy/test/envoye2e/env/grpc_echo" +) + +type GrpcServer struct { + grpcBackend *env.GRPCServer +} + +var _ Step = &GrpcServer{} + +func (g *GrpcServer) Run(p *Params) error { + g.grpcBackend = env.NewGRPCServer(p.Ports.BackendPort) + log.Printf("Starting GRPC echo server") + errCh := g.grpcBackend.Start() + if err := <-errCh; err != nil { + return fmt.Errorf("not able to start GRPC server: %v", err) + } + return nil +} + +func (g *GrpcServer) Cleanup() { + g.grpcBackend.Stop() +} + +var _ Step = &GrpcCall{} + +type GrpcCall struct { + ReqCount int + WantStatus *status.Status +} + +func (g *GrpcCall) Run(p *Params) error { + proxyAddr := fmt.Sprintf("127.0.0.1:%d", p.Ports.ClientPort) + conn, err := grpc.Dial(proxyAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) + if err != nil { + return fmt.Errorf("could not establish client connection to gRPC server: %v", err) + } + defer conn.Close() + client := grpc_echo.NewEchoClient(conn) + + for i := 0; i < g.ReqCount; i++ { + _, grpcErr := client.Echo(context.Background(), &grpc_echo.EchoRequest{ReturnStatus: g.WantStatus.Proto()}) + fromErr, ok := status.FromError(grpcErr) + if ok && fromErr.Code() != g.WantStatus.Code() { + return fmt.Errorf("failed GRPC call: %#v (code: %v)", grpcErr, fromErr.Code()) + } + fmt.Printf("successfully called GRPC server and get status code %+v\n", fromErr) + } + return nil +} + +func (g *GrpcCall) Cleanup() {} + +var _ Step = &GrpcStream{} + +type GrpcStream struct { + conn *grpc.ClientConn + stream grpc_echo.Echo_EchoStreamClient +} + +func (g *GrpcStream) Run(p *Params) error { + proxyAddr := fmt.Sprintf("127.0.0.1:%d", p.Ports.ClientPort) + var err error + g.conn, err = grpc.Dial(proxyAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) + if err != nil { + return fmt.Errorf("could not establish client connection to gRPC server: %v", err) + } + g.stream, err = grpc_echo.NewEchoClient(g.conn).EchoStream(context.Background()) + if err != nil { + return err + } + return nil +} + +func (g *GrpcStream) Send(counts []uint32) Step { + return StepFunction(func(p *Params) error { + for i := 0; i < len(counts); i++ { + count := counts[i] + fmt.Printf("requesting %v messages at %v stream message\n", count, i) + err := g.stream.Send(&grpc_echo.StreamRequest{ResponseCount: count}) + if err != nil { + return err + } + for j := 0; j < int(count); j++ { + _, err = g.stream.Recv() + if err != nil { + return err + } + } + } + return nil + }) +} + +func (g *GrpcStream) Close() Step { + return StepFunction(func(p *Params) error { + return g.conn.Close() + }) +} + +func (g *GrpcStream) Cleanup() {} diff --git a/test/envoye2e/driver/otel.go b/test/envoye2e/driver/otel.go new file mode 100644 index 00000000000..9847b3cf205 --- /dev/null +++ b/test/envoye2e/driver/otel.go @@ -0,0 +1,147 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "context" + "fmt" + "log" + "net" + "time" + + "github.com/google/go-cmp/cmp" + collogspb "go.opentelemetry.io/proto/otlp/collector/logs/v1" + colmetricspb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" + metricspb "go.opentelemetry.io/proto/otlp/metrics/v1" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/testing/protocmp" +) + +type OtelLogs struct { + collogspb.UnimplementedLogsServiceServer +} + +type OtelMetrics struct { + colmetricspb.UnimplementedMetricsServiceServer + finished bool + verify verify +} + +type verify struct { + want []*metricspb.Metric + done chan struct{} +} + +type Otel struct { + OtelLogs + OtelMetrics + grpc *grpc.Server + Port uint16 + // Metrics contains the expected (total) metric protos. Clients must produce them exactly. + Metrics []string +} + +func (x *OtelLogs) Export(ctx context.Context, req *collogspb.ExportLogsServiceRequest) (*collogspb.ExportLogsServiceResponse, error) { + return &collogspb.ExportLogsServiceResponse{}, nil +} + +func (x *OtelMetrics) Export(ctx context.Context, req *colmetricspb.ExportMetricsServiceRequest) (*colmetricspb.ExportMetricsServiceResponse, error) { + if x.finished { + return &colmetricspb.ExportMetricsServiceResponse{}, nil + } + for _, rm := range req.ResourceMetrics { + if rm.Resource != nil { + log.Printf("resource=%s\n", protojson.Format(rm.Resource)) + } + for _, sm := range rm.ScopeMetrics { + if sm.Scope != nil { + log.Printf("scope=%s\n", protojson.Format(sm.Scope)) + } + for _, m := range sm.Metrics { + // Clean up time field in the received metric. + if sum := m.GetSum(); sum != nil { + for _, dp := range sum.DataPoints { + dp.TimeUnixNano = 0 + } + } + diff := "" + for i, want := range x.verify.want { + if want.Name != m.Name { + continue + } + if diff = cmp.Diff(m, want, protocmp.Transform()); diff == "" { + x.verify.want = append(x.verify.want[:i], x.verify.want[i+1:]...) + log.Printf("matched metric %q, want to match %d\n", m.Name, len(x.verify.want)) + break + } + } + if diff != "" { + log.Printf("Failed to match a metric, last diff: %v", diff) + } + } + } + } + if len(x.verify.want) == 0 { + x.finished = true + close(x.verify.done) + } + return &colmetricspb.ExportMetricsServiceResponse{}, nil +} + +func (x *Otel) Wait() Step { + return &x.verify +} + +func (v *verify) Run(p *Params) error { + d := 30 * time.Second + select { + case <-v.done: + return nil + case <-time.After(d): + return fmt.Errorf("timed out waiting for all metrics to match") + } +} + +func (v *verify) Cleanup() { +} + +var _ Step = &Otel{} + +func (x *Otel) Run(p *Params) error { + log.Printf("Otel server starting on %d\n", x.Port) + x.grpc = grpc.NewServer() + collogspb.RegisterLogsServiceServer(x.grpc, &x.OtelLogs) + colmetricspb.RegisterMetricsServiceServer(x.grpc, &x.OtelMetrics) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", x.Port)) + if err != nil { + return err + } + x.verify.done = make(chan struct{}) + for _, m := range x.Metrics { + mpb := &metricspb.Metric{} + p.LoadTestProto(m, mpb) + x.verify.want = append(x.verify.want, mpb) + } + go func() { + _ = x.grpc.Serve(lis) + }() + return nil +} + +func (x *Otel) Cleanup() { + log.Println("stopping Otel server") + x.grpc.GracefulStop() +} diff --git a/test/envoye2e/driver/resource.go b/test/envoye2e/driver/resource.go new file mode 100644 index 00000000000..926bb480d14 --- /dev/null +++ b/test/envoye2e/driver/resource.go @@ -0,0 +1,90 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + + // nolint: staticcheck + legacyproto "github.com/golang/protobuf/proto" + "sigs.k8s.io/yaml" +) + +// Loads resources in the test data directory +// Functions here panic since test artifacts are usually loaded prior to execution, +// so there is no clean-up necessary. + +func BazelWorkspace() string { + workspace, err := exec.Command("bazel", "info", "workspace").Output() + if err != nil { + panic(err) + } + return strings.TrimSuffix(string(workspace), "\n") +} + +// Normalizes test data path +func TestPath(testFileName string) string { + return filepath.Join(BazelWorkspace(), testFileName) +} + +// Loads a test file content +func LoadTestData(testFileName string) string { + data, err := os.ReadFile(TestPath(testFileName)) + if err != nil { + panic(err) + } + return string(data) +} + +// Load a YAML and converts to JSON +func LoadTestJSON(testFileName string) string { + data := LoadTestData(testFileName) + js, err := yaml.YAMLToJSON([]byte(data)) + if err != nil { + panic(err) + } + return string(js) +} + +// Loads a test file and fills in template variables +func (p *Params) LoadTestData(testFileName string) string { + data := LoadTestData(testFileName) + out, err := p.Fill(data) + if err != nil { + panic(err) + } + return out +} + +// Fills in template variables in the given template data +func (p *Params) FillTestData(data string) string { + out, err := p.Fill(data) + if err != nil { + panic(err) + } + return out +} + +// Loads a test file as YAML into a proto and fills in template variables +func (p *Params) LoadTestProto(testFileName string, msg legacyproto.Message) legacyproto.Message { + data := LoadTestData(testFileName) + if err := p.FillYAML(data, msg); err != nil { + panic(err) + } + return msg +} diff --git a/test/envoye2e/driver/scenario.go b/test/envoye2e/driver/scenario.go new file mode 100644 index 00000000000..a81860835c4 --- /dev/null +++ b/test/envoye2e/driver/scenario.go @@ -0,0 +1,243 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "bytes" + "fmt" + "log" + "reflect" + "strings" + "testing" + "text/template" + "time" + + "github.com/golang/protobuf/jsonpb" // nolint: depguard // We need the deprecated module since the jsonpb replacement is not backwards compatible. + // nolint: staticcheck + legacyproto "github.com/golang/protobuf/proto" + yamlv2 "gopkg.in/yaml.v2" + "sigs.k8s.io/yaml" + + "istio.io/proxy/test/envoye2e/env" +) + +type ( + // Params include test context that is shared by all steps. + Params struct { + // Config is the XDS server state. + Config XDSServer + + // Ports record the port assignment for a test. + Ports *env.Ports + + // Vars include the variables which are used to fill in configuration template files. + Vars map[string]string + + // N records the index of repetition. It is only valid when using with Repeat step. + N int + } + + // Step is a unit of execution in the integration test. + Step interface { + // Run wraps the logic of a test step. + Run(*Params) error + + // Cleanup cleans up all the test artifacts created by Run. + Cleanup() + } + + // Scenario is a collection of Steps. It runs and cleans up all steps sequentially. + Scenario struct { + // Steps is a collection of steps which be executed sequentially. + Steps []Step + } + + // Repeat a step either for N number or duration + Repeat struct { + N int + Duration time.Duration + Step Step + } + + // Sleep injects a sleep with the given duration into the test execution. + Sleep struct { + time.Duration + } + + // Fork will copy params to avoid concurrent access + Fork struct { + Fore Step + Back Step + } + + // StepFunction models Lambda captured step + StepFunction func(p *Params) error +) + +// NewTestParams creates a new test params struct which keeps state of a test. vars will be used for template filling. +// A set of ports will be assigned. If TestInventory is provided, the port assignment will be offsetted based on the index of the test in the inventory. +func NewTestParams(t *testing.T, vars map[string]string, inv *env.TestInventory) *Params { + ind := inv.GetTestIndex(t) + ports := env.NewPorts(ind) + return &Params{ + Vars: vars, + Ports: ports, + } +} + +var _ Step = &Repeat{} + +func (r *Repeat) Run(p *Params) error { + if r.Duration != 0 { + start := time.Now() + p.N = 0 + for { + if time.Since(start) >= r.Duration { + break + } + log.Printf("repeat %d elapsed %v out of %v", p.N, time.Since(start), r.Duration) + if err := r.Step.Run(p); err != nil { + return err + } + p.N++ + } + } else { + for i := 0; i < r.N; i++ { + log.Printf("repeat %d out of %d", i, r.N) + p.N = i + if err := r.Step.Run(p); err != nil { + return err + } + } + } + return nil +} +func (r *Repeat) Cleanup() {} + +var _ Step = &Sleep{} + +func (s *Sleep) Run(_ *Params) error { + log.Printf("sleeping %v\n", s.Duration) + time.Sleep(s.Duration) + return nil +} +func (s *Sleep) Cleanup() {} + +// Fill a template file with ariable map in Params. +func (p *Params) Fill(s string) (string, error) { + t := template.Must(template.New("params"). + Option("missingkey=zero"). + Funcs(template.FuncMap{ + "indent": func(n int, s string) string { + pad := strings.Repeat(" ", n) + return pad + strings.Replace(s, "\n", "\n"+pad, -1) + }, + "fill": func(s string) string { + out, err := p.Fill(s) + if err != nil { + panic(err) + } + return out + }, + "divisible": func(i, j int) bool { + return i%j == 0 + }, + }). + Parse(s)) + var b bytes.Buffer + if err := t.Execute(&b, p); err != nil { + return "", err + } + return b.String(), nil +} + +var _ Step = &Fork{} + +func (f *Fork) Run(p *Params) error { + done := make(chan error, 1) + go func() { + p2 := *p + done <- f.Back.Run(&p2) + }() + + if err := f.Fore.Run(p); err != nil { + return err + } + + return <-done +} +func (f *Fork) Cleanup() {} + +func (s StepFunction) Run(p *Params) error { + return s(p) +} + +func (s StepFunction) Cleanup() {} + +var _ Step = &Scenario{} + +func (s *Scenario) Run(p *Params) error { + passed := make([]Step, 0, len(s.Steps)) + defer func() { + for i := range passed { + passed[len(passed)-1-i].Cleanup() + } + }() + for _, step := range s.Steps { + if err := step.Run(p); err != nil { + return err + } + passed = append(passed, step) + } + return nil +} + +func (s *Scenario) Cleanup() {} + +func ReadYAML(input string, pb legacyproto.Message) error { + var jsonObj interface{} + err := yamlv2.Unmarshal([]byte(input), &jsonObj) + if err != nil { + return fmt.Errorf("cannot parse: %v for %q", err, input) + } + // As a special case, convert [x] to x. + // This is needed because jsonpb is unable to parse arrays. + in := reflect.ValueOf(jsonObj) + switch in.Kind() { + case reflect.Slice, reflect.Array: + if in.Len() == 1 { + jsonObj = in.Index(0).Interface() + } + } + yml, err := yamlv2.Marshal(jsonObj) + if err != nil { + return fmt.Errorf("cannot marshal: %v for %q", err, input) + } + js, err := yaml.YAMLToJSON(yml) + if err != nil { + return fmt.Errorf("cannot unmarshal: %v for %q", err, input) + } + reader := strings.NewReader(string(js)) + m := jsonpb.Unmarshaler{} + return m.Unmarshal(reader, pb) +} + +func (p *Params) FillYAML(input string, pb legacyproto.Message) error { + out, err := p.Fill(input) + if err != nil { + return err + } + return ReadYAML(out, pb) +} diff --git a/test/envoye2e/driver/stats.go b/test/envoye2e/driver/stats.go new file mode 100644 index 00000000000..0b6110d59a8 --- /dev/null +++ b/test/envoye2e/driver/stats.go @@ -0,0 +1,163 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" + "google.golang.org/protobuf/testing/protocmp" + + "istio.io/proxy/test/envoye2e/env" +) + +type Stats struct { + AdminPort uint16 + Matchers map[string]StatMatcher +} + +type StatMatcher interface { + Matches(*Params, *dto.MetricFamily) error +} + +var _ Step = &Stats{} + +func (s *Stats) Run(p *Params) error { + var metrics map[string]*dto.MetricFamily + for i := 0; i < 15; i++ { + _, body, err := env.HTTPGet(fmt.Sprintf("http://127.0.0.1:%d/stats/prometheus", s.AdminPort)) + if err != nil { + return err + } + reader := strings.NewReader(body) + metrics, err = (&expfmt.TextParser{}).TextToMetricFamilies(reader) + if err != nil { + return err + } + count := 0 + for _, metric := range metrics { + matcher, found := s.Matchers[metric.GetName()] + if !found { + continue + } + if err = matcher.Matches(p, metric); err == nil { + log.Printf("matched metric %q", metric.GetName()) + count++ + continue + } else if _, ok := matcher.(*MissingStat); ok { + return fmt.Errorf("found metric that should have been missing: %s", metric.GetName()) + } + log.Printf("metric %q did not match: %v\n", metric.GetName(), err) + } + if count == len(s.Matchers) { + return nil + } + missingCount := 0 + for _, m := range s.Matchers { + if _, ok := m.(*MissingStat); ok { + missingCount++ + } + } + if count+missingCount == len(s.Matchers) { + return nil + } + time.Sleep(1 * time.Second) + } + return fmt.Errorf("failed to match all metrics: want %v, but got %v", s.Matchers, metrics) +} + +func (s *Stats) Cleanup() {} + +type ExactStat struct { + Metric string +} + +func (me *ExactStat) Matches(params *Params, that *dto.MetricFamily) error { + metric := &dto.MetricFamily{} + params.LoadTestProto(me.Metric, metric) + + if diff := cmp.Diff(metric, that, protocmp.Transform()); diff != "" { + return fmt.Errorf("diff: %v, got: %v, want: %v", diff, that, metric) + } + return nil +} + +var _ StatMatcher = &ExactStat{} + +// ExistStat matches if the metric exists in the output, +// but does not compare the Counter. +type ExistStat struct { + Metric string +} + +func (me *ExistStat) Matches(params *Params, that *dto.MetricFamily) error { + metric := &dto.MetricFamily{} + params.LoadTestProto(me.Metric, metric) + + switch metric.Type { + case dto.MetricType_COUNTER.Enum(): + if diff := cmp.Diff(metric, that, protocmp.Transform(), cmpopts.IgnoreFields(dto.Counter{}, "value")); diff != "" { + return fmt.Errorf("diff: %v, got: %v, want: %v", diff, that, metric) + } + } + + return nil +} + +var _ StatMatcher = &ExistStat{} + +type PartialStat struct { + Metric string +} + +func (me *PartialStat) Matches(params *Params, that *dto.MetricFamily) error { + metric := &dto.MetricFamily{} + params.LoadTestProto(me.Metric, metric) + for _, wm := range metric.Metric { + found := false + for _, gm := range that.Metric { + if diff := cmp.Diff(wm, gm, protocmp.Transform()); diff != "" { + continue + } + found = true + break + } + if !found { + return fmt.Errorf("cannot find metric, got: %v, want: %v", that.Metric, wm) + } + } + return nil +} + +var _ StatMatcher = &PartialStat{} + +type MissingStat struct { + Metric string +} + +func (m *MissingStat) Matches(_ *Params, that *dto.MetricFamily) error { + log.Printf("names: %s", *that.Name) + + if strings.EqualFold(m.Metric, *that.Name) { + return fmt.Errorf("found metric that should be missing: %s", m.Metric) + } + return nil +} diff --git a/test/envoye2e/driver/tcp.go b/test/envoye2e/driver/tcp.go new file mode 100644 index 00000000000..c8405aa65c6 --- /dev/null +++ b/test/envoye2e/driver/tcp.go @@ -0,0 +1,251 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "bufio" + "errors" + "fmt" + "io" + "log" + "net" + "strings" + "time" +) + +type TCPServer struct { + lis net.Listener + Prefix string +} + +var _ Step = &TCPServer{} + +func (t *TCPServer) Run(p *Params) error { + var err error + t.lis, err = net.Listen("tcp", fmt.Sprintf("127.0.0.3:%d", p.Ports.BackendPort)) + if err != nil { + return fmt.Errorf("failed to listen on %v", err) + } + go t.serve() + if err = waitForTCPServer(p.Ports.BackendPort); err != nil { + return err + } + return nil +} + +func (t *TCPServer) Cleanup() { + t.lis.Close() +} + +func (t *TCPServer) serve() { + for { + conn, err := t.lis.Accept() + if err != nil { + return + } + + // pass an accepted connection to a handler goroutine + go handleConnection(conn, t.Prefix) + } +} + +func waitForTCPServer(port uint16) error { + for i := 0; i < 30; i++ { + var conn net.Conn + var err error + conn, err = net.Dial("tcp", fmt.Sprintf("127.0.0.3:%d", port)) + if err != nil { + log.Println("Will wait 200ms and try again.") + time.Sleep(200 * time.Millisecond) + continue + } + // send to socket + fmt.Fprintf(conn, "ping\n") + // listen for reply + message, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + log.Println("Will wait 200ms and try again.") + time.Sleep(200 * time.Millisecond) + continue + } + fmt.Print("Message from server: " + message) + return nil + } + return errors.New("timeout waiting for TCP server to be ready") +} + +func handleConnection(conn net.Conn, prefix string) { + defer conn.Close() + reader := bufio.NewReader(conn) + for { + // read client request data + bytes, err := reader.ReadString('\n') + if err != nil { + if err != io.EOF { + log.Println("failed to read data, err:", err) + } + return + } + log.Printf("request: %s", bytes) + + // prepend prefix and send as response + line := fmt.Sprintf("%s %s", prefix, bytes) + log.Printf("response: %s", line) + _, _ = conn.Write([]byte(line)) + } +} + +type TCPConnection struct{} + +var _ Step = &TCPConnection{} + +func (t *TCPConnection) Run(p *Params) error { + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.Ports.ClientPort)) + if err != nil { + return fmt.Errorf("failed to connect to tcp server: %v", err) + } + defer conn.Close() + // send to socket + fmt.Fprintf(conn, "world"+"\n") + // listen for reply + message, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + return fmt.Errorf("failed to read bytes from conn %v", err) + } + wantMessage := "hello world\n" + if message != wantMessage { + return fmt.Errorf("received bytes got %v want %v", message, wantMessage) + } + return nil +} + +func (t *TCPConnection) Cleanup() {} + +// TCPServerAcceptAndClose implements a TCP server +// which accepts the data and then closes the connection +// immediately without any response. +// +// The exception from this description is the "ping" data +// which is handled differently for checking if the server +// is already up. +type TCPServerAcceptAndClose struct { + lis net.Listener +} + +var _ Step = &TCPServerAcceptAndClose{} + +func (t *TCPServerAcceptAndClose) Run(p *Params) error { + var err error + t.lis, err = net.Listen("tcp", fmt.Sprintf(":%d", p.Ports.BackendPort)) + if err != nil { + return fmt.Errorf("failed to listen on %v", err) + } + go t.serve() + if err = waitForTCPServer(p.Ports.BackendPort); err != nil { + return err + } + return nil +} + +func (t *TCPServerAcceptAndClose) Cleanup() { + t.lis.Close() +} + +func (t *TCPServerAcceptAndClose) serve() { + for { + conn, err := t.lis.Accept() + if err != nil { + return + } + + go t.handleConnection(conn) + } +} + +func (t *TCPServerAcceptAndClose) handleConnection(conn net.Conn) { + defer conn.Close() + reader := bufio.NewReader(conn) + bytes, err := reader.ReadString('\n') + if err != nil { + if err != io.EOF { + log.Println("failed to read data, err:", err) + } + return + } + bytes = strings.TrimSpace(bytes) + if strings.HasSuffix(bytes, "ping") { + log.Println("pinged - the TCP Server is available") + _, _ = conn.Write([]byte("alive\n")) + } + log.Println("received data. Closing the connection") +} + +// InterceptedTCPConnection is a connection which expects +// the terminated connection (before the timeout occurs) +type InterceptedTCPConnection struct { + ReadTimeout time.Duration +} + +var _ Step = &InterceptedTCPConnection{} + +func (t *InterceptedTCPConnection) Run(p *Params) error { + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.Ports.ClientPort)) + if err != nil { + return fmt.Errorf("failed to connect to tcp server: %v", err) + } + defer conn.Close() + + fmt.Fprintf(conn, "some data"+"\n") + err = conn.SetReadDeadline(time.Now().Add(t.ReadTimeout)) + if err != nil { + return fmt.Errorf("failed to set read deadline: %v", err) + } + + _, err = bufio.NewReader(conn).ReadString('\n') + if err != io.EOF { + return errors.New("the connection should be terminated") + } + return nil +} + +func (t *InterceptedTCPConnection) Cleanup() {} + +type TCPLoad struct { + conn *net.Conn +} + +var _ Step = &TCPLoad{} + +func (t *TCPLoad) Run(p *Params) error { + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.Ports.ClientPort)) + if err != nil { + return fmt.Errorf("failed to connect to tcp server: %v", err) + } + t.conn = &conn + + go func() { + for { + fmt.Fprintf(conn, "ping\n") + time.Sleep(1 * time.Second) + } + }() + return nil +} + +func (t *TCPLoad) Cleanup() { + if t.conn != nil { + (*t.conn).Close() + } +} diff --git a/test/envoye2e/driver/xds.go b/test/envoye2e/driver/xds.go new file mode 100644 index 00000000000..05f3d4f3bc2 --- /dev/null +++ b/test/envoye2e/driver/xds.go @@ -0,0 +1,240 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package driver + +import ( + "context" + "fmt" + "log" + "net" + "net/netip" + + clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + extensionservice "github.com/envoyproxy/go-control-plane/envoy/service/extension/v3" + secret "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" + "github.com/envoyproxy/go-control-plane/pkg/cache/types" + "github.com/envoyproxy/go-control-plane/pkg/cache/v3" + "github.com/envoyproxy/go-control-plane/pkg/server/v3" + "google.golang.org/grpc" + + "istio.io/proxy/test/envoye2e/workloadapi" +) + +// XDS creates an xDS server +type XDS struct { + grpc *grpc.Server +} + +// XDSServer is a struct holding xDS state. +type XDSServer struct { + cache.MuxCache + Extensions *ExtensionServer + Cache cache.SnapshotCache + Workloads *cache.LinearCache +} + +var _ Step = &XDS{} + +const WorkloadTypeURL = "type.googleapis.com/istio.workload.Workload" + +// Run starts up an Envoy XDS server. +func (x *XDS) Run(p *Params) error { + log.Printf("XDS server starting on %d\n", p.Ports.XDSPort) + x.grpc = grpc.NewServer() + p.Config.Extensions = NewExtensionServer(context.Background()) + extensionservice.RegisterExtensionConfigDiscoveryServiceServer(x.grpc, p.Config.Extensions) + + // Register caches. + p.Config.Cache = cache.NewSnapshotCache(false, cache.IDHash{}, x) + p.Config.Workloads = cache.NewLinearCache(WorkloadTypeURL, + cache.WithLogger(x)) + + p.Config.Caches = map[string]cache.Cache{ + "default": p.Config.Cache, + "workloads": p.Config.Workloads, + } + p.Config.Classify = func(r *cache.Request) string { + if r.TypeUrl == WorkloadTypeURL { + return "workloads" + } + return "default" + } + p.Config.ClassifyDelta = func(r *cache.DeltaRequest) string { + if r.TypeUrl == WorkloadTypeURL { + return "workloads" + } + return "default" + } + + xdsServer := server.NewServer(context.Background(), &p.Config, nil) + discovery.RegisterAggregatedDiscoveryServiceServer(x.grpc, xdsServer) + secret.RegisterSecretDiscoveryServiceServer(x.grpc, xdsServer) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", p.Ports.XDSPort)) + if err != nil { + return err + } + + go func() { + _ = x.grpc.Serve(lis) + }() + return nil +} + +type NamedWorkload struct { + *workloadapi.Workload +} + +func (nw *NamedWorkload) GetName() string { + return nw.Uid +} + +var _ types.ResourceWithName = &NamedWorkload{} + +// Cleanup stops the XDS server. +func (x *XDS) Cleanup() { + log.Println("stopping XDS server") + x.grpc.GracefulStop() +} + +func (x *XDS) Debugf(format string, args ...interface{}) { + log.Printf("xds debug: "+format, args...) +} + +func (x *XDS) Infof(format string, args ...interface{}) { + log.Printf("xds: "+format, args...) +} + +func (x *XDS) Errorf(format string, args ...interface{}) { + log.Printf("xds error: "+format, args...) +} + +func (x *XDS) Warnf(format string, args ...interface{}) { + log.Printf("xds warn: "+format, args...) +} + +type Update struct { + Node string + Version string + Listeners []string + Clusters []string + Secrets []string +} + +var _ Step = &Update{} + +func (u *Update) Run(p *Params) error { + p.Vars["Version"] = u.Version + version, err := p.Fill(u.Version) + if err != nil { + return err + } + log.Printf("update config for %q with version %q", u.Node, version) + + clusters := make([]types.Resource, 0, len(u.Clusters)) + for _, cluster := range u.Clusters { + out := &clusterv3.Cluster{} + if err := p.FillYAML(cluster, out); err != nil { + return err + } + clusters = append(clusters, out) + } + + listeners := make([]types.Resource, 0, len(u.Listeners)) + for _, listener := range u.Listeners { + out := &listenerv3.Listener{} + if err := p.FillYAML(listener, out); err != nil { + return err + } + listeners = append(listeners, out) + } + + secrets := make([]types.Resource, 0, len(u.Secrets)) + for _, secret := range u.Secrets { + out := &tls.Secret{} + if err := p.FillYAML(secret, out); err != nil { + return err + } + secrets = append(secrets, out) + } + + snap := &cache.Snapshot{} + snap.Resources[types.Cluster] = cache.NewResources(version, clusters) + snap.Resources[types.Listener] = cache.NewResources(version, listeners) + snap.Resources[types.Secret] = cache.NewResources(version, secrets) + return p.Config.Cache.SetSnapshot(context.Background(), u.Node, snap) +} + +func (u *Update) Cleanup() {} + +type UpdateExtensions struct { + Extensions []string +} + +var _ Step = &UpdateExtensions{} + +func (u *UpdateExtensions) Run(p *Params) error { + for _, extension := range u.Extensions { + out := &core.TypedExtensionConfig{} + if err := p.FillYAML(extension, out); err != nil { + return err + } + log.Printf("updating extension config %q", out.Name) + if err := p.Config.Extensions.Update(out); err != nil { + return err + } + } + return nil +} + +func (u *UpdateExtensions) Cleanup() {} + +type WorkloadMetadata struct { + Address string + Metadata string +} + +type UpdateWorkloadMetadata struct { + Workloads []WorkloadMetadata +} + +var _ Step = &UpdateWorkloadMetadata{} + +func (u *UpdateWorkloadMetadata) Run(p *Params) error { + for _, wl := range u.Workloads { + out := &workloadapi.Workload{} + if err := p.FillYAML(wl.Metadata, out); err != nil { + return err + } + // Parse address as IP bytes + ip, err := netip.ParseAddr(wl.Address) + if err != nil { + return err + } + log.Printf("updating metadata for %q\n", wl.Address) + out.Addresses = [][]byte{ip.AsSlice()} + namedWorkload := &NamedWorkload{out} + err = p.Config.Workloads.UpdateResource(namedWorkload.GetName(), namedWorkload) + if err != nil { + return err + } + } + return nil +} + +func (u *UpdateWorkloadMetadata) Cleanup() {} diff --git a/test/envoye2e/env/grpc.go b/test/envoye2e/env/grpc.go new file mode 100644 index 00000000000..76de1f7eef5 --- /dev/null +++ b/test/envoye2e/env/grpc.go @@ -0,0 +1,112 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "context" + "fmt" + "io" + "log" + "net" + "time" + + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" + + "istio.io/proxy/test/envoye2e/env/grpc_echo" +) + +// GRPCServer provides a simple grpc echo service for testing. +type GRPCServer struct { + port uint16 + listener net.Listener + + grpc_echo.UnimplementedEchoServer +} + +// NewGRPCServer configures a new GRPCServer. It does not attempt to +// start listening or anything else, however. +func NewGRPCServer(port uint16) *GRPCServer { + return &GRPCServer{port: port} +} + +// Start causes the GRPCServer to start a listener and begin serving. +func (g *GRPCServer) Start() <-chan error { + errCh := make(chan error) + addr := fmt.Sprintf("127.0.0.3:%d", g.port) + go func() { + l, err := net.Listen("tcp", addr) + if err != nil { + errCh <- err + return + } + g.listener = l + s := grpc.NewServer() + grpc_echo.RegisterEchoServer(s, g) + reflection.Register(s) + + errCh <- s.Serve(l) + }() + + go func() { + errCh <- tryWaitForGRPCServer(addr) + }() + return errCh +} + +// Stop closes the listener of the GRPCServer +func (g *GRPCServer) Stop() { + g.listener.Close() +} + +// Echo implements the grpc_echo service. +func (g *GRPCServer) Echo(ctx context.Context, req *grpc_echo.EchoRequest) (*empty.Empty, error) { + return &empty.Empty{}, status.FromProto(req.ReturnStatus).Err() +} + +func (g *GRPCServer) EchoStream(stream grpc_echo.Echo_EchoStreamServer) error { + var i uint32 + for { + req, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + for i = 0; i < req.ResponseCount; i++ { + if err = stream.Send(&empty.Empty{}); err != nil { + return err + } + } + } +} + +func tryWaitForGRPCServer(addr string) error { + for i := 0; i < 10; i++ { + log.Println("Attempting to establish connection to gRPC server: ", addr) + conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) + if err == nil { + conn.Close() + return nil + } + log.Println("Will wait 200ms and try again.") + time.Sleep(200 * time.Millisecond) + } + return fmt.Errorf("timeout waiting for gRPC server startup") +} diff --git a/test/envoye2e/env/grpc_echo/grpc_echo.pb.go b/test/envoye2e/env/grpc_echo/grpc_echo.pb.go new file mode 100644 index 00000000000..72728cf0e29 --- /dev/null +++ b/test/envoye2e/env/grpc_echo/grpc_echo.pb.go @@ -0,0 +1,246 @@ +// Copyright 2020 Istio Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.6.1 +// source: grpc_echo.proto + +package grpc_echo + +import ( + proto "github.com/golang/protobuf/proto" + empty "github.com/golang/protobuf/ptypes/empty" + rpc "google.golang.org/genproto/googleapis/rpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type EchoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ReturnStatus *rpc.Status `protobuf:"bytes,1,opt,name=return_status,json=returnStatus,proto3" json:"return_status,omitempty"` +} + +func (x *EchoRequest) Reset() { + *x = EchoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_echo_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EchoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchoRequest) ProtoMessage() {} + +func (x *EchoRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_echo_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EchoRequest.ProtoReflect.Descriptor instead. +func (*EchoRequest) Descriptor() ([]byte, []int) { + return file_grpc_echo_proto_rawDescGZIP(), []int{0} +} + +func (x *EchoRequest) GetReturnStatus() *rpc.Status { + if x != nil { + return x.ReturnStatus + } + return nil +} + +type StreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResponseCount uint32 `protobuf:"varint,1,opt,name=response_count,json=responseCount,proto3" json:"response_count,omitempty"` +} + +func (x *StreamRequest) Reset() { + *x = StreamRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_echo_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamRequest) ProtoMessage() {} + +func (x *StreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_echo_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamRequest.ProtoReflect.Descriptor instead. +func (*StreamRequest) Descriptor() ([]byte, []int) { + return file_grpc_echo_proto_rawDescGZIP(), []int{1} +} + +func (x *StreamRequest) GetResponseCount() uint32 { + if x != nil { + return x.ResponseCount + } + return 0 +} + +var File_grpc_echo_proto protoreflect.FileDescriptor + +var file_grpc_echo_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x65, 0x63, 0x68, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x09, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x65, 0x63, 0x68, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x46, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x37, 0x0a, 0x0d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0c, 0x72, 0x65, + 0x74, 0x75, 0x72, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x36, 0x0a, 0x0d, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x32, 0x82, 0x01, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x36, 0x0a, 0x04, 0x45, + 0x63, 0x68, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x65, 0x63, 0x68, 0x6f, 0x2e, + 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x65, 0x63, 0x68, 0x6f, 0x2e, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x28, 0x01, 0x30, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_grpc_echo_proto_rawDescOnce sync.Once + file_grpc_echo_proto_rawDescData = file_grpc_echo_proto_rawDesc +) + +func file_grpc_echo_proto_rawDescGZIP() []byte { + file_grpc_echo_proto_rawDescOnce.Do(func() { + file_grpc_echo_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_echo_proto_rawDescData) + }) + return file_grpc_echo_proto_rawDescData +} + +var file_grpc_echo_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_grpc_echo_proto_goTypes = []interface{}{ + (*EchoRequest)(nil), // 0: grpc_echo.EchoRequest + (*StreamRequest)(nil), // 1: grpc_echo.StreamRequest + (*rpc.Status)(nil), // 2: google.rpc.Status + (*empty.Empty)(nil), // 3: google.protobuf.Empty +} +var file_grpc_echo_proto_depIdxs = []int32{ + 2, // 0: grpc_echo.EchoRequest.return_status:type_name -> google.rpc.Status + 0, // 1: grpc_echo.Echo.Echo:input_type -> grpc_echo.EchoRequest + 1, // 2: grpc_echo.Echo.EchoStream:input_type -> grpc_echo.StreamRequest + 3, // 3: grpc_echo.Echo.Echo:output_type -> google.protobuf.Empty + 3, // 4: grpc_echo.Echo.EchoStream:output_type -> google.protobuf.Empty + 3, // [3:5] is the sub-list for method output_type + 1, // [1:3] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_grpc_echo_proto_init() } +func file_grpc_echo_proto_init() { + if File_grpc_echo_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_grpc_echo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EchoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_echo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_grpc_echo_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_grpc_echo_proto_goTypes, + DependencyIndexes: file_grpc_echo_proto_depIdxs, + MessageInfos: file_grpc_echo_proto_msgTypes, + }.Build() + File_grpc_echo_proto = out.File + file_grpc_echo_proto_rawDesc = nil + file_grpc_echo_proto_goTypes = nil + file_grpc_echo_proto_depIdxs = nil +} diff --git a/src/istio/mixerclient/global_dictionary.h b/test/envoye2e/env/grpc_echo/grpc_echo.proto similarity index 56% rename from src/istio/mixerclient/global_dictionary.h rename to test/envoye2e/env/grpc_echo/grpc_echo.proto index 78c690cff4f..b4971aaea7b 100644 --- a/src/istio/mixerclient/global_dictionary.h +++ b/test/envoye2e/env/grpc_echo/grpc_echo.proto @@ -1,4 +1,4 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,22 @@ * limitations under the License. */ -#ifndef ISTIO_MIXERCLIENT_GLOBAL_DICTIONARY_H -#define ISTIO_MIXERCLIENT_GLOBAL_DICTIONARY_H +syntax = "proto3"; -#include -#include +package grpc_echo; -namespace istio { -namespace mixerclient { +import "google/protobuf/empty.proto"; +import "google/rpc/status.proto"; -// Get automatically generated global words. -const std::vector& GetGlobalWords(); +message EchoRequest { + google.rpc.Status return_status = 1; +} -} // namespace mixerclient -} // namespace istio +message StreamRequest { + uint32 response_count = 1; +} -#endif // ISTIO_MIXERCLIENT_GLOBAL_DICTIONARY_H +service Echo { + rpc Echo(EchoRequest) returns (google.protobuf.Empty); + rpc EchoStream(stream StreamRequest) returns (stream google.protobuf.Empty); +} diff --git a/test/envoye2e/env/grpc_echo/grpc_echo_grpc.pb.go b/test/envoye2e/env/grpc_echo/grpc_echo_grpc.pb.go new file mode 100644 index 00000000000..dca419fbfef --- /dev/null +++ b/test/envoye2e/env/grpc_echo/grpc_echo_grpc.pb.go @@ -0,0 +1,167 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package grpc_echo + +import ( + context "context" + empty "github.com/golang/protobuf/ptypes/empty" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion7 + +// EchoClient is the client API for Echo service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EchoClient interface { + Echo(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*empty.Empty, error) + EchoStream(ctx context.Context, opts ...grpc.CallOption) (Echo_EchoStreamClient, error) +} + +type echoClient struct { + cc grpc.ClientConnInterface +} + +func NewEchoClient(cc grpc.ClientConnInterface) EchoClient { + return &echoClient{cc} +} + +func (c *echoClient) Echo(ctx context.Context, in *EchoRequest, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/grpc_echo.Echo/Echo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *echoClient) EchoStream(ctx context.Context, opts ...grpc.CallOption) (Echo_EchoStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_Echo_serviceDesc.Streams[0], "/grpc_echo.Echo/EchoStream", opts...) + if err != nil { + return nil, err + } + x := &echoEchoStreamClient{stream} + return x, nil +} + +type Echo_EchoStreamClient interface { + Send(*StreamRequest) error + Recv() (*empty.Empty, error) + grpc.ClientStream +} + +type echoEchoStreamClient struct { + grpc.ClientStream +} + +func (x *echoEchoStreamClient) Send(m *StreamRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *echoEchoStreamClient) Recv() (*empty.Empty, error) { + m := new(empty.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// EchoServer is the server API for Echo service. +// All implementations must embed UnimplementedEchoServer +// for forward compatibility +type EchoServer interface { + Echo(context.Context, *EchoRequest) (*empty.Empty, error) + EchoStream(Echo_EchoStreamServer) error + mustEmbedUnimplementedEchoServer() +} + +// UnimplementedEchoServer must be embedded to have forward compatible implementations. +type UnimplementedEchoServer struct { +} + +func (UnimplementedEchoServer) Echo(context.Context, *EchoRequest) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Echo not implemented") +} +func (UnimplementedEchoServer) EchoStream(Echo_EchoStreamServer) error { + return status.Errorf(codes.Unimplemented, "method EchoStream not implemented") +} +func (UnimplementedEchoServer) mustEmbedUnimplementedEchoServer() {} + +// UnsafeEchoServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EchoServer will +// result in compilation errors. +type UnsafeEchoServer interface { + mustEmbedUnimplementedEchoServer() +} + +func RegisterEchoServer(s *grpc.Server, srv EchoServer) { + s.RegisterService(&_Echo_serviceDesc, srv) +} + +func _Echo_Echo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EchoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EchoServer).Echo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc_echo.Echo/Echo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EchoServer).Echo(ctx, req.(*EchoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Echo_EchoStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(EchoServer).EchoStream(&echoEchoStreamServer{stream}) +} + +type Echo_EchoStreamServer interface { + Send(*empty.Empty) error + Recv() (*StreamRequest, error) + grpc.ServerStream +} + +type echoEchoStreamServer struct { + grpc.ServerStream +} + +func (x *echoEchoStreamServer) Send(m *empty.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *echoEchoStreamServer) Recv() (*StreamRequest, error) { + m := new(StreamRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _Echo_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpc_echo.Echo", + HandlerType: (*EchoServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Echo", + Handler: _Echo_Echo_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "EchoStream", + Handler: _Echo_EchoStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpc_echo.proto", +} diff --git a/test/envoye2e/env/http_client.go b/test/envoye2e/env/http_client.go new file mode 100644 index 00000000000..7108c42108f --- /dev/null +++ b/test/envoye2e/env/http_client.go @@ -0,0 +1,230 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "log" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" +) + +const ( + // HTTP client time out. + httpTimeOut = 5 * time.Second + + // Maximum number of ping the server to wait. + maxAttempts = 30 +) + +// HTTPGet send GET +func HTTPGet(url string) (code int, respBody string, err error) { + log.Println("HTTP GET", url) + client := &http.Client{Timeout: httpTimeOut} + resp, err := client.Get(url) + if err != nil { + log.Println(err) + return 0, "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return 0, "", err + } + respBody = string(body) + code = resp.StatusCode + return code, respBody, nil +} + +// HTTPGet send GET +func HTTPTlsGet(url, rootdir string, port uint16) (code int, respBody string, err error) { + certPool := x509.NewCertPool() + bs, err := os.ReadFile(filepath.Join(rootdir, "testdata/certs/cert-chain.pem")) + if err != nil { + return 0, "", fmt.Errorf("failed to read client ca cert: %s", err) + } + ok := certPool.AppendCertsFromPEM(bs) + if !ok { + return 0, "", fmt.Errorf("failed to append client certs") + } + + certificate, err := tls.LoadX509KeyPair(filepath.Join(rootdir, "testdata/certs/cert-chain.pem"), + filepath.Join(rootdir, "testdata/certs/key.pem")) + if err != nil { + return 0, "", fmt.Errorf("failed to get certificate") + } + // nolint: gosec + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, ServerName: "localhost", RootCAs: certPool} + tr := &http.Transport{ + TLSClientConfig: tlsConf, + //DialTLS: func(network, addr string) (net.Conn, error) { + // return tls.Dial("tcp", fmt.Sprintf("localhost:%d", port), tlsConf) + //return tls.Dial(network, addr, tlsConf) + //}, + } + log.Println("HTTP TLS GET", url) + client := &http.Client{Timeout: httpTimeOut, Transport: tr} + resp, err := client.Get(url) + log.Println("resp ", resp) + if err != nil { + log.Println(err) + return 0, "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return 0, "", err + } + respBody = string(body) + code = resp.StatusCode + return code, respBody, nil +} + +// HTTPPost sends POST +func HTTPPost(url string, contentType string, reqBody string) (code int, respBody string, err error) { + log.Println("HTTP POST", url) + client := &http.Client{Timeout: httpTimeOut} + resp, err := client.Post(url, contentType, strings.NewReader(reqBody)) + if err != nil { + log.Println(err) + return 0, "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return 0, "", err + } + respBody = string(body) + code = resp.StatusCode + return code, respBody, nil +} + +// ShortLiveHTTPPost send HTTP without keepalive +func ShortLiveHTTPPost(url string, contentType string, reqBody string) (code int, respBody string, err error) { + log.Println("Short live HTTP POST", url) + tr := &http.Transport{ + DisableKeepAlives: true, + } + client := &http.Client{Transport: tr} + resp, err := client.Post(url, contentType, strings.NewReader(reqBody)) + if err != nil { + log.Println(err) + return 0, "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return 0, "", err + } + respBody = string(body) + code = resp.StatusCode + log.Println(respBody) + return code, respBody, nil +} + +// HTTPGetWithHeaders send HTTP with headers +func HTTPGetWithHeaders(l string, headers map[string]string) (code int, respBody string, err error) { + log.Println("HTTP GET with headers: ", l) + client := &http.Client{Timeout: httpTimeOut} + req := http.Request{} + + req.Header = map[string][]string{} + for k, v := range headers { + req.Header[k] = []string{v} + } + req.Method = http.MethodGet + req.URL, err = url.Parse(l) + if err != nil { + log.Println(err) + return 0, "", err + } + + resp, err := client.Do(&req) + if err != nil { + log.Println(err) + return 0, "", err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return 0, "", err + } + respBody = string(body) + code = resp.StatusCode + log.Println(respBody) + return code, respBody, nil +} + +// WaitForHTTPServer waits for a HTTP server +func WaitForHTTPServer(url string) error { + return WaitForHTTPServerWithTLS(url, "", false, 0) +} + +// WaitForHTTPServer waits for a HTTP server +func WaitForHTTPServerWithTLS(url, rootDir string, enableTLS bool, port uint16) error { + for i := 0; i < maxAttempts; i++ { + log.Println("Pinging URL: ", url) + var err error + var code int + if enableTLS { + code, _, err = HTTPTlsGet(url, rootDir, port) + } else { + code, _, err = HTTPGet(url) + } + if err == nil && code == http.StatusOK { + log.Println("Server is up and running...") + return nil + } + log.Println("Will wait 200ms and try again.") + time.Sleep(200 * time.Millisecond) + } + return fmt.Errorf("timeout waiting for server startup") +} + +// WaitForPort waits for a TCP port +func WaitForPort(port uint16) { + serverPort := fmt.Sprintf("localhost:%v", port) + for i := 0; i < maxAttempts; i++ { + log.Println("Pinging port: ", serverPort) + _, err := net.Dial("tcp", serverPort) + if err == nil { + log.Println("The port is up and running...") + return + } + log.Println("Wait 200ms and try again.") + time.Sleep(200 * time.Millisecond) + } + log.Println("Give up the wait, continue the test...") +} + +// IsPortUsed checks if a port is used +func IsPortUsed(port uint16) bool { + serverPort := fmt.Sprintf("localhost:%v", port) + _, err := net.DialTimeout("tcp", serverPort, 100*time.Millisecond) + return err == nil +} diff --git a/test/envoye2e/env/inventory.go b/test/envoye2e/env/inventory.go new file mode 100644 index 00000000000..a1b4745fca8 --- /dev/null +++ b/test/envoye2e/env/inventory.go @@ -0,0 +1,32 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "testing" +) + +type TestInventory struct { + Tests []string +} + +func (p *TestInventory) GetTestIndex(t *testing.T) uint16 { + for i, e := range p.Tests { + if e == t.Name() { + return uint16(i) + } + } + panic("Need an entry in the test inventory") +} diff --git a/test/envoye2e/env/ports.go b/test/envoye2e/env/ports.go new file mode 100644 index 00000000000..5bd519be7eb --- /dev/null +++ b/test/envoye2e/env/ports.go @@ -0,0 +1,80 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "log" +) + +// Dynamic port allocation scheme +// In order to run the tests in parallel. Each test should use unique ports +// Each test has a unique test_name, its ports will be allocated based on that name + +const ( + portBase uint16 = 20000 + // Maximum number of ports used in each test. + portNum uint16 = 20 + portBaseShift uint16 = 3000 +) + +// Ports stores all used ports +type Ports struct { + BackendPort uint16 + ClientAdmin uint16 + ClientPort uint16 + ServerPort uint16 + ServerAdmin uint16 + XDSPort uint16 + ServerTunnelPort uint16 + Max uint16 +} + +func allocPortBase(name uint16) uint16 { + base := portBase + name*portNum + for i := 0; i < 10; i++ { + if allPortFree(base, portNum) { + return base + } + // Shift base port if there is collision. + base += portBaseShift + } + log.Println("could not find free ports, continue the test...") + return base +} + +func allPortFree(base uint16, ports uint16) bool { + for port := base; port < base+ports; port++ { + if IsPortUsed(port) { + log.Println("port is used ", port) + return false + } + } + return true +} + +// NewPorts allocate all ports based on test id. +func NewPorts(name uint16) *Ports { + base := allocPortBase(name) + return &Ports{ + BackendPort: base, + ClientAdmin: base + 1, + ClientPort: base + 2, + ServerPort: base + 3, + ServerAdmin: base + 4, + XDSPort: base + 5, + ServerTunnelPort: base + 6, + Max: base + 6, + } +} diff --git a/test/envoye2e/env/utils.go b/test/envoye2e/env/utils.go new file mode 100644 index 00000000000..447372d5e41 --- /dev/null +++ b/test/envoye2e/env/utils.go @@ -0,0 +1,92 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func GetBazelWorkspace() (string, error) { + // Get bazel args if any + buildArgs := os.Getenv("BAZEL_BUILD_ARGS") + + // Note: `bazel info bazel-bin` returns incorrect path to a binary (always fastbuild, not opt or dbg) + // Instead we rely on symbolic link envoy in the workspace + args := []string{"info", "workspace"} + if buildArgs != "" { + args = append(args, strings.Split(buildArgs, " ")...) + } + workspace, err := exec.Command("bazel", args...).Output() + if err != nil { + return "", err + } + return strings.TrimSuffix(string(workspace), "\n"), nil +} + +func GetBazelWorkspaceOrDie() string { + bin, err := GetBazelWorkspace() + if err != nil { + panic(err) + } + return bin +} + +func GetBazelBin() (string, error) { + workspace, err := GetBazelWorkspace() + if err != nil { + return "", err + } + return filepath.Join(workspace, "bazel-bin/"), nil +} + +func GetBazelBinOrDie() string { + bin, err := GetBazelBin() + if err != nil { + panic(err) + } + return bin +} + +func GetDefaultEnvoyBin() (string, error) { + bin, err := GetBazelBin() + if err != nil { + return "", err + } + return bin, nil +} + +func GetDefaultEnvoyBinOrDie() string { + return GetBazelBinOrDie() +} + +func SkipTSanASan(t *testing.T) { + if os.Getenv("TSAN") != "" || os.Getenv("ASAN") != "" { + t.Skip("https://github.com/istio/istio/issues/21273") + } +} + +func SkipTSan(t *testing.T) { + if os.Getenv("TSAN") != "" { + t.Skip("https://github.com/istio/istio/issues/21273") + } +} + +func IsTSanASan() bool { + return os.Getenv("TSAN") != "" || os.Getenv("ASAN") != "" +} diff --git a/test/envoye2e/env/wasm.go b/test/envoye2e/env/wasm.go new file mode 100644 index 00000000000..6cac1b03ee7 --- /dev/null +++ b/test/envoye2e/env/wasm.go @@ -0,0 +1,56 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package env + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "testing" +) + +// Date: 2/22/2023 +const SHA = "359dcd3a19f109c50e97517fe6b1e2676e870c4d" + +var Modules = []string{ + "attributegen", +} + +// EnsureWasmFiles downloads wasm files for testing. +func EnsureWasmFiles(t *testing.T) { + for _, module := range Modules { + file := filepath.Join(GetBazelWorkspaceOrDie(), fmt.Sprintf("extensions/%s.wasm", module)) + if _, err := os.Stat(file); err == nil { + continue + } + url := fmt.Sprintf("https://storage.googleapis.com/istio-build/proxy/%s-%s.wasm", module, SHA) + log.Printf("Downloading %s...\n", url) + resp, err := http.Get(url) + if err != nil || resp.StatusCode != http.StatusOK { + t.Fatal(err) + } + content, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(file, content, 0o666) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/test/envoye2e/http_metadata_exchange/exchange_test.go b/test/envoye2e/http_metadata_exchange/exchange_test.go new file mode 100644 index 00000000000..596edd117c2 --- /dev/null +++ b/test/envoye2e/http_metadata_exchange/exchange_test.go @@ -0,0 +1,169 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client_test + +import ( + "encoding/base64" + "testing" + "time" + + pstruct "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/protobuf/proto" + + "istio.io/proxy/test/envoye2e" + "istio.io/proxy/test/envoye2e/driver" +) + +func EncodeMetadata(t *testing.T, p *driver.Params) string { + pb := &pstruct.Struct{} + err := p.FillYAML("{"+p.Vars["ClientMetadata"]+"}", pb) + if err != nil { + t.Fatal(err) + } + bytes, err := proto.Marshal(pb) + if err != nil { + t.Fatal(err) + } + return base64.RawStdEncoding.EncodeToString(bytes) +} + +func TestHTTPExchange(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = params.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.HTTPCall{ + IP: "127.0.0.2", + Port: params.Ports.ServerPort, + Body: "hello, world!", + ResponseHeaders: map[string]string{ + "x-envoy-peer-metadata-id": driver.None, + "x-envoy-peer-metadata": driver.None, + }, + }, + &driver.HTTPCall{ + IP: "127.0.0.2", + Port: params.Ports.ServerPort, + Body: "hello, world!", + RequestHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "client", + }, + ResponseHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "server", + "x-envoy-peer-metadata": driver.None, + }, + }, + &driver.HTTPCall{ + IP: "127.0.0.2", + Port: params.Ports.ServerPort, + Body: "hello, world!", + RequestHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "client", + "x-envoy-peer-metadata": EncodeMetadata(t, params), + }, + ResponseHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "server", + "x-envoy-peer-metadata": driver.Any, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestNativeHTTPExchange(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = params.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl") + // TCP MX should not break HTTP MX when there is no TCP prefix or TCP MX ALPN. + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + metadata := EncodeMetadata(t, params) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl"), Concurrency: 2}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + // Must be high enough to exercise cache eviction. + N: 1000, + Step: &driver.HTTPCall{ + IP: "127.0.0.2", + Port: params.Ports.ServerPort, + Body: "hello, world!", + RequestHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "client{{ .N }}", + "x-envoy-peer-metadata": metadata, + }, + ResponseHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "server", + "x-envoy-peer-metadata": driver.Any, + }, + }, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "envoy_server_envoy_bug_failures": &driver.ExactStat{Metric: "testdata/metric/envoy_bug_failures.yaml"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestHTTPExchangeAdditionalLabels(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{}, envoye2e.ProxyE2ETests) + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = params.LoadTestData("testdata/filters/mx_native_inbound_labels.yaml.tmpl") + // TCP MX should not break HTTP MX when there is no TCP prefix or TCP MX ALPN. + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + metadata := EncodeMetadata(t, params) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{driver.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl"), Concurrency: 2}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + // Must be high enough to exercise cache eviction. + N: 1000, + Step: &driver.HTTPCall{ + IP: "127.0.0.2", + Port: params.Ports.ServerPort, + Body: "hello, world!", + RequestHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "client{{ .N }}", + "x-envoy-peer-metadata": metadata, + }, + ResponseHeaders: map[string]string{ + "x-envoy-peer-metadata-id": "server", + "x-envoy-peer-metadata": driver.Any, + }, + }, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "envoy_server_envoy_bug_failures": &driver.ExactStat{Metric: "testdata/metric/envoy_bug_failures.yaml"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} diff --git a/test/envoye2e/inventory.go b/test/envoye2e/inventory.go new file mode 100644 index 00000000000..2d76e42e74c --- /dev/null +++ b/test/envoye2e/inventory.go @@ -0,0 +1,67 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package envoye2e + +import ( + "istio.io/proxy/test/envoye2e/env" +) + +var ProxyE2ETests = &env.TestInventory{} + +func init() { + ProxyE2ETests.Tests = append(ProxyE2ETests.Tests, []string{ + "TestAttributeGen", + "TestBasicFlow", + "TestBasicHTTP", + "TestBasicHTTPGateway", + "TestBasicHTTPwithTLS", + "TestBasicTCPFlow", + "TestBasicCONNECT/quic", + "TestBasicCONNECT/h2", + "TestPassthroughCONNECT/quic", + "TestPassthroughCONNECT/h2", + "TestHTTPExchange", + "TestNativeHTTPExchange", + "TestHTTPExchangeAdditionalLabels", + "TestStats403Failure/#00", + "TestStatsECDS/#00", + "TestStatsEndpointLabels/#00", + "TestStatsServerWaypointProxy", + "TestStatsServerWaypointProxyCONNECT/full_metadata", + "TestStatsServerWaypointProxyCONNECT/empty_metadata", + "TestTCPStatsServerWaypointProxyCONNECT", + "TestStatsGrpc/#00", + "TestStatsGrpcStream/#00", + "TestStatsParallel/Default", + "TestStatsParallel/Customized", + "TestStatsPayload/Customized/", + "TestStatsPayload/Default/", + "TestStatsPayload/DisableHostHeader/", + "TestStatsPayload/UseHostHeader/", + "TestStatsParserRegression", + "TestStatsExpiry", + "TestTCPMetadataExchange/false", + "TestTCPMetadataExchange/true", + "TestTCPMetadataExchangeNoAlpn", + "TestTCPMetadataExchangeWithConnectionTermination", + "TestTCPMetadataNotFoundReporting", + "TestTCPMetricsDuringActiveConnection", + "TestStatsDestinationServiceNamespacePrecedence", + "TestAdditionalLabels", + "TestTCPMXAdditionalLabels", + "TestStatsClientSidecarCONNECT", + "TestStatsWithBaggageWaypointProxy", + }...) +} diff --git a/test/envoye2e/stats_plugin/stats_test.go b/test/envoye2e/stats_plugin/stats_test.go new file mode 100644 index 00000000000..e5fb1cca81b --- /dev/null +++ b/test/envoye2e/stats_plugin/stats_test.go @@ -0,0 +1,1130 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "fmt" + "strconv" + "testing" + "time" + + // Preload proto definitions + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_stats/v3" + dto "github.com/prometheus/client_model/go" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "istio.io/proxy/test/envoye2e" + "istio.io/proxy/test/envoye2e/driver" + "istio.io/proxy/test/envoye2e/env" +) + +type capture struct{} + +func (capture) Run(p *driver.Params) error { + prev, err := strconv.Atoi(p.Vars["RequestCount"]) + if err != nil { + return err + } + p.Vars["RequestCount"] = fmt.Sprintf("%d", p.N+prev) + return nil +} +func (capture) Cleanup() {} + +var Runtimes = []struct { + WasmRuntime string +}{ + { + // native filter + }, +} + +var TestCases = []struct { + Name string + ClientConfig string + ServerConfig string + ServerClusterName string + ClientStats map[string]driver.StatMatcher + ServerStats map[string]driver.StatMatcher + TestParallel bool + ElideServerMetadata bool +}{ + { + Name: "Default", + ClientConfig: "testdata/stats/client_config.yaml", + ServerConfig: "testdata/stats/server_config.yaml", + ClientStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total.yaml.tmpl"}, + }, + ServerStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + "istio_build": &driver.ExactStat{Metric: "testdata/metric/istio_build.yaml"}, + }, + TestParallel: true, + }, + { + Name: "Customized", + ClientConfig: "testdata/stats/client_config_customized.yaml.tmpl", + ServerConfig: "testdata/stats/server_config.yaml", + ClientStats: map[string]driver.StatMatcher{ + "istio_custom": &driver.ExactStat{Metric: "testdata/metric/client_custom_metric.yaml.tmpl"}, + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total_customized.yaml.tmpl"}, + "istio_request_duration_milliseconds": &driver.MissingStat{Metric: "istio_request_duration_milliseconds"}, + }, + ServerStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + "istio_build": &driver.ExactStat{Metric: "testdata/metric/istio_build.yaml"}, + }, + TestParallel: true, + }, + { + Name: "UseHostHeader", + ClientConfig: "testdata/stats/client_config.yaml", + ServerConfig: "testdata/stats/server_config.yaml", + ServerClusterName: "host_header", + ClientStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/host_header_fallback.yaml.tmpl"}, + }, + ServerStats: map[string]driver.StatMatcher{}, + }, + { + Name: "DisableHostHeader", + ClientConfig: "testdata/stats/client_config_disable_header_fallback.yaml", + ServerConfig: "testdata/stats/server_config_disable_header_fallback.yaml", + ServerClusterName: "host_header", + ClientStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_disable_host_header_fallback.yaml.tmpl"}, + }, + ServerStats: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_disable_host_header_fallback.yaml.tmpl"}, + }, + ElideServerMetadata: true, + }, +} + +func enableStats(t *testing.T, vars map[string]string) { + t.Helper() + vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_outbound.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl") +} + +func TestStatsPayload(t *testing.T) { + for _, testCase := range TestCases { + for _, runtime := range Runtimes { + t.Run(testCase.Name+"/"+runtime.WasmRuntime, func(t *testing.T) { + clientStats := make(map[string]driver.StatMatcher) + for metric, values := range testCase.ClientStats { + clientStats[metric] = values + } + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON(testCase.ClientConfig), + "StatsFilterServerConfig": driver.LoadTestJSON(testCase.ServerConfig), + "ServerClusterName": testCase.ServerClusterName, + "ElideServerMetadata": fmt.Sprintf("%t", testCase.ElideServerMetadata), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}, + }, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: clientStats}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: testCase.ServerStats}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } + } +} + +func TestStatsParallel(t *testing.T) { + env.SkipTSanASan(t) + for _, testCase := range TestCases { + t.Run(testCase.Name, func(t *testing.T) { + if !testCase.TestParallel { + t.Skip("Skip parallel testing") + } + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "1", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON(testCase.ClientConfig), + "StatsFilterServerConfig": driver.LoadTestJSON(testCase.ServerConfig), + "ElideServerMetadata": fmt.Sprintf("%t", testCase.ElideServerMetadata), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + clientRequestTotal := &dto.MetricFamily{} + serverRequestTotal := &dto.MetricFamily{} + params.LoadTestProto("testdata/metric/client_request_total.yaml.tmpl", clientRequestTotal) + params.LoadTestProto("testdata/metric/server_request_total.yaml.tmpl", serverRequestTotal) + enableStats(t, params.Vars) + + clientListenerTemplate := driver.LoadTestData("testdata/listener/client.yaml.tmpl") + serverListenerTemplate := driver.LoadTestData("testdata/listener/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{clientListenerTemplate}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{serverListenerTemplate}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + driver.Get(params.Ports.ClientPort, "hello, world!"), + &driver.Fork{ + Fore: &driver.Scenario{ + Steps: []driver.Step{ + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + Duration: 9 * time.Second, + Step: driver.Get(params.Ports.ClientPort, "hello, world!"), + }, + capture{}, + }, + }, + Back: &driver.Repeat{ + Duration: 10 * time.Second, + Step: &driver.Scenario{ + Steps: []driver.Step{ + &driver.Update{ + Node: "client", + Version: "{{.N}}", + Listeners: []string{clientListenerTemplate}, + }, + &driver.Update{ + Node: "server", + Version: "{{.N}}", + Listeners: []string{serverListenerTemplate}, + }, + // may need short delay so we don't eat all the CPU + &driver.Sleep{Duration: 100 * time.Millisecond}, + }, + }, + }, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: testCase.ClientStats}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: testCase.ServerStats}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestStatsGrpc(t *testing.T) { + env.SkipTSan(t) + for _, runtime := range Runtimes { + t.Run(runtime.WasmRuntime, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "DisableDirectResponse": "true", + "UsingGrpcBackend": "true", + "GrpcResponseStatus": "7", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.GrpcServer{}, + &driver.GrpcCall{ + ReqCount: 10, + WantStatus: status.New(codes.PermissionDenied, "denied"), + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestStatsGrpcStream(t *testing.T) { + env.SkipTSan(t) + for _, runtime := range Runtimes { + t.Run(runtime.WasmRuntime, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "UsingGrpcBackend": "true", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config_grpc.yaml.tmpl"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config_grpc.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + params.Vars["ClientHTTPFilters"] = params.LoadTestData("testdata/filters/grpc_stats.yaml") + params.Vars["ClientHTTPFilters"] + params.Vars["ServerHTTPFilters"] = params.LoadTestData("testdata/filters/grpc_stats.yaml") + params.Vars["ServerHTTPFilters"] + + bidi := &driver.GrpcStream{} + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.GrpcServer{}, + bidi, + // Send a first batch of messages on the stream and check stats + bidi.Send([]uint32{1, 5, 7}), + driver.StepFunction(func(p *driver.Params) error { + p.Vars["RequestMessages"] = "3" + p.Vars["ResponseMessages"] = "13" + return nil + }), + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_request_messages_total": &driver.ExactStat{Metric: "testdata/metric/server_request_messages.yaml.tmpl"}, + "istio_response_messages_total": &driver.ExactStat{Metric: "testdata/metric/server_response_messages.yaml.tmpl"}, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_request_messages_total": &driver.ExactStat{Metric: "testdata/metric/client_request_messages.yaml.tmpl"}, + "istio_response_messages_total": &driver.ExactStat{Metric: "testdata/metric/client_response_messages.yaml.tmpl"}, + }, + }, + // Send and close + bidi.Send([]uint32{10, 1, 1, 1, 1}), + bidi.Close(), + driver.StepFunction(func(p *driver.Params) error { + p.Vars["RequestMessages"] = "8" + p.Vars["ResponseMessages"] = "27" + return nil + }), + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_request_messages_total": &driver.ExactStat{Metric: "testdata/metric/server_request_messages.yaml.tmpl"}, + "istio_response_messages_total": &driver.ExactStat{Metric: "testdata/metric/server_response_messages.yaml.tmpl"}, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_request_messages_total": &driver.ExactStat{Metric: "testdata/metric/client_request_messages.yaml.tmpl"}, + "istio_response_messages_total": &driver.ExactStat{Metric: "testdata/metric/client_response_messages.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestAttributeGen(t *testing.T) { + env.EnsureWasmFiles(t) + env.SkipTSan(t) + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "AttributeGenFilterConfig": "filename: " + env.GetBazelWorkspaceOrDie() + "/extensions/attributegen.wasm", + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/request_classification_config.yaml"), + "ResponseCodeClass": "2xx", + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/attributegen.yaml.tmpl") + "\n" + + params.Vars["ServerHTTPFilters"] + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStatsParserRegression(t *testing.T) { + env.SkipTSan(t) + // This is a regression test for https://github.com/envoyproxy/envoy-wasm/issues/497 + params := driver.NewTestParams(t, map[string]string{ + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "ClientHTTPFilters": driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl"), + "StatsFilterClientConfig": "{}", + }, envoye2e.ProxyE2ETests) + listener0 := params.LoadTestData("testdata/listener/client.yaml.tmpl") + params.Vars["StatsFilterClientConfig"] = driver.LoadTestJSON("testdata/stats/client_config_customized.yaml.tmpl") + listener1 := params.LoadTestData("testdata/listener/client.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{listener0}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Update{ + Node: "client", + Version: "1", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{listener1}, + }, + &driver.Sleep{Duration: 1 * time.Second}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStats403Failure(t *testing.T) { + env.SkipTSan(t) + for _, runtime := range Runtimes { + t.Run(runtime.WasmRuntime, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + "ResponseCode": "403", + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/rbac.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "RBAC: access denied", + ResponseCode: 403, + }, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestStatsECDS(t *testing.T) { + env.SkipTSan(t) + for _, runtime := range Runtimes { + t.Run(runtime.WasmRuntime, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = params.LoadTestData("testdata/filters/extension_config_inbound.yaml.tmpl") + params.Vars["ClientHTTPFilters"] = params.LoadTestData("testdata/filters/extension_config_outbound.yaml.tmpl") + + updateExtensions := &driver.UpdateExtensions{ + Extensions: []string{ + driver.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl"), + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl"), + driver.LoadTestData("testdata/filters/mx_native_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl"), + }, + } + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}, + }, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + updateExtensions, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total.yaml.tmpl"}, + }}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestStatsEndpointLabels(t *testing.T) { + env.SkipTSan(t) + for _, runtime := range Runtimes { + t.Run(runtime.WasmRuntime, func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "EnableEndpointMetadata": "true", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + ResponseCode: 200, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total_endpoint_labels.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +const BackendMetadata = ` +namespace: default +workload_name: ratings-v1 +canonical_name: ratings +canonical_revision: version-1 +uid: //v1/pod/default/ratings +service_account: ratings +trust_domain: cluster.global +cluster_id: ratings-cluster +` + +// A mostly empty metadata. +// All workloads are guaranteed to have a UID and ought to have a namespace as well. +const EmptyMetadata = ` +uid: //v1/pod/default/ratings +namespace: default +` + +const ProductPageMetadata = ` +workload_name: productpage-v1 +uid: //v1/pod/default/productpage +` + +func TestStatsServerWaypointProxy(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "EnableDelta": "true", + "EnableMetadataDiscovery": "true", + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_waypoint_proxy_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_waypoint_proxy_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_inbound.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/mx_waypoint.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + params.Vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_outbound.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{{ + Address: "127.0.0.3", + Metadata: BackendMetadata, + }}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + ResponseCode: 200, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_waypoint_proxy_request_total.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +// TestStatsWithBaggageWaypointProxy verifies that baggage-based metadata discovery works correctly. +// +// The test setup is somewhat simplified version of the configuration that pilot generates. The major differences +// relevant to understanding the test below are as follows: +// +// 1. We use just 2 Envoys - client and server, that's what most of the tests here use and we just build +// on top of that +// - server plays a role of a destination workload ztunnel - it terminates HBONE connection +// - client plays a role of a waypoint +// 2. For connection from the test to the client we don't use HBONE: +// - downstream peer metadata discovery works with or without HBONE as long as baggage is provided +// - for connection from client to server we have to use HBONE, because upstream metadata discovery +// relies on network filters in the upstream internal listener. +func TestStatsWithBaggageWaypointProxy(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + // We are testing baggage-based metadata discovery, so no need for xDS-based metadata discovery + "EnableMetadataDiscovery": "false", + "RequestCount": "10", + // This makes internal_outbound cluster populate endpoint metadata with original destination details + "EnableTunnelEndpointMetadata": "true", + // This overrides the server port from ServerPort to ServerTunnelPort, and the listener that can + // terminate HBONE listens on the ServerTunnelPort, so that's what we need. + "EnableOriginalDstPortOverride": "true", + }, envoye2e.ProxyE2ETests) + + params.Vars["ClientMetadata"] = driver.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = driver.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["StatsFilterClientConfig"] = "disable_host_header_fallback: true\nreporter: SERVER_GATEWAY" + params.Vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/baggage_peer_metadata.yaml.tmpl") + + "\n" + driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl") + // This filter is what modifies the default framework HTTP response to include the baggage field from + // the request as one of the headers in the response + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/respond_with_baggage.yaml.tmpl") + params.Vars["UpstreamNetworkFilters"] = driver.LoadTestData("testdata/filters/upstream_peer_metadata.yaml.tmpl") + + clientBaggage := "k8s.deployment.name=productpage-v1,service.name=productpage-v1,app.name=productpage," + + "service.version=version-1,app.version=v1,k8s.namespace.name=default,k8s.cluster.name=client-cluster," + + "k8s.instance.name=productpage-v1-84975bc778-pxz2w" + testBaggage := "k8s.deployment.name=curl,service.name=curl,service.version=v1,k8s.namespace.name=default," + + "k8s.cluster.name=curl-cluster,k8s.instance.name=curl-xxxxxxxxxx-xxxxx" + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", Version: "0", + Clusters: []string{ + params.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + params.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + params.LoadTestData("testdata/listener/client.yaml.tmpl"), + params.LoadTestData("testdata/listener/baggage_peer_metadata.yaml.tmpl"), + }, + Secrets: []string{ + params.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + }, + &driver.Update{ + Node: "server", Version: "0", + Clusters: []string{ + params.LoadTestData("testdata/cluster/internal_inbound.yaml.tmpl"), + }, + Listeners: []string{ + params.LoadTestData("testdata/listener/terminate_connect.yaml.tmpl"), + params.LoadTestData("testdata/listener/server.yaml.tmpl"), + }, + Secrets: []string{ + params.LoadTestData("testdata/secret/server.yaml.tmpl"), + }, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + RequestHeaders: map[string]string{ + "baggage": testBaggage, + }, + ResponseCode: 200, + ResponseHeaders: map[string]string{ + // This is what we got from the client + "baggage": clientBaggage, + // This is what the server got from the client + "request-baggage": clientBaggage, + }, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total_baggage.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStatsClientSidecarCONNECT(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "EnableDelta": "true", + "EnableMetadataDiscovery": "true", + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_waypoint_proxy_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_waypoint.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + params.Vars["EnableTunnelEndpointMetadata"] = "true" + params.Vars["EnableOriginalDstPortOverride"] = "true" + params.Vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_outbound.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/client.yaml.tmpl"), + driver.LoadTestData("testdata/listener/internal_outbound.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + }, + &driver.Update{ + Node: "server", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_inbound.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/terminate_connect.yaml.tmpl"), + driver.LoadTestData("testdata/listener/server.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/server.yaml.tmpl"), + }, + }, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{{ + Address: "127.0.0.1", + Metadata: ProductPageMetadata, + }, { + Address: "127.0.0.2", // We're going to pretend our server is a mesh service + Metadata: BackendMetadata, + }}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + ResponseCode: 200, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_sidecar_connect_request_total.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStatsServerWaypointProxyCONNECT(t *testing.T) { + t.Run("full metadata", func(t *testing.T) { + runStatsServerWaypointProxyCONNECT(t, BackendMetadata, "testdata/metric/server_waypoint_proxy_connect_request_total.yaml.tmpl") + }) + t.Run("empty metadata", func(t *testing.T) { + runStatsServerWaypointProxyCONNECT(t, EmptyMetadata, "testdata/metric/server_waypoint_proxy_connect_emptymeta_request_total.yaml.tmpl") + }) +} + +func runStatsServerWaypointProxyCONNECT(t *testing.T, backendMetadata string, metricResult string) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "EnableDelta": "true", + "EnableMetadataDiscovery": "true", + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_waypoint_proxy_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_waypoint_proxy_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_waypoint.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + params.Vars["EnableTunnelEndpointMetadata"] = "true" + params.Vars["EnableOriginalDstPortOverride"] = "true" + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/client.yaml.tmpl"), + driver.LoadTestData("testdata/listener/internal_outbound.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + }, + &driver.Update{ + Node: "server", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_inbound.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/terminate_connect.yaml.tmpl"), + driver.LoadTestData("testdata/listener/server.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/server.yaml.tmpl"), + }, + }, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{{ + Address: "127.0.0.1", + Metadata: ProductPageMetadata, + }, { + Address: "127.0.0.3", + Metadata: backendMetadata, + }}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + ResponseCode: 200, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: metricResult}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestTCPStatsServerWaypointProxyCONNECT(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "EnableDelta": "true", + "EnableMetadataDiscovery": "true", + "DisableDirectResponse": "true", + "ConnectionCount": "10", + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_waypoint_proxy_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ServerClusterName"] = "internal_outbound" + params.Vars["ServerInternalAddress"] = "internal_inbound" + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_waypoint_proxy_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = driver.LoadTestData("testdata/filters/mx_waypoint_tcp.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + params.Vars["EnableTunnelEndpointMetadata"] = "true" + params.Vars["EnableOriginalDstPortOverride"] = "true" + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_outbound.yaml.tmpl"), + driver.LoadTestData("testdata/cluster/original_dst.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/tcp_client.yaml.tmpl"), + driver.LoadTestData("testdata/listener/internal_outbound.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/client.yaml.tmpl"), + }, + }, + &driver.Update{ + Node: "server", Version: "0", + Clusters: []string{ + driver.LoadTestData("testdata/cluster/internal_inbound.yaml.tmpl"), + }, + Listeners: []string{ + driver.LoadTestData("testdata/listener/terminate_connect.yaml.tmpl"), + driver.LoadTestData("testdata/listener/tcp_waypoint_server.yaml.tmpl"), + }, + Secrets: []string{ + driver.LoadTestData("testdata/secret/server.yaml.tmpl"), + }, + }, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{{ + Address: "127.0.0.1", + Metadata: ProductPageMetadata, + }, { + Address: "127.0.0.3", + Metadata: BackendMetadata, + }}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + &driver.Repeat{ + N: 10, + Step: &driver.TCPConnection{}, + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/server_waypoint_proxy_connect_connections_opened_total.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStatsExpiry(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "1", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/bootstrap/stats_expiry.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}, + }, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 1, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Sleep{Duration: 4 * time.Second}, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.MissingStat{Metric: "istio_requests_total"}, + "istio_build": &driver.ExactStat{Metric: "testdata/metric/istio_build.yaml"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestStatsDestinationServiceNamespacePrecedence(t *testing.T) { + clientStats := map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total_cluster_metadata_precedence.yaml.tmpl"}, + } + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_config.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_config.yaml"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + enableStats(t, params.Vars) + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}, + }, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client_cluster_metadata_precedence.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: clientStats}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestAdditionalLabels(t *testing.T) { + env.SkipTSan(t) + params := driver.NewTestParams(t, map[string]string{ + "RequestCount": "10", + "StatsFilterClientConfig": driver.LoadTestJSON("testdata/stats/client_additional_labels.yaml"), + "StatsFilterServerConfig": driver.LoadTestJSON("testdata/stats/server_additional_labels.yaml"), + "ResponseCodeClass": "2xx", + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_inbound_labels.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_inbound.yaml.tmpl") + params.Vars["ClientHTTPFilters"] = driver.LoadTestData("testdata/filters/mx_native_outbound_labels.yaml.tmpl") + "\n" + + driver.LoadTestData("testdata/filters/stats_outbound.yaml.tmpl") + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{Node: "client", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/client.yaml.tmpl")}}, + &driver.Update{Node: "server", Version: "0", Listeners: []string{params.LoadTestData("testdata/listener/server.yaml.tmpl")}}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.Repeat{ + N: 10, + Step: &driver.HTTPCall{ + Port: params.Ports.ClientPort, + Body: "hello, world!", + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ServerAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/server_request_total_labels.yaml.tmpl"}, + }, + }, + &driver.Stats{ + AdminPort: params.Ports.ClientAdmin, + Matchers: map[string]driver.StatMatcher{ + "istio_requests_total": &driver.ExactStat{Metric: "testdata/metric/client_request_total_labels.yaml.tmpl"}, + }, + }, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} diff --git a/test/envoye2e/tcp_metadata_exchange/tcp_metadata_exchange_test.go b/test/envoye2e/tcp_metadata_exchange/tcp_metadata_exchange_test.go new file mode 100644 index 00000000000..7f6b63fcfc2 --- /dev/null +++ b/test/envoye2e/tcp_metadata_exchange/tcp_metadata_exchange_test.go @@ -0,0 +1,384 @@ +// Copyright 2019 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client_test + +import ( + "fmt" + "testing" + "time" + + "istio.io/proxy/test/envoye2e" + "istio.io/proxy/test/envoye2e/driver" +) + +func TestTCPMetadataExchange(t *testing.T) { + for _, wds := range []bool{true, false} { + t.Run(fmt.Sprintf("%t", wds), func(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + mxStats := map[string]driver.StatMatcher{ + "envoy_metadata_exchange_metadata_added": &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_metadata_added.yaml.tmpl"}, + } + if wds { + params.Vars["AlpnProtocol"] = "disabled" + params.Vars["EnableMetadataDiscovery"] = "true" + params.Vars["AppVersionFallback"] = "true" + mxStats["envoy_metadata_exchange_alpn_protocol_not_found"] = &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_alpn_not_found.yaml.tmpl"} + } else { + params.Vars["AlpnProtocol"] = "mx-protocol" + mxStats["envoy_metadata_exchange_alpn_protocol_found"] = &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_alpn_found.yaml.tmpl"} + } + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/server_stats_network_filter.yaml.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + params.Vars["ClientClusterTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerListenerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{ + { + Address: "127.0.0.1", + Metadata: ` +namespace: default +workload_name: productpage-v1 +workload_type: DEPLOYMENT +canonical_name: productpage-v1 +canonical_revision: version-1 +cluster_id: client-cluster +uid: //v1/pod/default/productpage +`, + }, { + Address: "127.0.0.2", + Metadata: ` +namespace: default +workload_name: ratings-v1 +workload_type: DEPLOYMENT +canonical_name: ratings +canonical_revision: version-1 +cluster_id: server-cluster +uid: //v1/pod/default/ratings +`, + }, + }}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + &driver.Repeat{ + N: 10, + Step: &driver.TCPConnection{}, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_closed_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_connection_close.yaml.tmpl"}, + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_connection_open.yaml.tmpl"}, + "istio_tcp_received_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_received_bytes.yaml.tmpl"}, + "istio_tcp_sent_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_sent_bytes.yaml.tmpl"}, + }}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_closed_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_close.yaml.tmpl"}, + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_open.yaml.tmpl"}, + "istio_tcp_received_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_received_bytes.yaml.tmpl"}, + "istio_tcp_sent_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_sent_bytes.yaml.tmpl"}, + }}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: mxStats}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestTCPMXAdditionalLabels(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + mxStats := map[string]driver.StatMatcher{ + "envoy_metadata_exchange_metadata_added": &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_metadata_added.yaml.tmpl"}, + } + params.Vars["AlpnProtocol"] = "mx-protocol" + mxStats["envoy_metadata_exchange_alpn_protocol_found"] = &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_alpn_found.yaml.tmpl"} + params.Vars["EnableAdditionalLabels"] = "true" + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/server_stats_network_filter.yaml.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + params.Vars["ClientClusterTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerListenerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + &driver.Repeat{ + N: 10, + Step: &driver.TCPConnection{}, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_closed_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_connection_close.yaml.tmpl"}, + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_connection_open.yaml.tmpl"}, + "istio_tcp_received_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_received_bytes.yaml.tmpl"}, + "istio_tcp_sent_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_client_sent_bytes.yaml.tmpl"}, + }}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_closed_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_close.yaml.tmpl"}, + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_open.yaml.tmpl"}, + "istio_tcp_received_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_received_bytes.yaml.tmpl"}, + "istio_tcp_sent_bytes_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_sent_bytes.yaml.tmpl"}, + }}, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: mxStats}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestTCPMetadataExchangeNoAlpn(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "AlpnProtocol": "some-protocol", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/server_stats_network_filter.yaml.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + params.Vars["ClientClusterTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerListenerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + &driver.Repeat{ + N: 10, + Step: &driver.TCPConnection{}, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_open_without_mx.yaml.tmpl"}, + "envoy_metadata_exchange_alpn_protocol_not_found": &driver.ExactStat{Metric: "testdata/metric/tcp_server_mx_stats_alpn_not_found.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestTCPMetadataExchangeWithConnectionTermination(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "AlpnProtocol": "mx-protocol", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_stats_network_filter.yaml.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + params.Vars["ClientClusterTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerListenerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServerAcceptAndClose{}, + &driver.Repeat{ + N: 10, + Step: &driver.InterceptedTCPConnection{ + ReadTimeout: 10 * time.Second, + }, + }, + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_connections_opened_total": &driver.ExactStat{Metric: "testdata/metric/tcp_server_connection_open_without_mx.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +// Regression test for https://github.com/istio/istio/issues/59183 +// Verifies TCP metrics are emitted during active connection, not just at close. +// Without the fix, peerInfoRead() returns false for TCP because downstream_peer_obj +// key wasn't set, causing metrics to only be emitted when end_stream=true. +func TestTCPMetricsDuringActiveConnection(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "AlpnProtocol": "mx-protocol", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ServerMetadata"] = params.LoadTestData("testdata/server_node_metadata.json.tmpl") + params.Vars["ServerNetworkFilters"] = params.LoadTestData("testdata/filters/server_mx_network_filter.yaml.tmpl") + "\n" + + params.LoadTestData("testdata/filters/server_stats_network_filter.yaml.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + params.Vars["ClientClusterTLSContext"] = params.LoadTestData("testdata/transport_socket/client.yaml.tmpl") + params.Vars["ServerListenerTLSContext"] = params.LoadTestData("testdata/transport_socket/server.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.Update{ + Node: "server", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_server.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_server.yaml.tmpl")}, + }, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/server.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{Prefix: "hello"}, + // TCPLoad keeps connection open and sends pings every second + &driver.TCPLoad{}, + // Wait for data to flow while connection is ACTIVE (not closed) + &driver.Sleep{Duration: 3 * time.Second}, + // Check metrics DURING active connection - this is the regression test + // Without the fix, these metrics would not exist or have unknown peer metadata + &driver.Stats{AdminPort: params.Ports.ServerAdmin, Matchers: map[string]driver.StatMatcher{ + // Verify received bytes metric exists with proper peer metadata during active connection + "istio_tcp_received_bytes_total": &driver.ExistStat{Metric: "testdata/metric/tcp_server_received_bytes.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} + +func TestTCPMetadataNotFoundReporting(t *testing.T) { + params := driver.NewTestParams(t, map[string]string{ + "DisableDirectResponse": "true", + "AlpnProtocol": "mx-protocol", + "StatsConfig": driver.LoadTestData("testdata/bootstrap/stats.yaml.tmpl"), + }, envoye2e.ProxyE2ETests) + + params.Vars["ClientMetadata"] = params.LoadTestData("testdata/client_node_metadata.json.tmpl") + params.Vars["ClientUpstreamFilters"] = params.LoadTestData("testdata/filters/client_mx_network_filter.yaml.tmpl") + params.Vars["ClientNetworkFilters"] = params.LoadTestData("testdata/filters/client_stats_network_filter.yaml.tmpl") + + if err := (&driver.Scenario{ + Steps: []driver.Step{ + &driver.XDS{}, + &driver.Update{ + Node: "client", + Version: "0", + Clusters: []string{params.LoadTestData("testdata/cluster/tcp_client_unknown.yaml.tmpl")}, + Listeners: []string{params.LoadTestData("testdata/listener/tcp_client.yaml.tmpl")}, + }, + &driver.UpdateWorkloadMetadata{Workloads: []driver.WorkloadMetadata{ + { + Address: "127.0.0.1", + Metadata: ` +namespace: default +workload_name: productpage-v1 +workload_type: DEPLOYMENT +canonical_name: productpage-v1 +canonical_revision: version-1 +cluster_id: client-cluster +uid: //v1/pod/default/productpage +`, + }, + }}, + &driver.Envoy{Bootstrap: params.LoadTestData("testdata/bootstrap/client.yaml.tmpl")}, + &driver.Sleep{Duration: 1 * time.Second}, + &driver.TCPServer{}, + &driver.Repeat{ + N: 1, + Step: &driver.TCPLoad{}, + }, + &driver.Stats{AdminPort: params.Ports.ClientAdmin, Matchers: map[string]driver.StatMatcher{ + "istio_tcp_sent_bytes_total": &driver.ExistStat{Metric: "testdata/metric/tcp_client_sent_bytes_unknown.yaml.tmpl"}, + }}, + }, + }).Run(params); err != nil { + t.Fatal(err) + } +} diff --git a/test/envoye2e/workloadapi/discovery.pb.go b/test/envoye2e/workloadapi/discovery.pb.go new file mode 100644 index 00000000000..bbd6a71405f --- /dev/null +++ b/test/envoye2e/workloadapi/discovery.pb.go @@ -0,0 +1,364 @@ +// Copyright Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.15.8 +// source: source/extensions/common/workload_discovery/discovery.proto + +package workloadapi + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type WorkloadType int32 + +const ( + WorkloadType_DEPLOYMENT WorkloadType = 0 + WorkloadType_CRONJOB WorkloadType = 1 + WorkloadType_POD WorkloadType = 2 + WorkloadType_JOB WorkloadType = 3 +) + +// Enum value maps for WorkloadType. +var ( + WorkloadType_name = map[int32]string{ + 0: "DEPLOYMENT", + 1: "CRONJOB", + 2: "POD", + 3: "JOB", + } + WorkloadType_value = map[string]int32{ + "DEPLOYMENT": 0, + "CRONJOB": 1, + "POD": 2, + "JOB": 3, + } +) + +func (x WorkloadType) Enum() *WorkloadType { + p := new(WorkloadType) + *p = x + return p +} + +func (x WorkloadType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WorkloadType) Descriptor() protoreflect.EnumDescriptor { + return file_source_extensions_common_workload_discovery_discovery_proto_enumTypes[0].Descriptor() +} + +func (WorkloadType) Type() protoreflect.EnumType { + return &file_source_extensions_common_workload_discovery_discovery_proto_enumTypes[0] +} + +func (x WorkloadType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WorkloadType.Descriptor instead. +func (WorkloadType) EnumDescriptor() ([]byte, []int) { + return file_source_extensions_common_workload_discovery_discovery_proto_rawDescGZIP(), []int{0} +} + +type Workload struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uid string `protobuf:"bytes,20,opt,name=uid,proto3" json:"uid,omitempty"` + // Name represents the name for the workload. + // For Kubernetes, this is the pod name. + // This is just for debugging and may be elided as an optimization. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Namespace represents the namespace for the workload. + // This is just for debugging and may be elided as an optimization. + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Address represents the IPv4/IPv6 address for the workload. + // This should be globally unique. + // This should not have a port number. + // Each workload must have at least either an address or hostname; not both. + // TODO: support dual stack by making this repeated + // TODO: when this is repeated, update xds primary key from network/IP to network/UID + Addresses [][]byte `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses,omitempty"` + // Network represents the network this workload is on. This may be elided for the default network. + // A (network,address) pair makeup a unique key for a workload *at a point in time*. + Network string `protobuf:"bytes,4,opt,name=network,proto3" json:"network,omitempty"` + // The SPIFFE identity of the workload. The identity is joined to form spiffe:///ns//sa/. + // TrustDomain of the workload. May be elided if this is the mesh wide default (typically cluster.local) + TrustDomain string `protobuf:"bytes,6,opt,name=trust_domain,json=trustDomain,proto3" json:"trust_domain,omitempty"` + // ServiceAccount of the workload. May be elided if this is "default" + ServiceAccount string `protobuf:"bytes,7,opt,name=service_account,json=serviceAccount,proto3" json:"service_account,omitempty"` + // CanonicalName for the workload. Used for telemetry. + CanonicalName string `protobuf:"bytes,10,opt,name=canonical_name,json=canonicalName,proto3" json:"canonical_name,omitempty"` + // CanonicalRevision for the workload. Used for telemetry. + CanonicalRevision string `protobuf:"bytes,11,opt,name=canonical_revision,json=canonicalRevision,proto3" json:"canonical_revision,omitempty"` + // WorkloadType represents the type of the workload. Used for telemetry. + WorkloadType WorkloadType `protobuf:"varint,12,opt,name=workload_type,json=workloadType,proto3,enum=istio.workload.WorkloadType" json:"workload_type,omitempty"` + // WorkloadName represents the name for the workload (of type WorkloadType). Used for telemetry. + WorkloadName string `protobuf:"bytes,13,opt,name=workload_name,json=workloadName,proto3" json:"workload_name,omitempty"` + ClusterId string `protobuf:"bytes,18,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"` +} + +func (x *Workload) Reset() { + *x = Workload{} + if protoimpl.UnsafeEnabled { + mi := &file_source_extensions_common_workload_discovery_discovery_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Workload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Workload) ProtoMessage() {} + +func (x *Workload) ProtoReflect() protoreflect.Message { + mi := &file_source_extensions_common_workload_discovery_discovery_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Workload.ProtoReflect.Descriptor instead. +func (*Workload) Descriptor() ([]byte, []int) { + return file_source_extensions_common_workload_discovery_discovery_proto_rawDescGZIP(), []int{0} +} + +func (x *Workload) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +func (x *Workload) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Workload) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *Workload) GetAddresses() [][]byte { + if x != nil { + return x.Addresses + } + return nil +} + +func (x *Workload) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *Workload) GetTrustDomain() string { + if x != nil { + return x.TrustDomain + } + return "" +} + +func (x *Workload) GetServiceAccount() string { + if x != nil { + return x.ServiceAccount + } + return "" +} + +func (x *Workload) GetCanonicalName() string { + if x != nil { + return x.CanonicalName + } + return "" +} + +func (x *Workload) GetCanonicalRevision() string { + if x != nil { + return x.CanonicalRevision + } + return "" +} + +func (x *Workload) GetWorkloadType() WorkloadType { + if x != nil { + return x.WorkloadType + } + return WorkloadType_DEPLOYMENT +} + +func (x *Workload) GetWorkloadName() string { + if x != nil { + return x.WorkloadName + } + return "" +} + +func (x *Workload) GetClusterId() string { + if x != nil { + return x.ClusterId + } + return "" +} + +var File_source_extensions_common_workload_discovery_discovery_proto protoreflect.FileDescriptor + +var file_source_extensions_common_workload_discovery_discovery_proto_rawDesc = []byte{ + 0x0a, 0x3b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, + 0x6f, 0x61, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2f, 0x64, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x69, + 0x73, 0x74, 0x69, 0x6f, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xdd, 0x04, + 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, + 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x61, 0x6e, 0x6f, + 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x61, 0x6e, + 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, + 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1c, 0x2e, 0x69, 0x73, 0x74, 0x69, 0x6f, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x77, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x77, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x4a, + 0x04, 0x08, 0x15, 0x10, 0x16, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x08, 0x10, + 0x09, 0x4a, 0x04, 0x08, 0x13, 0x10, 0x14, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, + 0x0e, 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x4a, 0x04, 0x08, 0x10, 0x10, 0x11, 0x4a, + 0x04, 0x08, 0x11, 0x10, 0x12, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x52, + 0x0f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x52, 0x08, 0x77, 0x61, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0f, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x52, 0x0d, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x0b, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x69, 0x70, 0x73, 0x52, 0x16, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2a, 0x3d, 0x0a, + 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, + 0x0a, 0x44, 0x45, 0x50, 0x4c, 0x4f, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x43, 0x52, 0x4f, 0x4e, 0x4a, 0x4f, 0x42, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x4f, + 0x44, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x4f, 0x42, 0x10, 0x03, 0x42, 0x1b, 0x5a, 0x19, + 0x74, 0x65, 0x73, 0x74, 0x2f, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x65, 0x32, 0x65, 0x2f, 0x77, 0x6f, + 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_source_extensions_common_workload_discovery_discovery_proto_rawDescOnce sync.Once + file_source_extensions_common_workload_discovery_discovery_proto_rawDescData = file_source_extensions_common_workload_discovery_discovery_proto_rawDesc +) + +func file_source_extensions_common_workload_discovery_discovery_proto_rawDescGZIP() []byte { + file_source_extensions_common_workload_discovery_discovery_proto_rawDescOnce.Do(func() { + file_source_extensions_common_workload_discovery_discovery_proto_rawDescData = protoimpl.X.CompressGZIP(file_source_extensions_common_workload_discovery_discovery_proto_rawDescData) + }) + return file_source_extensions_common_workload_discovery_discovery_proto_rawDescData +} + +var file_source_extensions_common_workload_discovery_discovery_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_source_extensions_common_workload_discovery_discovery_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_source_extensions_common_workload_discovery_discovery_proto_goTypes = []interface{}{ + (WorkloadType)(0), // 0: istio.workload.WorkloadType + (*Workload)(nil), // 1: istio.workload.Workload +} +var file_source_extensions_common_workload_discovery_discovery_proto_depIdxs = []int32{ + 0, // 0: istio.workload.Workload.workload_type:type_name -> istio.workload.WorkloadType + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_source_extensions_common_workload_discovery_discovery_proto_init() } +func file_source_extensions_common_workload_discovery_discovery_proto_init() { + if File_source_extensions_common_workload_discovery_discovery_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_source_extensions_common_workload_discovery_discovery_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Workload); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_source_extensions_common_workload_discovery_discovery_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_source_extensions_common_workload_discovery_discovery_proto_goTypes, + DependencyIndexes: file_source_extensions_common_workload_discovery_discovery_proto_depIdxs, + EnumInfos: file_source_extensions_common_workload_discovery_discovery_proto_enumTypes, + MessageInfos: file_source_extensions_common_workload_discovery_discovery_proto_msgTypes, + }.Build() + File_source_extensions_common_workload_discovery_discovery_proto = out.File + file_source_extensions_common_workload_discovery_discovery_proto_rawDesc = nil + file_source_extensions_common_workload_discovery_discovery_proto_goTypes = nil + file_source_extensions_common_workload_discovery_discovery_proto_depIdxs = nil +} diff --git a/testdata/bootstrap/client.yaml.tmpl b/testdata/bootstrap/client.yaml.tmpl new file mode 100644 index 00000000000..4f9162d1728 --- /dev/null +++ b/testdata/bootstrap/client.yaml.tmpl @@ -0,0 +1,82 @@ +node: + id: client + cluster: test-cluster + metadata: { {{ .Vars.ClientMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-outbound-cluster + connect_timeout: 5s + type: STATIC + http2_protocol_options: {} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default + {{- end }} + load_assignment: + cluster_name: server-outbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} + {{- if eq .Vars.EnableEndpointMetadata "true" }} + metadata: + filter_metadata: + istio: + workload: ratings-v1;default;ratings;version-1;server-cluster + {{- end }} +{{ .Vars.ClientTLSContext | indent 4 }} +{{ .Vars.ClientStaticCluster | indent 2 }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} diff --git a/testdata/bootstrap/client_cluster_metadata_precedence.yaml.tmpl b/testdata/bootstrap/client_cluster_metadata_precedence.yaml.tmpl new file mode 100644 index 00000000000..9b06d326a1c --- /dev/null +++ b/testdata/bootstrap/client_cluster_metadata_precedence.yaml.tmpl @@ -0,0 +1,82 @@ +node: + id: client + cluster: test-cluster + metadata: { {{ .Vars.ClientMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-outbound-cluster + connect_timeout: 5s + type: STATIC + http2_protocol_options: {} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: server + {{- end }} + load_assignment: + cluster_name: server-outbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} + {{- if eq .Vars.EnableEndpointMetadata "true" }} + metadata: + filter_metadata: + istio: + workload: ratings-v1;default;ratings;version-1;server-cluster + {{- end }} +{{ .Vars.ClientTLSContext | indent 4 }} +{{ .Vars.ClientStaticCluster | indent 2 }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} diff --git a/testdata/bootstrap/otel_stats.yaml.tmpl b/testdata/bootstrap/otel_stats.yaml.tmpl new file mode 100644 index 00000000000..d115508ddcc --- /dev/null +++ b/testdata/bootstrap/otel_stats.yaml.tmpl @@ -0,0 +1,12 @@ +stats_sinks: +- name: otel + typed_config: + "@type": type.googleapis.com/envoy.extensions.stat_sinks.open_telemetry.v3.SinkConfig + grpc_service: + envoy_grpc: + cluster_name: otel +stats_config: + stats_matcher: + inclusion_list: + patterns: + - prefix: "istiocustom." diff --git a/testdata/bootstrap/server.yaml.tmpl b/testdata/bootstrap/server.yaml.tmpl new file mode 100644 index 00000000000..12533e5df8f --- /dev/null +++ b/testdata/bootstrap/server.yaml.tmpl @@ -0,0 +1,112 @@ +node: + id: server + cluster: test-cluster + metadata: { {{ .Vars.ServerMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ServerAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-inbound-cluster + connect_timeout: 5s + type: STATIC + {{- if eq .Vars.UsingGrpcBackend "true" }} + http2_protocol_options: {} + {{- end }} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default + {{- end }} + load_assignment: + cluster_name: server-inbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} +{{ .Vars.ServerStaticCluster | indent 2 }} +{{- if ne .Vars.DisableDirectResponse "true" }} + listeners: + - name: staticreply + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} + filter_chains: + - filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: staticreply + codec_type: AUTO + route_config: + name: staticreply + virtual_hosts: + - name: staticreply + domains: ["*"] + routes: + - match: + prefix: "/" + direct_response: + {{- if .Vars.DirectResponseCode }} + status: {{ .Vars.DirectResponseCode }} + {{- else }} + status: 200 + {{- end }} + body: + inline_string: "hello, world!" + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +{{- end }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} diff --git a/testdata/bootstrap/stats.yaml.tmpl b/testdata/bootstrap/stats.yaml.tmpl new file mode 100644 index 00000000000..201b2e96620 --- /dev/null +++ b/testdata/bootstrap/stats.yaml.tmpl @@ -0,0 +1,73 @@ +stats_config: + use_all_default_tags: true + stats_tags: + - tag_name: "reporter" + regex: "(reporter=\\.=(.*?);\\.;)" + - tag_name: "source_namespace" + regex: "(source_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_workload" + regex: "(source_workload=\\.=(.*?);\\.;)" + - tag_name: "source_canonical_service" + regex: "(source_canonical_service=\\.=(.*?);\\.;)" + - tag_name: "source_canonical_revision" + regex: "(source_canonical_revision=\\.=(.*?);\\.;)" + - tag_name: "source_workload_namespace" + regex: "(source_workload_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_principal" + regex: "(source_principal=\\.=(.*?);\\.;)" + - tag_name: "source_app" + regex: "(source_app=\\.=(.*?);\\.;)" + - tag_name: "source_version" + regex: "(source_version=\\.=(.*?);\\.;)" + - tag_name: "destination_namespace" + regex: "(destination_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_cluster" + regex: "(source_cluster=\\.=(.*?);\\.;)" + - tag_name: "destination_workload" + regex: "(destination_workload=\\.=(.*?);\\.;)" + - tag_name: "destination_workload_namespace" + regex: "(destination_workload_namespace=\\.=(.*?);\\.;)" + - tag_name: "destination_principal" + regex: "(destination_principal=\\.=(.*?);\\.;)" + - tag_name: "destination_app" + regex: "(destination_app=\\.=(.*?);\\.;)" + - tag_name: "destination_version" + regex: "(destination_version=\\.=(.*?);\\.;)" + - tag_name: "destination_service" + regex: "(destination_service=\\.=(.*?);\\.;)" + - tag_name: "destination_canonical_service" + regex: "(destination_canonical_service=\\.=(.*?);\\.;)" + - tag_name: "destination_canonical_revision" + regex: "(destination_canonical_revision=\\.=(.*?);\\.;)" + - tag_name: "destination_service_name" + regex: "(destination_service_name=\\.=(.*?);\\.;)" + - tag_name: "destination_service_namespace" + regex: "(destination_service_namespace=\\.=(.*?);\\.;)" + - tag_name: "destination_cluster" + regex: "(destination_cluster=\\.=(.*?);\\.;)" + - tag_name: "request_protocol" + regex: "(request_protocol=\\.=(.*?);\\.;)" + - tag_name: "response_code" + regex: "(response_code=\\.=(.*?);\\.;)|_rq(_(\\.d{3}))$" + - tag_name: "grpc_response_status" + regex: "(grpc_response_status=\\.=(.*?);\\.;)" + - tag_name: "response_flags" + regex: "(response_flags=\\.=(.*?);\\.;)" + - tag_name: "connection_security_policy" + regex: "(connection_security_policy=\\.=(.*?);\\.;)" +# Extra regexes used for configurable metrics + - tag_name: "configurable_metric_a" + regex: "(configurable_metric_a=\\.=(.*?);\\.;)" + - tag_name: "configurable_metric_b" + regex: "(configurable_metric_b=\\.=(.*?);\\.;)" + - tag_name: "route_name" + regex: "(route_name=\\.=(.*?);\\.;)" +# Internal monitoring + - tag_name: "cache" + regex: "(cache\\.(.*?)\\.)" + - tag_name: "component" + regex: "(component\\.(.*?)\\.)" + - tag_name: "tag" + regex: "(tag\\.(.*?);\\.)" + - tag_name: "wasm_filter" + regex: "(wasm_filter\\.(.*?)\\.)" diff --git a/testdata/bootstrap/stats_expiry.yaml.tmpl b/testdata/bootstrap/stats_expiry.yaml.tmpl new file mode 100644 index 00000000000..d0410bded2a --- /dev/null +++ b/testdata/bootstrap/stats_expiry.yaml.tmpl @@ -0,0 +1,2 @@ +stats_flush_interval: 1s +stats_eviction_interval: 1s diff --git a/testdata/certs/access-token b/testdata/certs/access-token new file mode 100644 index 00000000000..7be0e417b03 --- /dev/null +++ b/testdata/certs/access-token @@ -0,0 +1 @@ +kombucha \ No newline at end of file diff --git a/testdata/certs/cert-chain.pem b/testdata/certs/cert-chain.pem new file mode 100644 index 00000000000..cb825644a3f --- /dev/null +++ b/testdata/certs/cert-chain.pem @@ -0,0 +1,60 @@ +-----BEGIN CERTIFICATE----- +MIIFMTCCAxmgAwIBAgIDAxaCMA0GCSqGSIb3DQEBCwUAMCIxDjAMBgNVBAoMBUlz +dGlvMRAwDgYDVQQDDAdSb290IENBMB4XDTE5MDcyMjIxMzAzMloXDTIxMDcyMTIx +MzAzMlowNzEOMAwGA1UECgwFSXN0aW8xGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBD +QTELMAkGA1UEBwwCY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4 +rEngnty6lVXmyFqC5DsLpoDXWPaOXr4bmKYHL4PeL5vX/OOn+kbHhm4JjYOxKTmO +YtdLttsQZT7jUd+3WercyVIesWULIO33VbEtNvqKE408J0+5W266+Y+dSmVbrbOf +a6nKP6gpVf1r7Rf0NeS4S3XnUQ0igWo/Pbqn3S2C+ewkR66sCAB5vopKLzdABIN1 +7oLXil2mY4cotk4QPDRgk+AHh+uw1w6JC2c3FcNi3MLh7DIVsLyX//3BWX2bs4jR +FKva6w1KX2nohECj5FqCd7JuFdqtQO+XW5Ihhag3Hzq9VrqDgR2h0XACLqRNJQG4 +0yzP0b0SvOdpOj6JE33IxOBcLGTvrteBadA0sMzWoCfqYeFLOBhFUGSDamHqd0Or +qIAza/dE3Pb3VX0OZzW601PqnWXr4YDIKIdb3tgc97j/zbYvcjp40MQfgik6S/lZ +v8E5ZHHc1Je0zGojL8mAjoklCET1HyP/aRSMIRekdYuCjPqjVyrGeeS3R/Fatigm +gicVYvFDT7iGauyHPA7894CavHVaA40q20Y78bDJSVgsiznNGN7n2oenBZ7P8kbk +Y2pbNnqhn67v5Na1uSHVGMjB+kbVn0WZbbSawKp0W30TCtnuaBdfI1QjOWYdkIEs +pvtdI31V3cLJO9vzegwhcdYS7YG95m6VrdMQbaBE3wIDAQABo1swWTAdBgNVHQ4E +FgQUuTgg1nLlC0d35VPxZh1T6NqkDg8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNV +HQ8BAf8EBAMCAuQwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUA +A4ICAQBV0mZPDPDOna692/cRVP2qHoHzEsYLttTioRCmQT8ideW8tpW7IwWozpKr +BlcaCXUc1K8hoMFSgYCcuh+VMH8qNCQHDEcWoPHPBFrr83ALRVdh4cYeMa7ZcIRS +l08Fa5TbVQXDkkj+t0KFr6VIBzXvVw8W/r8bgy4LSu/33WGQg4fRecp9mm0j/P8Y +DaWalN1m8TeRZtN1k7ltHmkeOPH+3NlgZ4YvlZ+ltPMrXowdP+/nCZgeR1BzFmer +0EVZ0Hq35EvXrmrrN5X4cc3b9OmaQpPQxqSlA/8hwyd0ItLZCYv1v4CB+0AI6CvY +P2RtxJ87UCz9wlthIlV2a8/d0NItV08HATfK5nXjuY8Ndm3V+jgEGGivizEaSeso +grBKJ/TbyoUpsfji5Fc2ogzrGkon1EFgR/WJ8FVlty2YVnjTfjVxD8OJ8Znjm1MH +YbisHAdTqTND0Fa2F7GFxtltD0DxQ2zsH3D8W98dxeRRigYCifixqFtk72iE702o +4K3CfPhi7MN4dxbQNFXtjrjnIQn9lN+ih+E1RK0Z4LTrd4WwsJF1MHBm6MRIFu4t +xaJb3fB5Artwn6DJ1vhfLoONDfwbrRL9/QDt0fFKtnCMbHcApsGJmrXskGim8Kma +Cw3FWjtdhpzmgK5L0SVell2IK3gEF3rphETn37YFDCttOUzpCg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFDCCAvygAwIBAgIUZqU0Sviq/wULK6UV7PoAZ7B+nqAwDQYJKoZIhvcNAQEL +BQAwIjEOMAwGA1UECgwFSXN0aW8xEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMTkwNzIy +MjEzMDA0WhcNMjkwNzE5MjEzMDA0WjAiMQ4wDAYDVQQKDAVJc3RpbzEQMA4GA1UE +AwwHUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANNl5/pH +/ktdqEsb83cqHrYJCyzbvWce6k/iud4Czu6FClFX8b+n/Rv9GrZFxJwKAFlUx3iA +BGlSn/1XYpnhudQhgVGvyuWNO5kX4BfrAJwfWt+7Mn6NcWvunDqwqUPxI07sgCJW +AYBAwkZH/Nhn6tj571XWNPziUtCwlPNkFMiRu/2nI/tq12IgwimFjVgiCuprNfyX +tQz/DMVTWpCRQLK5ptlYMfk0P25UKyJdKHnr1MPQBJmPXMfSSqpGjksikV4QnYc7 +CXB3ucq7ty0IWA8QXH+86WqMTh22mosWVXHe0OGbzYtuyVnXc1G7YRv4D87G3Ves +G4n/8e+RaDTacvwOsYEkuQGk+s8pggPkIqydGy02JNZ4cSRpXJRTzME2BgBZxT8S +Ew1Omr5+iuLNRAKEYRM/eWI7qrs5fxpD6K9JELHS41hWHGdW94PP0wKz70trx5pM +fLpcVm7BQ5ppgf+t4vgKnrNiACQpfyZbInCBU0doaZaqVMnKH0vgyM7xrC43fsOP +y5URy3tEH8Uk7Dbvsmj7AXR7IPKlYtgcqcJXmeWa+kLOpx3G55hgJL1ySrxXg/qz +AobgmV0IycH2ntn5lXvjbwe0cfXAnZgGoALZjJVuEazyBmmVzjBjG2Qcq35nHfp8 +Rm6WnCZIaGsZqgoDuSJD280ZLWW7R0PMcnypAgMBAAGjQjBAMB0GA1UdDgQWBBQZ +h3/ckcK23ZYKO+JsZowd3dIobDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIC5DANBgkqhkiG9w0BAQsFAAOCAgEAjh4CdrwnLsqwVxyVSgxd7TfSHtKE/J2Y +2IZ4fJYXGkq3McPk2e9u0zjCH0buvfDwyAItLIacD+YwIP+OC2WxLe+YMZ5KkXl3 +LuhQ2TOoRlrbp5tYLQITZIIl9+vNkgnn1DkdxkLm9cDDag19LSxa9Rjrnb3wwFAT +IzEhy+d18FpQtdMMhmonU/L8Oy5LqjT5BR3T8VrXYUsaAkcUs/yHNTFAY3iJFBWL +Z8dFa5v0A1Ryi8quSNo7lK/hSEZvvV9k4XfFAolXSUqe8BCuXe0rbAq3Jq9HgDww +oImGM0uz4Zf89uhTk1O7UOUfQoSTmA0yZICtQkCiOC0J4AlAOTmiEXUC9gicV3R8 +dvVOqNBOcBELglZ+NIMm6FQQqPh1nZ6A3Bh+JRTPerAF12725RZZE6XMxq2MSr3G +k5yH10QPMH7/DJRQUhRHAhbge+jk2csa7EGSxABcbsPLSV+cEzXRO4cJeItoZQLh +saFhIn9lGukXG6lgiperOqZl6DFVcUG6/nogK7KOTAnV9zjR/7vNwvYzPI9iOR3V +6dbG38KnipcfL885VLJVTnfhvYHlxFklCKTEnOHnmKsM0qjQuky3DBzmDA6iqeOM +SHRje5LKxi7mllJfu/X0MxYJWiu6i4gMCWZsC3UtAJQ09x7iwcNr/1bl9ApGszOy +Uff0OxD2hzk= +-----END CERTIFICATE----- diff --git a/testdata/certs/client-key.cert b/testdata/certs/client-key.cert new file mode 100644 index 00000000000..fbe64cde161 --- /dev/null +++ b/testdata/certs/client-key.cert @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCOZbIDvbyKV3pX +JO9ckVHHGkfDcZekdY5QkO8mhyxzgm0db/czaKef8tq8SfoRBiUpDUTeM8EBwBpk +HB4NW7pep1VCYyT0QANwpgkKObW3/c2IB/a+gTF5OS5QpRFRvmHPKdbTEUDqnbfo ++jdTh1RXS13ULml32jLLbJJ8g1hu5Lxp2az50WizwzeNjV8sBcQ6lSfWqJrxkjis +u9UEO9sAv1kYheHtmmEPFZIdbJQqAqwhQvD11Oxz8LZoPGEX0fqGI+G5LMI7dCGC +pIZOd3GXDkevAAorO2kdMokISo0zXbXsM4nS5R6ycpnlfKuCtUwfTYPh7k6MD0kS +9I5ZGScJAgMBAAECggEAF8RUhnhUMDjyk/n3s5kTr6oswSvD9jCizMKsD9+u5San +RKsL9PoqRDGaHaQxR0chy6zJylaUB9FC9mOVBoDBZgwC2H5IzDWk/VfRGXJRaMDF +XceKgPh6Q/PtzYsHjX+7voKKtxbhoWcqq4mb02a6holvQztt4hG6uaZI+txHVda6 +94+hgxL06DwNPh9VCiStXC4WeB1BaWRlqj0d5LY8v3ipDDeokOBqxPuR7Haf9kGK +0MwYe5blPdZgBwM0UHg9NAOJNHoEZ5P7zVDMaUk0BTJ2EVzoHmlexzeEcHmg8qRh +EqMjW0jlwcl17jQ1QMXN+zt91AkytklUvbpBKHQtkwKBgQC72ghoeecaBNoyenKR +29unbQ/W8J+OZUYdW701G54xxykAHckeGnYIhZ76fzzfuEVTwII2Is8POlMVeNjl +EbfW/KPaL2t+cxh5yLErcFRsLiB+67RBCwktOUnKRqIm/hVs33xJE753o6aJGm0E +MsYnYeDIFW74Ct+utVJyln4T9wKBgQDCDkPBk0VQBkPZgTp8lUUqvjRQECIUGptx +iC9z/vCXsNekomVPlBHahdPnjVKr6p52aJJcWfiEMUIHvOnTNbQnWJHApfx0h7JS +SNy36BqirynU49nbg3Z2bql7S7zAHkrazocQqnTOzfDT2LDDyHwP6+2jmVAshNv7 +O1Xcvb3c/wKBgD4xp8r/YTZKGPvRcpE8G3NJNo9RR2JbwWUC9JfatvuAFuEE+4tN +83pK0yHYco0Xc0yRVgsaZzeBdfSL+DOPNDCnoJAiVxKchKP9gDsDi8/tTbD31Mwc +HUOtzfJ8hD8orGtJatq/ALaXphGKgEF9lgF/9G4KOp1A7GHpgoyRqthtAoGAOkLG +HOv2N1xqKncd9CFsrrSESDVPxfFnEeLtPEoiOaiiVY9cE1RFN/JN+Ir5cxvxj2M9 +7fQlJKsVQ/V3zi2ldNqmh8xNyz6iTwoJGj3ZIVatnHj8A2eovU3kHFxUwulVV/QB +oQNMJnq1/yRjjaQ3eyA+LIvvAi6xTPA3ixp8UkkCgYBI7/rSqdHt1aY4Ubp/Qb1P +8F2oqkI0yT0E31+y/H/PP1b0Hf/CedQhyBYfrt2NkLo4ejSX738fH0onCKUgbSiT +dEtO6maVpV/P5qC2D4sYvLI4iaT0FjEz6sbSUWp2qmPlcsQwwq2mF0bUkAd4uCQk +RVD3SeqV4EwOsq/r2ny5VQ== +-----END PRIVATE KEY----- diff --git a/testdata/certs/client.cert b/testdata/certs/client.cert new file mode 100644 index 00000000000..beaf8c8dcdc --- /dev/null +++ b/testdata/certs/client.cert @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxzCCAq+gAwIBAgIUNcRvk34WYmZ33/X2OmPe5G2mRAswDQYJKoZIhvcNAQEL +BQAwPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTAyMTgyMDUyWhcNMjcwNTEwMTgyMDUy +WjA9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AI5lsgO9vIpXelck71yRUccaR8Nxl6R1jlCQ7yaHLHOCbR1v9zNop5/y2rxJ+hEG +JSkNRN4zwQHAGmQcHg1bul6nVUJjJPRAA3CmCQo5tbf9zYgH9r6BMXk5LlClEVG+ +Yc8p1tMRQOqdt+j6N1OHVFdLXdQuaXfaMstsknyDWG7kvGnZrPnRaLPDN42NXywF +xDqVJ9aomvGSOKy71QQ72wC/WRiF4e2aYQ8Vkh1slCoCrCFC8PXU7HPwtmg8YRfR ++oYj4bkswjt0IYKkhk53cZcOR68ACis7aR0yiQhKjTNdtewzidLlHrJymeV8q4K1 +TB9Ng+HuTowPSRL0jlkZJwkCAwEAAaOBvjCBuzAJBgNVHRMEAjAAMBEGCWCGSAGG ++EIBAQQEAwIHgDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIw +NgYDVR0RBC8wLYYrc3BpZmZlOi8vY2x1c3Rlci5sb2NhbC9ucy9kZWZhdWx0L3Nh +L2NsaWVudDAdBgNVHQ4EFgQUEr3U4t0vU2OfEkNhkv/OQkZzHvMwHwYDVR0jBBgw +FoAU4QAONZ/lVw4pbdUOVA03YKVvqGAwDQYJKoZIhvcNAQELBQADggEBAC76IVYv +kkD+ojj6O9w40y5U7YJ6UL2s7tN5HHZrV6t608YdgooJ9GLvUVDRlOohCB3MjBQE +M059b7+b6rqGJYNWQWlICdvZ1rSHUQRdWNAe9xqgmYXGT2zLgZJyhplboz381oPu +BUwFhs+j6Xek+1ub+NpiYjRZQ37jp5xeh6jodyKJdkGY6Arxe6nrO6ZuebIySXbG +k5GbeGyvXcCgqycatromMWmtG71zUtesfCu7GNyrReuJQ4f778Tb/N5qopBXOOum +ciJgRQH2bVUMEMutLvj+FROOl54YtO/3Lxi2kzzLAEfBHG6NC6yfq+TBIi++xgsP +3au7z8O9muicSto= +-----END CERTIFICATE----- diff --git a/testdata/certs/client_ext.cnf b/testdata/certs/client_ext.cnf new file mode 100644 index 00000000000..fde8b913ded --- /dev/null +++ b/testdata/certs/client_ext.cnf @@ -0,0 +1,7 @@ +basicConstraints = CA:FALSE +nsCertType = client +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth +subjectAltName = @alt_names +[alt_names] +URI.1 = spiffe://cluster.local/ns/default/sa/client diff --git a/testdata/certs/generate.sh b/testdata/certs/generate.sh new file mode 100644 index 00000000000..0518850c682 --- /dev/null +++ b/testdata/certs/generate.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +openssl genrsa -out root.key 2048 +openssl req -x509 -new -nodes -key root.key -sha256 -days 1825 -out root.cert + +# Server certificate: +openssl genrsa -out server-key.cert 2048 +openssl req -new -key server-key.cert -out server.csr +openssl x509 -req -in server.csr -CA root.cert -CAkey root.key -out server.cert -days 1650 -sha256 -extfile server_ext.cnf + +# Client certificate: +openssl genrsa -out client-key.cert 2048 +openssl req -new -key client-key.cert -out client.csr +openssl x509 -req -in client.csr -CA root.cert -CAkey root.key -out client.cert -days 1650 -sha256 -extfile client_ext.cnf + + +# Stackdriver certs need localhost as the common name diff --git a/testdata/certs/key.pem b/testdata/certs/key.pem new file mode 100644 index 00000000000..21e580d3565 --- /dev/null +++ b/testdata/certs/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAuKxJ4J7cupVV5shaguQ7C6aA11j2jl6+G5imBy+D3i+b1/zj +p/pGx4ZuCY2DsSk5jmLXS7bbEGU+41Hft1nq3MlSHrFlCyDt91WxLTb6ihONPCdP +uVtuuvmPnUplW62zn2upyj+oKVX9a+0X9DXkuEt151ENIoFqPz26p90tgvnsJEeu +rAgAeb6KSi83QASDde6C14pdpmOHKLZOEDw0YJPgB4frsNcOiQtnNxXDYtzC4ewy +FbC8l//9wVl9m7OI0RSr2usNSl9p6IRAo+RagneybhXarUDvl1uSIYWoNx86vVa6 +g4EdodFwAi6kTSUBuNMsz9G9ErznaTo+iRN9yMTgXCxk767XgWnQNLDM1qAn6mHh +SzgYRVBkg2ph6ndDq6iAM2v3RNz291V9Dmc1utNT6p1l6+GAyCiHW97YHPe4/822 +L3I6eNDEH4IpOkv5Wb/BOWRx3NSXtMxqIy/JgI6JJQhE9R8j/2kUjCEXpHWLgoz6 +o1cqxnnkt0fxWrYoJoInFWLxQ0+4hmrshzwO/PeAmrx1WgONKttGO/GwyUlYLIs5 +zRje59qHpwWez/JG5GNqWzZ6oZ+u7+TWtbkh1RjIwfpG1Z9FmW20msCqdFt9EwrZ +7mgXXyNUIzlmHZCBLKb7XSN9Vd3CyTvb83oMIXHWEu2BveZula3TEG2gRN8CAwEA +AQKCAgBC6lLerFGo3iHBPQnm8dIfV5bJ8TdtwRC7qSVH50SuBqw+qCjJnht1gtVu +arO0Rw7O9Cu1CK36E+Wksu8QXemHVP+HlZnaXXU8sPVBP/GqhIkhqdDuhh3qbDFI +ukNd4+P5OSbN3SEO0VTBfai3Wavlx5oSVkEfJqub/L8cwj0Sf4K8Zqj5NvENLCip +1s/7R2dnHSSV+1IRz3CTJPPGWDpWYF7F+89ARbzDlbkxsZYZxYpsGIzRZTgBD8Yg +AFBOUdCaihX3fkJTl50lnn5ZpI3TRpIF569UJfpq6shZkzevuYYsQzfUHL3i+6PN +dp8cQPONyB8tsn8DQiXL8Enmm4Rw1KgVicc7r14PT1iNPkB1DJd6a0wTbjHKdt14 +aSoVneDJc/7s2clgC/W/PUiKrXff7uaTe3sN0qTN4dtI9uNFT5HQ5Af9+p/coP8z +cGxGIqQHFzmYivXzkjScrQ4cFHjWSDMBW/fttlrRAOO3qiDOVti1jG2pnbDH1TZU +ailFAD92jlOQ3hel90S7YwjvuU4cw2/JiJLhvQujPUlVfgdRkGMfiZ4PfT+k8uX7 +8fkFWRdbSdO7Fwr9u/7ORcbsX7vUFWT/NSn04a9UYdrHPt6r4ETcKbP0SsQF7Qp7 +w1tIgC/oSDSEulyJzA3o4Ci9v3n67r0yLDeRERHFj51gQ3G60QKCAQEA3CYLSExI +RQoNu6jxx92jCKIRYlIaTo8f5DbONDqQPJIGiL37GG5Tf2qjanUUZRKPUx1SwfVZ +P/UMa6IgDYYHO+Kvv2GsOajBlSOjs+28qV3AI+m45qWulT/NaESiDE2nMwAExXIy +HCqVGgnW8ZMhDhL39Q0Cgt9tUoK6O1fuRrp27uKaLD+YYmhtDWy7mS8BvWcIl7CU +jBOM3PS7rs5RRJd3/8joCmEMGuzPsMtFF2iwA5SigsWLMjD7QHyWPDT0NlShxIMP +A0LAIcoxer5FoCUw/XorCT6VkY1Mr7dA8D4X2ZIT5ZI/Y7AJZj8Gn47LSfrfCyVF +vk/CyJnC2Df1KQKCAQEA1r9F17kU3r1DaZeTNuwgOtxDMpEBTbF1GoHz97g4ef3W +MAWnCw51cTEtmsNqDElWszAWqlRjyHd+N+LdKiicZG3V9bhOSHNHu9QCQDn5um43 +w5IUSI8gQ4CqXhGXfZ5slXdHUYDCZ6VYt+0srR0rEDQoWd0cwYLA3wuOVISl7o4+ +ltAbFBrv0GdCR22tJZwIRqcrqYCKFuwtKuOFzyj597OADCE/qWn8969LBq4kXYdM +6IosifGOiAF49sl13Q/aDCam60VjEWKF+TqdmsO3TCLvrupuKnvEdXlXK5IJbIXe ++Z+b2kiov5wBR+u1bfeXdH35uxSgVr86XxXLRe9ixwKCAQBSCKcpoKtJdq6ZYCIA +bRmEbQf3UErXPUQQAVAjbDM1LuDacZiwiOP6Vd1hHRGlfB4GRaYB+o/wYjrnnLk+ +8NOfQCBnO1k2/yhrj6U/tfYYUoP3ne81m0WL/gNnuDN+TC1itr4QaTY9Aq0ez83V +pRKrMOxO1zM5W1JcbbRByslSd8c7yxrSJDx/ZxRD7WGWekq2rj8obzdbXymdaGDL +ibwEyECCAvZcb79YBSh7Y7NyPqNgIjHQcxYkdNYbOJGvC7h4yl6hYIjmmSgJL1Py +vhYpz9IKkkyZHEYVv8Z0r9+15h1zCJj7cdzHI+DMxe2M5WPhRGd6ur/bY9NcdteB +RJDJAoIBAA7XHwt+ZdvStoLoj6re/Ic0y4wGC1IELnSLgIGhAH4ltZSR/247LJCK +9nzYfk6lDtHJQ/e3Z0HmSBmymtgcAFrMYFnfx8En/lAToagwmXpxvXbNdItjILap +gJyJmK98sEJQAOS4AjdJbO0g/dJkzqILCLLVHfSdhZikYsyichkfSWIAta5ZAjOj +vyfSg4Gy27uON+05zdExtxlcqdWcHlIo3HN6JL0fbvTq70Nh629vNzhmvBc4U0JA +38wmNff17XqjfSuLGwKLjXigvV2Bovwm+etblgtnjDcWEJkZOX9/bN5RUmLuXIMJ +U+lVd69Gyfep8QUlssLr6ivCBM8rcOcCggEBAMuanzBKGV2ct+TUifFE84zqFIyE +56PoW0mkKNbtNCswEAsbPPLsdhSoTrkMZcIy933S4TvYe7PXrSwr4w8eGEQv/wvY +yUkSrNwu38P8V2d6uCkZ5z5TnafzB3g7eRDYw3e6jBl9ACyPcOpc44ScrX4n6mqb +JOQ0oAvE6LVmwq4HxosSXQVymUhNBUflHpYkG8OBz3e2l+oO+0ojQ1AMspx46gEO +NmEX44x7BXED0Vf8er4GDMRnVtXBD3z7oerGqJC9CtWK/u4DeLc4cJ2oWTY7wc2r +QM8PWj4L8NlUfm8t7KG10FUjJlzwPXU1VJXfqzJP2X8yRq3O8OATZgaLjYs= +-----END RSA PRIVATE KEY----- diff --git a/testdata/certs/root-cert.pem b/testdata/certs/root-cert.pem new file mode 100644 index 00000000000..261912d6eac --- /dev/null +++ b/testdata/certs/root-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFDCCAvygAwIBAgIUZqU0Sviq/wULK6UV7PoAZ7B+nqAwDQYJKoZIhvcNAQEL +BQAwIjEOMAwGA1UECgwFSXN0aW8xEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMTkwNzIy +MjEzMDA0WhcNMjkwNzE5MjEzMDA0WjAiMQ4wDAYDVQQKDAVJc3RpbzEQMA4GA1UE +AwwHUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANNl5/pH +/ktdqEsb83cqHrYJCyzbvWce6k/iud4Czu6FClFX8b+n/Rv9GrZFxJwKAFlUx3iA +BGlSn/1XYpnhudQhgVGvyuWNO5kX4BfrAJwfWt+7Mn6NcWvunDqwqUPxI07sgCJW +AYBAwkZH/Nhn6tj571XWNPziUtCwlPNkFMiRu/2nI/tq12IgwimFjVgiCuprNfyX +tQz/DMVTWpCRQLK5ptlYMfk0P25UKyJdKHnr1MPQBJmPXMfSSqpGjksikV4QnYc7 +CXB3ucq7ty0IWA8QXH+86WqMTh22mosWVXHe0OGbzYtuyVnXc1G7YRv4D87G3Ves +G4n/8e+RaDTacvwOsYEkuQGk+s8pggPkIqydGy02JNZ4cSRpXJRTzME2BgBZxT8S +Ew1Omr5+iuLNRAKEYRM/eWI7qrs5fxpD6K9JELHS41hWHGdW94PP0wKz70trx5pM +fLpcVm7BQ5ppgf+t4vgKnrNiACQpfyZbInCBU0doaZaqVMnKH0vgyM7xrC43fsOP +y5URy3tEH8Uk7Dbvsmj7AXR7IPKlYtgcqcJXmeWa+kLOpx3G55hgJL1ySrxXg/qz +AobgmV0IycH2ntn5lXvjbwe0cfXAnZgGoALZjJVuEazyBmmVzjBjG2Qcq35nHfp8 +Rm6WnCZIaGsZqgoDuSJD280ZLWW7R0PMcnypAgMBAAGjQjBAMB0GA1UdDgQWBBQZ +h3/ckcK23ZYKO+JsZowd3dIobDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIC5DANBgkqhkiG9w0BAQsFAAOCAgEAjh4CdrwnLsqwVxyVSgxd7TfSHtKE/J2Y +2IZ4fJYXGkq3McPk2e9u0zjCH0buvfDwyAItLIacD+YwIP+OC2WxLe+YMZ5KkXl3 +LuhQ2TOoRlrbp5tYLQITZIIl9+vNkgnn1DkdxkLm9cDDag19LSxa9Rjrnb3wwFAT +IzEhy+d18FpQtdMMhmonU/L8Oy5LqjT5BR3T8VrXYUsaAkcUs/yHNTFAY3iJFBWL +Z8dFa5v0A1Ryi8quSNo7lK/hSEZvvV9k4XfFAolXSUqe8BCuXe0rbAq3Jq9HgDww +oImGM0uz4Zf89uhTk1O7UOUfQoSTmA0yZICtQkCiOC0J4AlAOTmiEXUC9gicV3R8 +dvVOqNBOcBELglZ+NIMm6FQQqPh1nZ6A3Bh+JRTPerAF12725RZZE6XMxq2MSr3G +k5yH10QPMH7/DJRQUhRHAhbge+jk2csa7EGSxABcbsPLSV+cEzXRO4cJeItoZQLh +saFhIn9lGukXG6lgiperOqZl6DFVcUG6/nogK7KOTAnV9zjR/7vNwvYzPI9iOR3V +6dbG38KnipcfL885VLJVTnfhvYHlxFklCKTEnOHnmKsM0qjQuky3DBzmDA6iqeOM +SHRje5LKxi7mllJfu/X0MxYJWiu6i4gMCWZsC3UtAJQ09x7iwcNr/1bl9ApGszOy +Uff0OxD2hzk= +-----END CERTIFICATE----- diff --git a/testdata/certs/root.cert b/testdata/certs/root.cert new file mode 100644 index 00000000000..8ab65fa3cf1 --- /dev/null +++ b/testdata/certs/root.cert @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWzCCAkOgAwIBAgIUEQ3MTVIpIdYHZPx7+Y0cmOTjR/QwDQYJKoZIhvcNAQEL +BQAwPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTAyMTgwMTI5WhcNMjcxMTAxMTgwMTI5 +WjA9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AJqJYGCKrPqgrHE8fsi+P1uXbqsFzO322Za168wNJh0cIy4Lzd0zGbDXvsVujQ0s +UOUfNyM2qgb6G/7GJ5WYluwSWRYbv2Cc1c3XiGyIBl/eE/utNQ+wfIgf6XbMilWm +CG1WzOEU+ss8d1qewk+Kfdf7u+vKMKU52QMOd75Kwpn513QHZGFBVxAZCVlRtyhe +qL+Nk1pT7mQLux22bwUDqLjT5wwtSscSjPWOgQHetjiz6W1nD+pyMRUl0YMf37pY +t0n44lR98hh1mv0LPYfSKGo/RzjlUEJiGEfomwbb4sBW3dQnInqeMrH6vDsDSHzK +Wt5XoQs17uu/YkIChrIpA2kCAwEAAaNTMFEwHQYDVR0OBBYEFOEADjWf5VcOKW3V +DlQNN2Clb6hgMB8GA1UdIwQYMBaAFOEADjWf5VcOKW3VDlQNN2Clb6hgMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFgY2O2ueINKw4dbMcJqMo+6 +cG6AszL9GafnlA1qSJtD6cHyyF8lkhWzHtIAM1/QrwO3/yciquMw1gTubwxF44Uk +NvTPUnqw4bPWNXlqowrXzoM1uqQcvGCwhcEfHkOtiSVXR7Sa+0fGCLGpmlwdWUw1 +WbRTFCOKqmJFC0/J7/smcTrCzac28YRcELnTsVOVa6jXrib9TUvnwzK2nI8tK8OO +SLQTJFjEgig17XztZ8OCT0MTPwvE7A+nLaPwf5/vC5NtCF7HyL67H3ItdbNX6C/U +w7LGFivmoENW31jZirbAEmbRpvCBvlarEP+vteR61JDkZ3Y7KfgYxzykxRK6WFw= +-----END CERTIFICATE----- diff --git a/testdata/certs/root.key b/testdata/certs/root.key new file mode 100644 index 00000000000..f1adf6d2b94 --- /dev/null +++ b/testdata/certs/root.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaiWBgiqz6oKxx +PH7Ivj9bl26rBczt9tmWtevMDSYdHCMuC83dMxmw177Fbo0NLFDlHzcjNqoG+hv+ +xieVmJbsElkWG79gnNXN14hsiAZf3hP7rTUPsHyIH+l2zIpVpghtVszhFPrLPHda +nsJPin3X+7vryjClOdkDDne+SsKZ+dd0B2RhQVcQGQlZUbcoXqi/jZNaU+5kC7sd +tm8FA6i40+cMLUrHEoz1joEB3rY4s+ltZw/qcjEVJdGDH9+6WLdJ+OJUffIYdZr9 +Cz2H0ihqP0c45VBCYhhH6JsG2+LAVt3UJyJ6njKx+rw7A0h8ylreV6ELNe7rv2JC +AoayKQNpAgMBAAECggEABIhw0Kgkso3VvRUVkXkeJedDXmYjSJwn7gcsUV4V3v75 +K7O6MEm8UE8FyCgJ4GfrQAtKPGDRPDboLREeLmHNVIOlxBOgiZqUHW9775YOXM/I +dYaTQsBuKLaJ7DuL977w6pckF5qcUCuYAysav0dMUXJvhNc3ldMkcdXh7o2mF2rc +aY0OdN2Za16hCaIQAmbvOqUXbrQRahQos3DAuOze9vLKEAMC+uPHPTY4N2Iu7Dnj +UeiTFIH271tOtsB6bNJBnMbve6yw14K93kQiVoSOFufliSj3qveYQRzqEd78Kkl7 +DqgXB4uIAjapBcs6n/X+vcbDEN/+9DxsQ6jBiNptEQKBgQDAXuv/OteUkfywMPiu +jEj+ps1sW5LL3Oz3wR2Ryq7+TK70QTyRpWm+Hjb1mOPGQQueyS1i81d9t6m47dZH +HTWCk6eP3i4t0iFVxsSx8zy4M/xfl6KFyFYls65rBMCtk19AHCAf8wrA16zYRGQ/ +MCLIDpZ2fK34oiVBsqrmdR7Q2QKBgQDNptS3Almsyk9dO/oQwRzr2tFapFU4Je48 +YdBB3OrLcGN4OIt7mqRYp8XiY4RcIQgM4N1GWQ99I7DJwY6e2XjXlu71h3NWUTRg +PoPbAwtx2TYtpwUfnQ07rXT5SbGZpf+Cqtc6PiAY0EcIvZuzh7twSOM6fEddYQoa +9LbVEz8tEQKBgQCDctk0CBMzVCYkhvIG45klWPlZp6FBaG8MRIteCe9VmTSbdtBa +fXsqDB9l5tkNqXi1QaafzMPmBdAVq38WDOF5nkeLSTio2sMoh6/0IM9G108GSukl +HWWwUX1HZ3H5qZAWkKFq83pPl5BmHyWY/91kcoNh54RBNxraL6oT28f40QKBgQDM +FSxxNS7iz5406wk9STc3Q96QshYz80hZucPfKKoFG9JKgurAzfUcWdqB0LqQZuND +TH+qiUVarWmKvr+XGj/Wytz24eVumoV8oW1ekcXwxFsEsQPfnI5+U6OKpDxQOzC2 +bm3KSc62cTKdFPUIE8HKKzr8VkrH+z35BDLQfxop0QKBgHq/X2vRzk6SzqEmBz9b +t4dI/raW0cA5uia9orzfg4U5IKHMygw0tR2azHFC3pqRiOaYNADn511mdfE+U6PJ +Lh/4ZJtFtQ0+F//srM8+tvb5nDE9z5A0n+p2/3v8lC9u7gGzewx7Fl5xtsPkU9pO +lzOWbdeVA2Y/g1AFYHMX7/xi +-----END PRIVATE KEY----- diff --git a/testdata/certs/server-key.cert b/testdata/certs/server-key.cert new file mode 100644 index 00000000000..4047aac534b --- /dev/null +++ b/testdata/certs/server-key.cert @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC+3js555q2V18G +IRwSS3aLbTnwKaiIcNjZIcpOJWsirDLCgLzlULQh259K9IFGr3pzFIgYCMXDevAy +p6cH3dVfD95+gjqWqJ4rpo0g6aRfj3rrLzrooOQJR4aaRkPIqnWcgkfJYyvfproj +OA+aB+8j5k9yY+zvcYcDjN/PP29mnP6cOkIsMq9wI44HrZY4FP8QU7AucAcWOvt6 +t5vST05v43A0WpsYwAMuVQxuknHKELrkpDE+zwKxobrpPzLG2WVuC7r8mGrD1cHF +5A6K7aXiWYTe80wY5PFFbXo2x8IhIaqjCnwA+xFwmeuOCRI7KdI+Hb8gNKLGwiMH +K/rcClSlAgMBAAECggEADRvMK4Tpjj5f61bYy7TdFmRRB1qFFaHScs8sSsOtIPt8 +nPkkgAdT58NipE44lrc0jLTLSANKOcu2tXPoN9UXc2jumfetuG0qN8s/hBQn0txS +z9Y1kIaEQtLjvrK2sxBp9W7JKV6jQ7/6prKR9701wlxt9mhAfI1qfWbudBhiOUTO +pcRr8Ck/ZE/I4gh5+Rvgj8tfg96B3Qq1MbZZ6lI0dNS6BQ97UICTE/xmHqrlCRD9 +JwFXDSQHC/hRWqJl3vFYIGFYeL4GngRn5+WOR7j1p6q2jszTT81jRCRtdHvllcYl +tY7CqpgG6erWoebnBCi8ox2uzY+Nbw4LrxjQOZG0fQKBgQDgs5POGvrWnwbMXpOA +L2obYC4RmPHK6tf46wabbUJx7gLGzzoeRnpc8DH3OYkpswo1vKjgiI5KCqWg4xA5 +cmLFp6PC+Sgp7dKqkTncFOFTy4r6jgRzpgaIbQZuDNhCh5xCMDqUxTl0KoV2J687 +Xo5ZbhPJgto/dXPijJgT3zLUSwKBgQDZdDh4sq3j/VodaYz5yjnWkX6OatWrRkG5 +VnFg0ZAzhqUQWmfYkE1Lb7S8paaBI5f/RRMHiq5eOBy67gZzBpZp/FrWvF8rgUPg +h8qp1BuZUcQ7oIm2klakXVb5dODgl6jQfe7jWH8KTwYJolMwfsaJ8Naa04kyikS2 +Jc3ItS6EzwKBgQCq/fzsSvu3dyzlONNmKK7GRlrIaWsWz7+qXK+ad3qo2Eako+3G +PDvBncdoKxCF+wk5+2dH9qLRFWkVFbWzAajIYNnt9UzrG1/FDN7K69jMu4f3QzuA +BkfSaaUK+htYBXqTo7/wlmUyUWlekLR4qWwKAgpsvnb285pMPFE+TguQIQKBgQCk +hlNntsDsW7a/xCl+oKvMFT7soBZTxQ9bG/UibMwuv/PJgK1LZDqnFbhoduiYkoah +A/EW5q1w6gGKySamBtjtDZrpF5LmBqKFkhgbEDllckEHYDpxoRzetSRmDzFJnFWE +kZOZ/U35TbritSc97N1oZojokZ4fWBAOxGGDNtogbwKBgQCN3kevOYBwSyKRiklu +dca+CZb4smXCrzaGq5ue9vDyiuXoid8eMscHeGbn5zGBvZyCUQvU8M79Ptm+g0cy +DsXEZ6xCfiOPb0z/XAm2xEJqGnHvzOjj9FwG+RF7PKlBB1SVIcpoe0VpsgznD8tP +TI2BPjeIOJvMiYoDx1DAuhbp9g== +-----END PRIVATE KEY----- diff --git a/testdata/certs/server.cert b/testdata/certs/server.cert new file mode 100644 index 00000000000..f6ba6f6e625 --- /dev/null +++ b/testdata/certs/server.cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIUA5B66o/DRpXnqGjyWRlvgeiFlU8wDQYJKoZIhvcNAQEL +BQAwPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTAyMTgyNzA3WhcNMjcwNTEwMTgyNzA3 +WjA9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AL7eOznnmrZXXwYhHBJLdottOfApqIhw2Nkhyk4layKsMsKAvOVQtCHbn0r0gUav +enMUiBgIxcN68DKnpwfd1V8P3n6COpaoniumjSDppF+PeusvOuig5AlHhppGQ8iq +dZyCR8ljK9+muiM4D5oH7yPmT3Jj7O9xhwOM388/b2ac/pw6Qiwyr3AjjgetljgU +/xBTsC5wBxY6+3q3m9JPTm/jcDRamxjAAy5VDG6SccoQuuSkMT7PArGhuuk/MsbZ +ZW4LuvyYasPVwcXkDortpeJZhN7zTBjk8UVtejbHwiEhqqMKfAD7EXCZ644JEjsp +0j4dvyA0osbCIwcr+twKVKUCAwEAAaOBszCBsDAJBgNVHRMEAjAAMBEGCWCGSAGG ++EIBAQQEAwIGQDAOBgNVHQ8BAf8EBAMCBaAwQAYDVR0RBDkwN4Yrc3BpZmZlOi8v +Y2x1c3Rlci5sb2NhbC9ucy9kZWZhdWx0L3NhL3NlcnZlcoIIaXN0aW8uaW8wHQYD +VR0OBBYEFDFa3IJOzeyX60Y1KQxqDATglbVSMB8GA1UdIwQYMBaAFOEADjWf5VcO +KW3VDlQNN2Clb6hgMA0GCSqGSIb3DQEBCwUAA4IBAQCGLgjHejSfUD8a0uFFzIC5 +nKHhG55mo6kEJNspm2yOb4jtOFHLzExEFpgFUHmqzhSvuhLruEPWT0WDANS8p5x4 +opA2vJakh5OxxVYURUCNxI3w9MFET/BqgDjcrNDjFTZkoJ4Pt2/egw8RJp8kXz36 +4iSdbHNN838Y+36Ke9+xV0U7g0I2dP26wKEYdV98e6zMQlmtCyCKTOUxq2MWKvPv +OlmQ8++iWtDpA9AIcQfFeuQN3AbgJbMCw174GqVQfeOWVP4ojenXnui6KP7uyQvh +KRu89cJLqPLZ5FHr67eE5sI0ixd7fP5EKRSRztGJeB3J4oOd4jUbKGUhg6e/M6F8 +-----END CERTIFICATE----- diff --git a/testdata/certs/server.csr b/testdata/certs/server.csr new file mode 100644 index 00000000000..5f6b1565c91 --- /dev/null +++ b/testdata/certs/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICgjCCAWoCAQAwPTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMSEwHwYDVQQK +DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC+3js555q2V18GIRwSS3aLbTnwKaiIcNjZIcpOJWsirDLCgLzl +ULQh259K9IFGr3pzFIgYCMXDevAyp6cH3dVfD95+gjqWqJ4rpo0g6aRfj3rrLzro +oOQJR4aaRkPIqnWcgkfJYyvfprojOA+aB+8j5k9yY+zvcYcDjN/PP29mnP6cOkIs +Mq9wI44HrZY4FP8QU7AucAcWOvt6t5vST05v43A0WpsYwAMuVQxuknHKELrkpDE+ +zwKxobrpPzLG2WVuC7r8mGrD1cHF5A6K7aXiWYTe80wY5PFFbXo2x8IhIaqjCnwA ++xFwmeuOCRI7KdI+Hb8gNKLGwiMHK/rcClSlAgMBAAGgADANBgkqhkiG9w0BAQsF +AAOCAQEAIKr1a5455s8ahiwjuRN/lqYkQLCr1EyQBJLT9mez7xUkSsvy5uzTSU88 +UsPH4PptG+Bvw/+fkBBGildwtDFsq5pgnILUeDPE6cK5ePProZAICNBUK72XkwUm +PUI5wh+8VfpCnIBNvQ1nRl2/lydYyEIkFoQpJ86MzPVqeIck2G8jGxq8Ocs5QvfJ +B+0smVYifMO10M1VMRJGOkeB0J5b4i3WT4W0JJ6Wzk+chCl3EE575rRlZbSXKaT4 +RVl36LlDl+8Rvt1VP0eOxySmfZsBXzDQe59gU00+nEv9fH+y8DefCNZsTHXE4Egt +AAxA6Xy5PAU0xIoU+7go4M+HtidDAA== +-----END CERTIFICATE REQUEST----- diff --git a/testdata/certs/server_ext.cnf b/testdata/certs/server_ext.cnf new file mode 100644 index 00000000000..b3466c9068a --- /dev/null +++ b/testdata/certs/server_ext.cnf @@ -0,0 +1,7 @@ +basicConstraints = CA:FALSE +nsCertType = server +keyUsage = critical, digitalSignature, keyEncipherment +subjectAltName = @alt_names +[alt_names] +URI.1 = spiffe://cluster.local/ns/default/sa/server +DNS.1 = istio.io diff --git a/testdata/client_node_metadata.json.tmpl b/testdata/client_node_metadata.json.tmpl new file mode 100644 index 00000000000..2d9d4aaa50f --- /dev/null +++ b/testdata/client_node_metadata.json.tmpl @@ -0,0 +1,40 @@ +"APP_CONTAINERS": "test,bonzai", +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.5-dev", +"LABELS": { + "app": "productpage", + "pod-template-hash": "84975bc778", + "version": "v1", + "service.istio.io/canonical-name": "productpage-v1", + "service.istio.io/canonical-revision": "version-1", + "role": "client" +}, +"MESH_ID": "mesh", +"NAME": "productpage-v1-84975bc778-pxz2w", +"NAMESPACE": "default", +"CLUSTER_ID": "client-cluster", +"OWNER": "kubernetes://apis/apps/v1/namespaces/default/deployments/productpage-v1", +"PLATFORM_METADATA": { + "gcp_gke_cluster_name": "test-cluster", + "gcp_location": "us-east4-b", + "gcp_project": "test-project", + "gcp_project_number": "123" +}, +"POD_NAME": "productpage-v1-84975bc778-pxz2w", +"SERVICE_ACCOUNT": "bookinfo-productpage", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"STS_PORT": "{{ .Vars.STSPort }}", +"WORKLOAD_NAME": "productpage-v1", +"app": "productpage", +"istio": "sidecar", +"kubernetes.io/limit-ranger": "LimitRanger plugin set: cpu request for container productpage", +"pod-template-hash": "84975bc778", +"version": "v1" diff --git a/testdata/cloud_run_client_node_metadata.json.tmpl b/testdata/cloud_run_client_node_metadata.json.tmpl new file mode 100644 index 00000000000..6a663dc38bd --- /dev/null +++ b/testdata/cloud_run_client_node_metadata.json.tmpl @@ -0,0 +1,24 @@ +"ISTIO_VERSION": "CSM", +"LABELS": { + "service.istio.io/canonical-name": "productpage", + "service.istio.io/canonical-revision": "productpage-00001-abc" +}, +"MESH_ID": "projects/23413241234/locations/global/meshes/test-mesh", +"NAME": "productpage", +"NAMESPACE": "test-project", +"PLATFORM_METADATA": { + "gcp_project": "test-project", + "gcp_project_number": "23413241234", + "gcp_location": "us-central1", + "gcp_cloud_run_service": "productpage", + "gcp_cloud_run_revision": "productpage-00001-abc", + "gcp_cloud_run_configuration": "productpage", +}, +"SERVICE_ACCOUNT": "bookinfo-productpage", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"STS_PORT": "{{ .Vars.STSPort }}", +"WORKLOAD_NAME": "productpage", diff --git a/testdata/cloud_run_server_node_metadata.json.tmpl b/testdata/cloud_run_server_node_metadata.json.tmpl new file mode 100644 index 00000000000..ebb0a330c73 --- /dev/null +++ b/testdata/cloud_run_server_node_metadata.json.tmpl @@ -0,0 +1,24 @@ +"ISTIO_VERSION": "CSM", +"LABELS": { + "service.istio.io/canonical-name": "ratings", + "service.istio.io/canonical-revision": "ratings-00001-xyz" +}, +"MESH_ID": "projects/23413241234/locations/global/meshes/test-mesh", +"NAME": "ratings", +"NAMESPACE": "test-project", +"PLATFORM_METADATA": { + "gcp_project": "test-project", + "gcp_project_number": "23413241234", + "gcp_location": "us-central1", + "gcp_cloud_run_service": "ratings", + "gcp_cloud_run_revision": "ratings-00001-xyz", + "gcp_cloud_run_configuration": "ratings", +}, +"SERVICE_ACCOUNT": "bookinfo-ratings", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"STS_PORT": "{{ .Vars.STSPort }}", +"WORKLOAD_NAME": "ratings", \ No newline at end of file diff --git a/testdata/cluster/internal_inbound.yaml.tmpl b/testdata/cluster/internal_inbound.yaml.tmpl new file mode 100644 index 00000000000..13147fafead --- /dev/null +++ b/testdata/cluster/internal_inbound.yaml.tmpl @@ -0,0 +1,17 @@ +name: internal_inbound +load_assignment: + cluster_name: internal_inbound + endpoints: + - lb_endpoints: + - endpoint: + address: + envoy_internal_address: + server_listener_name: {{ .Vars.ServerInternalAddress }} +transport_socket: + name: envoy.transport_sockets.internal_upstream + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.internal_upstream.v3.InternalUpstreamTransport + transport_socket: + name: envoy.transport_sockets.raw_buffer + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer diff --git a/testdata/cluster/internal_outbound.yaml.tmpl b/testdata/cluster/internal_outbound.yaml.tmpl new file mode 100644 index 00000000000..485a3ab4966 --- /dev/null +++ b/testdata/cluster/internal_outbound.yaml.tmpl @@ -0,0 +1,39 @@ +name: internal_outbound +metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default +load_assignment: + cluster_name: internal_outbound + endpoints: + - lb_endpoints: + - endpoint: + address: + envoy_internal_address: + server_listener_name: internal_outbound +{{- if eq .Vars.EnableTunnelEndpointMetadata "true" }} + metadata: + filter_metadata: + envoy.filters.listener.original_dst: + local: 127.0.0.2:{{ .Ports.ServerPort }} + istio: + workload: ratings-v1;default;ratings;version-1;server-cluster +{{- end }} +{{- if .Vars.UpstreamNetworkFilters }} +filters: +{{ .Vars.UpstreamNetworkFilters | fill }} +{{- end }} +transport_socket: + name: envoy.transport_sockets.internal_upstream + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.internal_upstream.v3.InternalUpstreamTransport + passthrough_metadata: + - name: envoy.filters.listener.original_dst + kind: { host: {}} + transport_socket: + name: envoy.transport_sockets.raw_buffer + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer diff --git a/testdata/cluster/original_dst.yaml.tmpl b/testdata/cluster/original_dst.yaml.tmpl new file mode 100644 index 00000000000..8fe6c4a04cc --- /dev/null +++ b/testdata/cluster/original_dst.yaml.tmpl @@ -0,0 +1,63 @@ +name: original_dst +type: ORIGINAL_DST +cleanup_interval: 1s +lb_policy: CLUSTER_PROVIDED +{{- if eq .Vars.EnableOriginalDstPortOverride "true" }} +original_dst_lb_config: + upstream_port_override: {{ .Ports.ServerTunnelPort }} +{{ end }} +typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: +{{ if eq .Vars.quic "true" }} + http3_protocol_options: + allow_extended_connect: true +{{ else }} + http2_protocol_options: + allow_connect: true +{{ end }} +transport_socket: +{{ if eq .Vars.quic "true" }} + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + value: + upstream_tls_context: + common_tls_context: + tls_certificate_sds_secret_configs: + name: client + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } + sni: istio.io +{{ else }} + name: tls + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + value: + common_tls_context: + tls_certificate_sds_secret_configs: + name: client + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } +{{ end }} diff --git a/testdata/cluster/otel.yaml.tmpl b/testdata/cluster/otel.yaml.tmpl new file mode 100644 index 00000000000..c2025c14a60 --- /dev/null +++ b/testdata/cluster/otel.yaml.tmpl @@ -0,0 +1,12 @@ +- connect_timeout: 1s + load_assignment: + cluster_name: otel + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Vars.OtelPort }} + http2_protocol_options: {} + name: otel diff --git a/testdata/cluster/server.yaml.tmpl b/testdata/cluster/server.yaml.tmpl new file mode 100644 index 00000000000..8d228cd6476 --- /dev/null +++ b/testdata/cluster/server.yaml.tmpl @@ -0,0 +1,27 @@ +{{- if .Vars.ServerClusterName }} +name: {{ .Vars.ServerClusterName }} +{{- else }} +name: server-outbound-cluster +metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default +{{- end }} +connect_timeout: 5s +type: STATIC +load_assignment: + {{- if .Vars.ServerClusterName }} + cluster_name: {{ .Vars.ServerClusterName }} + {{- else }} + cluster_name: server-outbound-cluster + {{- end }} + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} diff --git a/testdata/cluster/tcp_client.yaml.tmpl b/testdata/cluster/tcp_client.yaml.tmpl new file mode 100644 index 00000000000..76547618d83 --- /dev/null +++ b/testdata/cluster/tcp_client.yaml.tmpl @@ -0,0 +1,22 @@ +name: outbound|9080|tcp|server.default.svc.cluster.local +metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default +connect_timeout: 5s +type: STATIC +load_assignment: + cluster_name: outbound|9080|tcp|server.default.svc.cluster.local + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +{{ .Vars.ClientClusterTLSContext }} +filters: +{{ .Vars.ClientUpstreamFilters | fill }} diff --git a/testdata/cluster/tcp_client_unknown.yaml.tmpl b/testdata/cluster/tcp_client_unknown.yaml.tmpl new file mode 100644 index 00000000000..a7ecdd3025b --- /dev/null +++ b/testdata/cluster/tcp_client_unknown.yaml.tmpl @@ -0,0 +1,22 @@ +name: outbound|9080|tcp|server.default.svc.cluster.local +metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default +connect_timeout: 5s +type: STATIC +load_assignment: + cluster_name: outbound|9080|tcp|server.default.svc.cluster.local + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} +{{ .Vars.ClientClusterTLSContext }} +filters: +{{ .Vars.ClientUpstreamFilters | fill }} diff --git a/testdata/cluster/tcp_passthrough.yaml.tmpl b/testdata/cluster/tcp_passthrough.yaml.tmpl new file mode 100644 index 00000000000..01bb275fa5e --- /dev/null +++ b/testdata/cluster/tcp_passthrough.yaml.tmpl @@ -0,0 +1,17 @@ +name: tcp_passthrough +load_assignment: + cluster_name: tcp_passthrough + endpoints: + - lb_endpoints: + - endpoint: + address: + envoy_internal_address: + server_listener_name: tcp_passthrough +transport_socket: + name: envoy.transport_sockets.internal_upstream + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.internal_upstream.v3.InternalUpstreamTransport + transport_socket: + name: envoy.transport_sockets.raw_buffer + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer diff --git a/testdata/cluster/tcp_server.yaml.tmpl b/testdata/cluster/tcp_server.yaml.tmpl new file mode 100644 index 00000000000..a383b937485 --- /dev/null +++ b/testdata/cluster/tcp_server.yaml.tmpl @@ -0,0 +1,19 @@ +name: inbound|9080|tcp|server.default.svc.cluster.local +metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default +connect_timeout: 5s +type: STATIC +load_assignment: + cluster_name: inbound|9080|tcp|server.default.svc.cluster.local + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} diff --git a/testdata/filters/access_log_policy.yaml.tmpl b/testdata/filters/access_log_policy.yaml.tmpl new file mode 100644 index 00000000000..5ac11dfda81 --- /dev/null +++ b/testdata/filters/access_log_policy.yaml.tmpl @@ -0,0 +1,14 @@ +- name: envoy.filters.http.wasm + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + vm_config: + runtime: "envoy.wasm.runtime.null" + code: + local: { inline_string: "envoy.wasm.access_log_policy" } + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { log_window_duration: "{{ .Vars.LogWindowDuration }}" } diff --git a/testdata/filters/attributegen.yaml.tmpl b/testdata/filters/attributegen.yaml.tmpl new file mode 100644 index 00000000000..c19f0c14798 --- /dev/null +++ b/testdata/filters/attributegen.yaml.tmpl @@ -0,0 +1,40 @@ +- name: istio.attributegen + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + vm_config: + vm_id: attributegen{{ .N }} + runtime: envoy.wasm.runtime.v8 + code: + local: { {{ .Vars.AttributeGenFilterConfig }} } + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { + "attributes": [ + { + "output_attribute": "istio_responseClass", + "match": [ + { + "value": "2xx", + "condition": "response.code >= 200 && response.code <= 299" + } + ] + }, + { + "output_attribute": "istio_operationId", + "match": [ + { + "value": "GetMethod", + "condition": "request.method == 'GET'" + }, + { + "value": "PostMethod", + "condition": "request.method == 'POST'" + } + ] + } + ] + } diff --git a/testdata/filters/baggage_peer_metadata.yaml.tmpl b/testdata/filters/baggage_peer_metadata.yaml.tmpl new file mode 100644 index 00000000000..91d7f757ef5 --- /dev/null +++ b/testdata/filters/baggage_peer_metadata.yaml.tmpl @@ -0,0 +1,12 @@ +- name: waypoint_peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + downstream_discovery: + - baggage: {} + downstream_propagation: + - baggage: {} + upstream_discovery: + - upstream_filter_state: + peer_metadata_key: upstream_peer diff --git a/testdata/filters/client_mx_network_filter.yaml.tmpl b/testdata/filters/client_mx_network_filter.yaml.tmpl new file mode 100644 index 00000000000..b42a613f8fb --- /dev/null +++ b/testdata/filters/client_mx_network_filter.yaml.tmpl @@ -0,0 +1,13 @@ +- name: envoy.filters.network.upstream.metadata_exchange + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.tcp.metadataexchange.config.MetadataExchange + value: + protocol: mx-protocol +{{- if eq .Vars.EnableMetadataDiscovery "true" }} + enable_discovery: true +{{- end }} +{{- if eq .Vars.EnableAdditionalLabels "true" }} + additional_labels: + - role +{{- end }} diff --git a/testdata/filters/client_stats_network_filter.yaml.tmpl b/testdata/filters/client_stats_network_filter.yaml.tmpl new file mode 100644 index 00000000000..eadfeaa64cd --- /dev/null +++ b/testdata/filters/client_stats_network_filter.yaml.tmpl @@ -0,0 +1,27 @@ +- name: envoy.filters.network.wasm + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct +{{ if ne .Vars.WasmRuntime "" }} + type_url: envoy.extensions.filters.network.wasm.v3.Wasm + value: + config: + root_id: "stats_outbound" + vm_config: + runtime: envoy.wasm.runtime.null + code: + local: { inline_string: "envoy.wasm.stats" } + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { "debug": "false", "field_separator": ";.;", "tcp_reporting_duration": "1s" } +{{ else }} + type_url: type.googleapis.com/stats.PluginConfig + value: + tcp_reporting_duration: 1s + {{- if eq .Vars.EnableAdditionalLabels "true" }} + metrics: + - name: tcp_connections_opened_total + dimensions: + role: filter_state.upstream_peer.labels['role'] + {{- end }} +{{ end }} diff --git a/testdata/filters/client_wrong_mx_network_filter.yaml.tmpl b/testdata/filters/client_wrong_mx_network_filter.yaml.tmpl new file mode 100644 index 00000000000..e3e7ee9d440 --- /dev/null +++ b/testdata/filters/client_wrong_mx_network_filter.yaml.tmpl @@ -0,0 +1,6 @@ +- name: envoy.filters.network.upstream.metadata_exchange + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.tcp.metadataexchange.config.MetadataExchange + value: + protocol: mx-protocol1 diff --git a/testdata/filters/extension_config_inbound.yaml.tmpl b/testdata/filters/extension_config_inbound.yaml.tmpl new file mode 100644 index 00000000000..6a67d8fdd77 --- /dev/null +++ b/testdata/filters/extension_config_inbound.yaml.tmpl @@ -0,0 +1,24 @@ +- name: mx_inbound{{.N}} + config_discovery: + config_source: + api_config_source: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + type_urls: + - envoy.extensions.filters.http.wasm.v3.Wasm + - io.istio.http.peer_metadata.Config +- name: stats_inbound{{.N}} + config_discovery: + config_source: + api_config_source: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + type_urls: + - envoy.extensions.filters.http.wasm.v3.Wasm + - stats.PluginConfig diff --git a/testdata/filters/extension_config_outbound.yaml.tmpl b/testdata/filters/extension_config_outbound.yaml.tmpl new file mode 100644 index 00000000000..68a0340e7c9 --- /dev/null +++ b/testdata/filters/extension_config_outbound.yaml.tmpl @@ -0,0 +1,24 @@ +- name: mx_outbound{{.N}} + config_discovery: + config_source: + api_config_source: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + type_urls: + - envoy.extensions.filters.http.wasm.v3.Wasm + - io.istio.http.peer_metadata.Config +- name: stats_outbound{{.N}} + config_discovery: + config_source: + api_config_source: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + type_urls: + - envoy.extensions.filters.http.wasm.v3.Wasm + - stats.PluginConfig diff --git a/testdata/filters/grpc_stats.yaml b/testdata/filters/grpc_stats.yaml new file mode 100644 index 00000000000..5347f3587c2 --- /dev/null +++ b/testdata/filters/grpc_stats.yaml @@ -0,0 +1,5 @@ +- name: grpc_stats + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig + emit_filter_state: true + diff --git a/testdata/filters/mx_native_inbound.yaml.tmpl b/testdata/filters/mx_native_inbound.yaml.tmpl new file mode 100644 index 00000000000..89a6b830244 --- /dev/null +++ b/testdata/filters/mx_native_inbound.yaml.tmpl @@ -0,0 +1,9 @@ +- name: mx_inbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + downstream_discovery: + - istio_headers: {} + downstream_propagation: + - istio_headers: {} diff --git a/testdata/filters/mx_native_inbound_labels.yaml.tmpl b/testdata/filters/mx_native_inbound_labels.yaml.tmpl new file mode 100644 index 00000000000..6a5e45cceb9 --- /dev/null +++ b/testdata/filters/mx_native_inbound_labels.yaml.tmpl @@ -0,0 +1,11 @@ +- name: mx_inbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + downstream_discovery: + - istio_headers: {} + downstream_propagation: + - istio_headers: {} + additional_labels: + - role diff --git a/testdata/filters/mx_native_outbound.yaml.tmpl b/testdata/filters/mx_native_outbound.yaml.tmpl new file mode 100644 index 00000000000..2a42dacbcdb --- /dev/null +++ b/testdata/filters/mx_native_outbound.yaml.tmpl @@ -0,0 +1,10 @@ +- name: mx_outbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + upstream_discovery: + - istio_headers: {} + - workload_discovery: {} + upstream_propagation: + - istio_headers: {} diff --git a/testdata/filters/mx_native_outbound_labels.yaml.tmpl b/testdata/filters/mx_native_outbound_labels.yaml.tmpl new file mode 100644 index 00000000000..02ceb4c0034 --- /dev/null +++ b/testdata/filters/mx_native_outbound_labels.yaml.tmpl @@ -0,0 +1,11 @@ +- name: mx_outbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + upstream_discovery: + - istio_headers: {} + upstream_propagation: + - istio_headers: {} + additional_labels: + - role diff --git a/testdata/filters/mx_waypoint.yaml.tmpl b/testdata/filters/mx_waypoint.yaml.tmpl new file mode 100644 index 00000000000..6700ba65eef --- /dev/null +++ b/testdata/filters/mx_waypoint.yaml.tmpl @@ -0,0 +1,7 @@ +- name: mx_inbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + upstream_discovery: + - workload_discovery: {} diff --git a/testdata/filters/mx_waypoint_tcp.yaml.tmpl b/testdata/filters/mx_waypoint_tcp.yaml.tmpl new file mode 100644 index 00000000000..f24979a5602 --- /dev/null +++ b/testdata/filters/mx_waypoint_tcp.yaml.tmpl @@ -0,0 +1,7 @@ +- name: tc_mx_inbound{{.N}} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange + value: + protocol: "istio-peer-exchange" + enable_discovery: true diff --git a/testdata/filters/rbac.yaml.tmpl b/testdata/filters/rbac.yaml.tmpl new file mode 100644 index 00000000000..77f60b529fb --- /dev/null +++ b/testdata/filters/rbac.yaml.tmpl @@ -0,0 +1,13 @@ +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + rules: + action: DENY + policies: + ns[foo]-policy[httpbin-deny]-rule[0]: + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/rbac_dry_run_action_allow.yaml.tmpl b/testdata/filters/rbac_dry_run_action_allow.yaml.tmpl new file mode 100644 index 00000000000..5ec77cbda95 --- /dev/null +++ b/testdata/filters/rbac_dry_run_action_allow.yaml.tmpl @@ -0,0 +1,14 @@ +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + shadowRulesStatPrefix: istio_dry_run_allow_ + shadowRules: + action: ALLOW + policies: + ns[foo]-policy[httpbin-dryrun-allow]-rule[0]: + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/rbac_dry_run_action_both.yaml.tmpl b/testdata/filters/rbac_dry_run_action_both.yaml.tmpl new file mode 100644 index 00000000000..be380f8b7cf --- /dev/null +++ b/testdata/filters/rbac_dry_run_action_both.yaml.tmpl @@ -0,0 +1,28 @@ +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + shadowRulesStatPrefix: istio_dry_run_deny_ + shadowRules: + action: DENY + policies: + ns[foo]-policy[httpbin-dryrun-deny]-rule[0]: + permissions: + - any: true + principals: + - any: true +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + shadowRulesStatPrefix: istio_dry_run_allow_ + shadowRules: + action: ALLOW + policies: + ns[foo]-policy[httpbin-dryrun-allow]-rule[0]: + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/rbac_dry_run_action_deny.yaml.tmpl b/testdata/filters/rbac_dry_run_action_deny.yaml.tmpl new file mode 100644 index 00000000000..f3e2172d009 --- /dev/null +++ b/testdata/filters/rbac_dry_run_action_deny.yaml.tmpl @@ -0,0 +1,14 @@ +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + shadowRulesStatPrefix: istio_dry_run_deny_ + shadowRules: + action: DENY + policies: + ns[foo]-policy[httpbin-dryrun-deny]-rule[0]: + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/rbac_log.yaml.tmpl b/testdata/filters/rbac_log.yaml.tmpl new file mode 100644 index 00000000000..c475a00c395 --- /dev/null +++ b/testdata/filters/rbac_log.yaml.tmpl @@ -0,0 +1,13 @@ +- name: envoy.filters.http.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.http.rbac.v3.RBAC + value: + rules: + action: LOG + policies: + "test": + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/rbac_tcp.yaml.tmpl b/testdata/filters/rbac_tcp.yaml.tmpl new file mode 100644 index 00000000000..b506cbab7b6 --- /dev/null +++ b/testdata/filters/rbac_tcp.yaml.tmpl @@ -0,0 +1,30 @@ +- name: envoy.filters.network.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.rbac.v3.RBAC + value: + statPrefix: tcp. + shadowRulesStatPrefix: istio_dry_run_deny_ + shadowRules: + action: DENY + policies: + ns[foo]-policy[tcp-dryrun-deny]-rule[0]: + permissions: + - any: true + principals: + - any: true +- name: envoy.filters.network.rbac + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.rbac.v3.RBAC + value: + statPrefix: tcp. + shadowRulesStatPrefix: istio_dry_run_allow_ + shadowRules: + action: ALLOW + policies: + ns[foo]-policy[tcp-dryrun-allow]-rule[0]: + permissions: + - any: true + principals: + - any: true \ No newline at end of file diff --git a/testdata/filters/respond_with_baggage.yaml.tmpl b/testdata/filters/respond_with_baggage.yaml.tmpl new file mode 100644 index 00000000000..6b3a52c9108 --- /dev/null +++ b/testdata/filters/respond_with_baggage.yaml.tmpl @@ -0,0 +1,12 @@ +- name: envoy.filters.http.header_mutation + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation + value: + mutations: + response_mutations: + - append: + header: + key: "request-baggage" + value: "%FILTER_STATE(io.istio.baggage:PLAIN)%" + append_action: OVERWRITE_IF_EXISTS_OR_ADD diff --git a/testdata/filters/server_mx_network_filter.yaml.tmpl b/testdata/filters/server_mx_network_filter.yaml.tmpl new file mode 100644 index 00000000000..cedd3d2ec34 --- /dev/null +++ b/testdata/filters/server_mx_network_filter.yaml.tmpl @@ -0,0 +1,13 @@ +- name: envoy.filters.network.metadata_exchange + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.tcp.metadataexchange.config.MetadataExchange + value: + protocol: mx-protocol +{{- if eq .Vars.EnableMetadataDiscovery "true" }} + enable_discovery: true +{{- end }} +{{- if eq .Vars.EnableAdditionalLabels "true" }} + additional_labels: + - role +{{- end }} diff --git a/testdata/filters/server_stats_network_filter.yaml.tmpl b/testdata/filters/server_stats_network_filter.yaml.tmpl new file mode 100644 index 00000000000..7975a096b30 --- /dev/null +++ b/testdata/filters/server_stats_network_filter.yaml.tmpl @@ -0,0 +1,27 @@ +- name: envoy.filters.network.wasm + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct +{{ if ne .Vars.WasmRuntime "" }} + type_url: envoy.extensions.filters.network.wasm.v3.Wasm + value: + config: + root_id: "stats_inbound" + vm_config: + runtime: envoy.wasm.runtime.null + code: + local: { inline_string: "envoy.wasm.stats" } + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { "debug": "false", "field_separator": ";.;", "tcp_reporting_duration": "1s" } +{{ else }} + type_url: type.googleapis.com/stats.PluginConfig + value: + tcp_reporting_duration: 1s + {{- if eq .Vars.EnableAdditionalLabels "true" }} + metrics: + - name: tcp_connections_opened_total + dimensions: + role: filter_state.downstream_peer.labels['role'] + {{- end }} +{{ end }} diff --git a/testdata/filters/stats_inbound.yaml.tmpl b/testdata/filters/stats_inbound.yaml.tmpl new file mode 100644 index 00000000000..f4c612edb33 --- /dev/null +++ b/testdata/filters/stats_inbound.yaml.tmpl @@ -0,0 +1,23 @@ +- name: stats_inbound{{ .N }} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct +{{ if ne .Vars.WasmRuntime "" }} + type_url: envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + root_id: stats_inbound + vm_config: + vm_id: stats_inbound{{ .N }} + runtime: {{ .Vars.WasmRuntime }} + code: + local: { {{ .Vars.StatsFilterCode }} } + allow_precompiled: true + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + {{ .Vars.StatsFilterServerConfig | fill }} +{{ else }} + type_url: type.googleapis.com/stats.PluginConfig + value: + {{ .Vars.StatsFilterServerConfig | fill | indent 6 }} +{{ end }} diff --git a/testdata/filters/stats_outbound.yaml.tmpl b/testdata/filters/stats_outbound.yaml.tmpl new file mode 100644 index 00000000000..399272d83f5 --- /dev/null +++ b/testdata/filters/stats_outbound.yaml.tmpl @@ -0,0 +1,24 @@ +- name: stats_outbound{{ .N }} + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct +{{ if ne .Vars.WasmRuntime "" }} + type_url: envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + root_id: stats_outbound + vm_config: + vm_id: stats_outbound{{ .N }} + runtime: {{ .Vars.WasmRuntime }} + code: + local: { {{ .Vars.StatsFilterCode }} } + allow_precompiled: true + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + {{ .Vars.StatsFilterClientConfig | fill }} +{{ else }} + type_url: type.googleapis.com/stats.PluginConfig + value: +{{ .Vars.StatsFilterClientConfig | fill | indent 6 }} +{{ end }} + diff --git a/testdata/filters/upstream_peer_metadata.yaml.tmpl b/testdata/filters/upstream_peer_metadata.yaml.tmpl new file mode 100644 index 00000000000..ddac6578fb8 --- /dev/null +++ b/testdata/filters/upstream_peer_metadata.yaml.tmpl @@ -0,0 +1,5 @@ +- name: upstream_peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.network_filters.peer_metadata.UpstreamConfig + value: {} diff --git a/testdata/gce_client_node_metadata.json.tmpl b/testdata/gce_client_node_metadata.json.tmpl new file mode 100644 index 00000000000..22c6fbfadec --- /dev/null +++ b/testdata/gce_client_node_metadata.json.tmpl @@ -0,0 +1,29 @@ +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.5-dev", +"LABELS": { + "app": "productpage", + "version": "v1", + "service.istio.io/canonical-name": "productpage-v1", + "service.istio.io/canonical-revision": "version-1" +}, +"MESH_ID": "proj-123", +"NAME": "productpage-vm", +"NAMESPACE": "default", +"PLATFORM_METADATA": { + "gcp_gce_instance_id": "234215124341324123", + "gcp_location": "us-east4-b", + "gcp_project": "test-project", + "gcp_project_number": "23412341234", +}, +"SERVICE_ACCOUNT": "bookinfo-productpage", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"STS_PORT": "{{ .Vars.STSPort }}", +"WORKLOAD_NAME": "productpage-v1", diff --git a/testdata/gce_server_node_metadata.json.tmpl b/testdata/gce_server_node_metadata.json.tmpl new file mode 100644 index 00000000000..9c7539d16c2 --- /dev/null +++ b/testdata/gce_server_node_metadata.json.tmpl @@ -0,0 +1,30 @@ +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.5-dev", +"LABELS": { + "app": "ratings", + "version": "v1", + "service.istio.io/canonical-name": "ratings", + "service.istio.io/canonical-revision": "version-1" +}, +"MESH_ID": "proj-123", +"NAME": "ratings-v1-vm", +"NAMESPACE": "default", +"PLATFORM_METADATA": { + "gcp_gce_instance_id": "2342123412341234", + "gcp_gce_instance_created_by": "projects/23412341234/instanceGroupManagers/324234", + "gcp_location": "us-east4-b", + "gcp_project": "test-project", + "gcp_project_number": "23413241234", +}, +"SERVICE_ACCOUNT": "bookinfo-ratings", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STS_PORT": "{{ .Vars.STSPort }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"WORKLOAD_NAME": "ratings-v1", \ No newline at end of file diff --git a/testdata/generic_client_node_metadata.json.tmpl b/testdata/generic_client_node_metadata.json.tmpl new file mode 100644 index 00000000000..5dfbc187c71 --- /dev/null +++ b/testdata/generic_client_node_metadata.json.tmpl @@ -0,0 +1,29 @@ +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.14-dev", +"LABELS": { + "app": "productpage", + "version": "v1", + "service.istio.io/canonical-name": "productpage-v1", + "service.istio.io/canonical-revision": "version-1" +}, +"MESH_ID": "proj-123", +"NAME": "productpage-vm", +"NAMESPACE": "default", +"OWNER": "kubernetes://apis/networking.istio.io/v1alpha3/namespace/default/workloadgroups/productpage-v1", +"PLATFORM_METADATA": { + "gcp_location": "us-east4-b", + "gcp_project": "test-project", + "gcp_project_number": "23412341234", +}, +"SERVICE_ACCOUNT": "bookinfo-productpage", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +"STS_PORT": "{{ .Vars.STSPort }}", +"WORKLOAD_NAME": "productpage-v1", diff --git a/testdata/generic_server_node_metadata.json.tmpl b/testdata/generic_server_node_metadata.json.tmpl new file mode 100644 index 00000000000..2159250a8b9 --- /dev/null +++ b/testdata/generic_server_node_metadata.json.tmpl @@ -0,0 +1,38 @@ +"APP_CONTAINERS": "server", +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.14-dev", +"LABELS": { + "app": "ratings", + "version": "v1", + "service.istio.io/canonical-name": "ratings", + "service.istio.io/canonical-revision": "version-1" +}, +"MESH_ID": "proj-123", +"NAME": "ratings-v1-84975bc778-pxz2w", +"NAMESPACE": "default", +"CLUSTER_ID": "server-cluster", +"OWNER": "kubernetes://apis/networking.istio.io/v1alpha3/namespace/default/workloadgroups/ratings-v1", +"PLATFORM_METADATA": { + "gcp_location": "us-east4-b", + "gcp_project": "test-project" +}, +"POD_NAME": "ratings-v1-84975bc778-pxz2w", +"SERVICE_ACCOUNT": "bookinfo-ratings", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STS_PORT": "{{ .Vars.STSPort }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +{{- if .Vars.StackdriverLoggingExportIntervalSecs }} +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "{{ .Vars.StackdriverLoggingExportIntervalSecs }}", +{{- else }} +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +{{- end }} +{{- if .Vars.StackdriverTcpLogEntryTimeoutSecs }} +"STACKDRIVER_TCP_LOG_ENTRY_TIMEOUT_SECS": "{{ .Vars.StackdriverTcpLogEntryTimeoutSecs }}", +{{- end }} +"WORKLOAD_NAME": "ratings-v1", diff --git a/testdata/listener/baggage_peer_metadata.yaml.tmpl b/testdata/listener/baggage_peer_metadata.yaml.tmpl new file mode 100644 index 00000000000..8eefcd99482 --- /dev/null +++ b/testdata/listener/baggage_peer_metadata.yaml.tmpl @@ -0,0 +1,29 @@ +name: internal_outbound +use_original_dst: false +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: envoy.filters.network.peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.network_filters.peer_metadata.Config + value: + baggage_key: "io.istio.baggage" + - name: connect_originate + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: connect_originate + cluster: original_dst + tunneling_config: + hostname: "%DOWNSTREAM_LOCAL_ADDRESS%" + propagate_response_headers: true + headers_to_add: + - header: + key: "baggage" + value: "%FILTER_STATE(io.istio.baggage:PLAIN)%" + append_action: OVERWRITE_IF_EXISTS_OR_ADD diff --git a/testdata/listener/client.yaml.tmpl b/testdata/listener/client.yaml.tmpl new file mode 100644 index 00000000000..6f772aa4de0 --- /dev/null +++ b/testdata/listener/client.yaml.tmpl @@ -0,0 +1,38 @@ +{{- if ne .Vars.ClientListeners "" }} +{{ .Vars.ClientListeners }} +{{- else }} +name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +filter_chains: +- filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: client +{{ .Vars.ClientHTTPAccessLogs | fill | indent 6 }} + http_filters: +{{ .Vars.ClientHTTPFilters | fill | indent 6 }} + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: client + virtual_hosts: + - name: client + domains: ["*"] + routes: + - name: client_route + match: { prefix: / } + route: + {{- if .Vars.ServerClusterName }} + cluster: {{ .Vars.ServerClusterName}} + {{- else }} + cluster: server-outbound-cluster + {{- end }} + timeout: 0s +{{- end }} diff --git a/testdata/listener/client_passthrough.yaml.tmpl b/testdata/listener/client_passthrough.yaml.tmpl new file mode 100644 index 00000000000..b544283278b --- /dev/null +++ b/testdata/listener/client_passthrough.yaml.tmpl @@ -0,0 +1,46 @@ +name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +filter_chains: +- filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: client + http_filters: + - name: connect_authority + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.set_filter_state.v3.Config + value: + on_request_headers: + - object_key: envoy.filters.listener.original_dst.local_ip + format_string: + text_format_source: + inline_string: "%REQ(:AUTHORITY)%" + omit_empty_values: true + shared_with_upstream: ONCE + skip_if_empty: true + - object_key: envoy.filters.listener.original_dst.remote_ip + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_REMOTE_ADDRESS%" + shared_with_upstream: ONCE + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: client + virtual_hosts: + - name: client + domains: ["*"] + routes: + - name: client_route + match: { prefix: / } + route: + cluster: tcp_passthrough + timeout: 0s diff --git a/testdata/listener/internal_outbound.yaml.tmpl b/testdata/listener/internal_outbound.yaml.tmpl new file mode 100644 index 00000000000..4285add33e4 --- /dev/null +++ b/testdata/listener/internal_outbound.yaml.tmpl @@ -0,0 +1,16 @@ +name: internal_outbound +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: original_dst + tunneling_config: + hostname: "%DOWNSTREAM_LOCAL_ADDRESS%" + stat_prefix: outbound diff --git a/testdata/listener/server.yaml.tmpl b/testdata/listener/server.yaml.tmpl new file mode 100644 index 00000000000..d6e33746494 --- /dev/null +++ b/testdata/listener/server.yaml.tmpl @@ -0,0 +1,45 @@ +{{- if ne .Vars.ServerListeners "" }} +{{ .Vars.ServerListeners }} +{{- else }} +{{- if ne .Vars.ServerInternalAddress "" }} +name: {{ .Vars.ServerInternalAddress }} +{{- else }} +name: server +{{- end }} +traffic_direction: INBOUND +{{- if ne .Vars.ServerInternalAddress "" }} +internal_listener: {} +{{- else }} +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +{{- end }} +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: server +{{ .Vars.ServerHTTPAccessLogs | fill | indent 6 }} + http_filters: +{{ .Vars.ServerHTTPFilters | fill | indent 6 }} + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: server + virtual_hosts: + - name: server + domains: ["*"] + routes: + - name: server_route + match: { prefix: / } + route: + cluster: server-inbound-cluster + timeout: 0s +{{ .Vars.ServerRouteRateLimits | fill | indent 14 }} +{{ .Vars.ServerTLSContext | indent 2 }} +{{- end }} diff --git a/testdata/listener/tcp_client.yaml.tmpl b/testdata/listener/tcp_client.yaml.tmpl new file mode 100644 index 00000000000..ef63bfec52b --- /dev/null +++ b/testdata/listener/tcp_client.yaml.tmpl @@ -0,0 +1,27 @@ +name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +listener_filters: +- name: "envoy.filters.listener.tls_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector +- name: "envoy.filters.listener.http_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector +filter_chains: +- filters: +{{ .Vars.ClientNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: outbound_tcp + {{- if .Vars.ServerClusterName }} + cluster: {{ .Vars.ServerClusterName}} + {{- else }} + cluster: outbound|9080|tcp|server.default.svc.cluster.local + {{- end }} diff --git a/testdata/listener/tcp_passthrough.yaml.tmpl b/testdata/listener/tcp_passthrough.yaml.tmpl new file mode 100644 index 00000000000..ee9c08a47b0 --- /dev/null +++ b/testdata/listener/tcp_passthrough.yaml.tmpl @@ -0,0 +1,25 @@ +name: tcp_passthrough +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: connect_authority + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config + value: + on_new_connection: + - object_key: envoy.filters.listener.original_dst.local_ip + format_string: + text_format_source: + inline_string: "%FILTER_STATE(envoy.filters.listener.original_dst.local_ip:PLAIN)%" + shared_with_upstream: ONCE + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: internal_outbound + stat_prefix: tcp_passthrough diff --git a/testdata/listener/tcp_server.yaml.tmpl b/testdata/listener/tcp_server.yaml.tmpl new file mode 100644 index 00000000000..9960faccc38 --- /dev/null +++ b/testdata/listener/tcp_server.yaml.tmpl @@ -0,0 +1,24 @@ +name: server +traffic_direction: INBOUND +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +listener_filters: +- name: "envoy.filters.listener.tls_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector +- name: "envoy.filters.listener.http_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: inbound_tcp + cluster: inbound|9080|tcp|server.default.svc.cluster.local +{{ .Vars.ServerListenerTLSContext | indent 2 }} diff --git a/testdata/listener/tcp_waypoint_server.yaml.tmpl b/testdata/listener/tcp_waypoint_server.yaml.tmpl new file mode 100644 index 00000000000..7d44d34cd8c --- /dev/null +++ b/testdata/listener/tcp_waypoint_server.yaml.tmpl @@ -0,0 +1,29 @@ +{{- if ne .Vars.ServerListeners "" }} +{{ .Vars.ServerListeners }} +{{- else }} +{{- if ne .Vars.ServerInternalAddress "" }} +name: {{ .Vars.ServerInternalAddress }} +{{- else }} +name: server +{{- end }} +traffic_direction: INBOUND +{{- if ne .Vars.ServerInternalAddress "" }} +internal_listener: {} +{{- else }} +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +{{- end }} +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: server_inbound_tcp + cluster: server-inbound-cluster +{{ .Vars.ServerListenerTLSContext | indent 2 }} +{{- end }} diff --git a/testdata/listener/terminate_connect.yaml.tmpl b/testdata/listener/terminate_connect.yaml.tmpl new file mode 100644 index 00000000000..0066d069900 --- /dev/null +++ b/testdata/listener/terminate_connect.yaml.tmpl @@ -0,0 +1,131 @@ +name: terminate_connect +address: + socket_address: +{{ if eq .Vars.quic "true" }} + protocol: UDP +{{ end }} + address: 127.0.0.2 + port_value: {{ .Ports.ServerTunnelPort }} +{{ if eq .Vars.quic "true" }} +udp_listener_config: + quic_options: {} + downstream_socket_config: + prefer_gro: true +{{ end }} +filter_chains: +- filters: + # Capture SSL info for the internal listener passthrough + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: terminate_connect +{{ if eq .Vars.quic "true" }} + codec_type: HTTP3 +{{ end }} + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: + - "*" + routes: + - match: + connect_matcher: + {} + route: + cluster: internal_inbound + upgrade_configs: + - upgrade_type: CONNECT + connect_config: + {} + http_filters: + {{ if eq .Vars.quic "true" }} + # TODO: accessing uriSanPeerCertificates() triggers a crash in quiche version. + {{ else }} + - name: authn + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.set_filter_state.v3.Config + value: + on_request_headers: + - object_key: io.istio.peer_principal + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_PEER_URI_SAN%" + shared_with_upstream: ONCE + - object_key: io.istio.local_principal + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_LOCAL_URI_SAN%" + shared_with_upstream: ONCE + - object_key: io.istio.baggage + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%REQ(baggage)%" + shared_with_upstream: ONCE + {{ end }} + - name: peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + downstream_discovery: + - workload_discovery: {} + downstream_propagation: + - baggage: {} + shared_with_upstream: true + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + http2_protocol_options: + allow_connect: true + upgrade_configs: + - upgrade_type: CONNECT + transport_socket: +{{ if eq .Vars.quic "true" }} + name: quic + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstreamTransport + value: + downstream_tls_context: + common_tls_context: + tls_certificate_sds_secret_configs: + name: server + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } +# require_client_certificate: true # XXX: This setting is ignored ATM per @danzh. +{{ else }} + name: tls + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + value: + common_tls_context: + tls_certificate_sds_secret_configs: + name: server + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } + require_client_certificate: true +{{ end }} diff --git a/testdata/metric/basic_flow_client_requests.yaml.tmpl b/testdata/metric/basic_flow_client_requests.yaml.tmpl new file mode 100644 index 00000000000..91428ed0bd9 --- /dev/null +++ b/testdata/metric/basic_flow_client_requests.yaml.tmpl @@ -0,0 +1,12 @@ +name: envoy_listener_http_downstream_rq_xx +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: envoy_response_code_class + value: "2" + - name: envoy_http_conn_manager_prefix + value: client + - name: envoy_listener_address + value: "127.0.0.1_{{ .Ports.ClientPort }}" diff --git a/testdata/metric/basic_flow_client_tcp_connection.yaml.tmpl b/testdata/metric/basic_flow_client_tcp_connection.yaml.tmpl new file mode 100644 index 00000000000..d917e1f6f05 --- /dev/null +++ b/testdata/metric/basic_flow_client_tcp_connection.yaml.tmpl @@ -0,0 +1,8 @@ +name: envoy_tcp_downstream_cx_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.ConnectionCount }} + label: + - name: envoy_tcp_prefix + value: "outbound_tcp" diff --git a/testdata/metric/basic_flow_server_requests.yaml.tmpl b/testdata/metric/basic_flow_server_requests.yaml.tmpl new file mode 100644 index 00000000000..8aa2d15cdad --- /dev/null +++ b/testdata/metric/basic_flow_server_requests.yaml.tmpl @@ -0,0 +1,12 @@ +name: envoy_listener_http_downstream_rq_xx +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: envoy_response_code_class + value: "2" + - name: envoy_http_conn_manager_prefix + value: server + - name: envoy_listener_address + value: "127.0.0.2_{{ .Ports.ServerPort }}" diff --git a/testdata/metric/basic_flow_server_tcp_connection.yaml.tmpl b/testdata/metric/basic_flow_server_tcp_connection.yaml.tmpl new file mode 100644 index 00000000000..440279f03c7 --- /dev/null +++ b/testdata/metric/basic_flow_server_tcp_connection.yaml.tmpl @@ -0,0 +1,8 @@ +name: envoy_tcp_downstream_cx_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.ConnectionCount }} + label: + - name: envoy_tcp_prefix + value: "inbound_tcp" diff --git a/testdata/metric/client_custom_metric.yaml.tmpl b/testdata/metric/client_custom_metric.yaml.tmpl new file mode 100644 index 00000000000..ba363792ade --- /dev/null +++ b/testdata/metric/client_custom_metric.yaml.tmpl @@ -0,0 +1,10 @@ +name: istio_custom +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: proxy + - name: source_principal + value: malicious diff --git a/testdata/metric/client_disable_host_header_fallback.yaml.tmpl b/testdata/metric/client_disable_host_header_fallback.yaml.tmpl new file mode 100644 index 00000000000..1ed47de8912 --- /dev/null +++ b/testdata/metric/client_disable_host_header_fallback.yaml.tmpl @@ -0,0 +1,56 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: unknown + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: unknown + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: http + - name: response_code + value: "200" + - name: grpc_response_status + value: "" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/client_request_messages.yaml.tmpl b/testdata/metric/client_request_messages.yaml.tmpl new file mode 100644 index 00000000000..af069d83df1 --- /dev/null +++ b/testdata/metric/client_request_messages.yaml.tmpl @@ -0,0 +1,48 @@ +name: istio_request_messages_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestMessages }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: configurable_metric_a + value: v1 diff --git a/testdata/metric/client_request_total.yaml.tmpl b/testdata/metric/client_request_total.yaml.tmpl new file mode 100644 index 00000000000..33da594e6f5 --- /dev/null +++ b/testdata/metric/client_request_total.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/client_request_total_baggage.yaml.tmpl b/testdata/metric/client_request_total_baggage.yaml.tmpl new file mode 100644 index 00000000000..3bb2439b7a0 --- /dev/null +++ b/testdata/metric/client_request_total_baggage.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: waypoint + - name: source_workload + value: curl + - name: source_canonical_service + value: curl + - name: source_canonical_revision + value: v1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: curl + - name: source_version + value: v1 + - name: source_cluster + value: curl-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: none diff --git a/testdata/metric/client_request_total_cluster_metadata_precedence.yaml.tmpl b/testdata/metric/client_request_total_cluster_metadata_precedence.yaml.tmpl new file mode 100644 index 00000000000..c998ef84c53 --- /dev/null +++ b/testdata/metric/client_request_total_cluster_metadata_precedence.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: server + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/client_request_total_customized.yaml.tmpl b/testdata/metric/client_request_total_customized.yaml.tmpl new file mode 100644 index 00000000000..6ca53a4ea25 --- /dev/null +++ b/testdata/metric/client_request_total_customized.yaml.tmpl @@ -0,0 +1,54 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: _productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: _default + - name: source_principal + value: malicious + - name: source_app + value: _productpage + - name: source_version + value: _productpage + - name: source_cluster + value: client-cluster + - name: destination_workload + value: "unknown" + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_version + value: _v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: _ratings + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: HTTP/1.1 + - name: response_code + value: "200" + - name: connection_security_policy + value: unknown + - name: configurable_metric_a + value: gateway + - name: route_name + value: client_route,server-outbound-cluster,server diff --git a/testdata/metric/client_request_total_endpoint_labels.yaml.tmpl b/testdata/metric/client_request_total_endpoint_labels.yaml.tmpl new file mode 100644 index 00000000000..f001f299c16 --- /dev/null +++ b/testdata/metric/client_request_total_endpoint_labels.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: unknown + - name: destination_version + value: unknown + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/client_request_total_labels.yaml.tmpl b/testdata/metric/client_request_total_labels.yaml.tmpl new file mode 100644 index 00000000000..ed2a98e93c7 --- /dev/null +++ b/testdata/metric/client_request_total_labels.yaml.tmpl @@ -0,0 +1,62 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown + - name: role + value: server diff --git a/testdata/metric/client_response_messages.yaml.tmpl b/testdata/metric/client_response_messages.yaml.tmpl new file mode 100644 index 00000000000..f932407f1d9 --- /dev/null +++ b/testdata/metric/client_response_messages.yaml.tmpl @@ -0,0 +1,48 @@ +name: istio_response_messages_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.ResponseMessages }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: configurable_metric_a + value: v1 diff --git a/testdata/metric/client_sidecar_connect_request_total.yaml.tmpl b/testdata/metric/client_sidecar_connect_request_total.yaml.tmpl new file mode 100644 index 00000000000..d263ebb6d0c --- /dev/null +++ b/testdata/metric/client_sidecar_connect_request_total.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.global/ns/default/sa/ratings + - name: destination_app + value: ratings + - name: destination_version + value: version-1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: ratings-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown # Because we can't verify the source principal (dumb reason) diff --git a/testdata/metric/envoy_bug_failures.yaml b/testdata/metric/envoy_bug_failures.yaml new file mode 100644 index 00000000000..666500f9987 --- /dev/null +++ b/testdata/metric/envoy_bug_failures.yaml @@ -0,0 +1,5 @@ +name: envoy_server_envoy_bug_failures +type: COUNTER +metric: +- counter: + value: 0 diff --git a/testdata/metric/host_header_fallback.yaml.tmpl b/testdata/metric/host_header_fallback.yaml.tmpl new file mode 100644 index 00000000000..45dd46a2a9c --- /dev/null +++ b/testdata/metric/host_header_fallback.yaml.tmpl @@ -0,0 +1,56 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: 127.0.0.1:{{ .Ports.ClientPort }} + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: 127.0.0.1:{{ .Ports.ClientPort }} + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: http + - name: response_code + value: "200" + - name: grpc_response_status + value: "" + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/istio_build.yaml b/testdata/metric/istio_build.yaml new file mode 100644 index 00000000000..3032a42734d --- /dev/null +++ b/testdata/metric/istio_build.yaml @@ -0,0 +1,10 @@ +name: istio_build +type: GAUGE +metric: +- gauge: + value: 1 + label: + - name: component + value: proxy + - name: tag + value: 1.5-dev \ No newline at end of file diff --git a/testdata/metric/server_disable_host_header_fallback.yaml.tmpl b/testdata/metric/server_disable_host_header_fallback.yaml.tmpl new file mode 100644 index 00000000000..027b0ee349e --- /dev/null +++ b/testdata/metric/server_disable_host_header_fallback.yaml.tmpl @@ -0,0 +1,56 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: ratings + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: ratings + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: http + - name: response_code + value: "200" + - name: grpc_response_status + value: "" + - name: response_flags + value: "-" + - name: connection_security_policy + value: none diff --git a/testdata/metric/server_request_messages.yaml.tmpl b/testdata/metric/server_request_messages.yaml.tmpl new file mode 100644 index 00000000000..c3f01681ee7 --- /dev/null +++ b/testdata/metric/server_request_messages.yaml.tmpl @@ -0,0 +1,46 @@ +name: istio_request_messages_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestMessages }} + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster diff --git a/testdata/metric/server_request_total.yaml.tmpl b/testdata/metric/server_request_total.yaml.tmpl new file mode 100644 index 00000000000..2eb4a899e7e --- /dev/null +++ b/testdata/metric/server_request_total.yaml.tmpl @@ -0,0 +1,66 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + {{- if .Vars.ResponseCodeClass }} + value: {{ .Vars.ResponseCodeClass }} + {{- else if .Vars.ResponseCode }} + value: "{{ .Vars.ResponseCode }}" + {{- else }} + value: "200" + {{- end }} + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: none diff --git a/testdata/metric/server_request_total_labels.yaml.tmpl b/testdata/metric/server_request_total_labels.yaml.tmpl new file mode 100644 index 00000000000..3528dadd7c0 --- /dev/null +++ b/testdata/metric/server_request_total_labels.yaml.tmpl @@ -0,0 +1,62 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: none + - name: role + value: client diff --git a/testdata/metric/server_response_messages.yaml.tmpl b/testdata/metric/server_response_messages.yaml.tmpl new file mode 100644 index 00000000000..6b774ddc2d6 --- /dev/null +++ b/testdata/metric/server_response_messages.yaml.tmpl @@ -0,0 +1,46 @@ +name: istio_response_messages_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.ResponseMessages }} + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: unknown + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster diff --git a/testdata/metric/server_waypoint_proxy_connect_connections_opened_total.yaml.tmpl b/testdata/metric/server_waypoint_proxy_connect_connections_opened_total.yaml.tmpl new file mode 100644 index 00000000000..2706e55be2d --- /dev/null +++ b/testdata/metric/server_waypoint_proxy_connect_connections_opened_total.yaml.tmpl @@ -0,0 +1,52 @@ +name: istio_tcp_connections_opened_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: waypoint + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: unknown + - name: source_canonical_revision + value: latest + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: unknown + - name: source_version + value: unknown + - name: source_cluster + value: unknown + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.global/ns/default/sa/ratings + - name: destination_app + value: ratings + - name: destination_version + value: version-1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: ratings-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/server_waypoint_proxy_connect_emptymeta_request_total.yaml.tmpl b/testdata/metric/server_waypoint_proxy_connect_emptymeta_request_total.yaml.tmpl new file mode 100644 index 00000000000..5413f765856 --- /dev/null +++ b/testdata/metric/server_waypoint_proxy_connect_emptymeta_request_total.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: waypoint + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: unknown + - name: source_canonical_revision + value: latest + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: unknown + - name: source_version + value: unknown + - name: source_cluster + value: unknown + - name: destination_workload + value: unknown + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/default + - name: destination_app + value: unknown + - name: destination_version + value: unknown + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: unknown + - name: destination_canonical_revision + value: unknown + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: unknown + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/server_waypoint_proxy_connect_request_total.yaml.tmpl b/testdata/metric/server_waypoint_proxy_connect_request_total.yaml.tmpl new file mode 100644 index 00000000000..7062669fe76 --- /dev/null +++ b/testdata/metric/server_waypoint_proxy_connect_request_total.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: waypoint + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: unknown + - name: source_canonical_revision + value: latest + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: unknown + - name: source_version + value: unknown + - name: source_cluster + value: unknown + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.global/ns/default/sa/ratings + - name: destination_app + value: ratings + - name: destination_version + value: version-1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: ratings-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/server_waypoint_proxy_request_total.yaml.tmpl b/testdata/metric/server_waypoint_proxy_request_total.yaml.tmpl new file mode 100644 index 00000000000..df018ce9ecd --- /dev/null +++ b/testdata/metric/server_waypoint_proxy_request_total.yaml.tmpl @@ -0,0 +1,60 @@ +name: istio_requests_total +type: COUNTER +metric: +- counter: + value: {{ .Vars.RequestCount }} + label: + - name: reporter + value: waypoint + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.global/ns/default/sa/ratings + - name: destination_app + value: ratings + - name: destination_version + value: version-1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: ratings-cluster + - name: request_protocol + {{- if .Vars.GrpcResponseStatus }} + value: grpc + {{- else }} + value: http + {{- end }} + - name: response_code + value: "200" + - name: grpc_response_status + value: "{{ .Vars.GrpcResponseStatus }}" + - name: response_flags + value: "-" + - name: connection_security_policy + value: none diff --git a/testdata/metric/tcp_client_connection_close.yaml.tmpl b/testdata/metric/tcp_client_connection_close.yaml.tmpl new file mode 100644 index 00000000000..6691d1d748e --- /dev/null +++ b/testdata/metric/tcp_client_connection_close.yaml.tmpl @@ -0,0 +1,57 @@ +name: istio_tcp_connections_closed_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings +{{- if eq .Vars.AppVersionFallback "true" }} + - name: destination_version + value: version-1 +{{- else }} + - name: destination_version + value: v1 +{{- end }} + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/tcp_client_connection_open.yaml.tmpl b/testdata/metric/tcp_client_connection_open.yaml.tmpl new file mode 100644 index 00000000000..0ea5bdf7c42 --- /dev/null +++ b/testdata/metric/tcp_client_connection_open.yaml.tmpl @@ -0,0 +1,61 @@ +name: istio_tcp_connections_opened_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings +{{- if eq .Vars.AppVersionFallback "true" }} + - name: destination_version + value: version-1 +{{- else }} + - name: destination_version + value: v1 +{{- end }} + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown +{{- if eq .Vars.EnableAdditionalLabels "true" }} + - name: role + value: unknown +{{- end }} diff --git a/testdata/metric/tcp_client_received_bytes.yaml.tmpl b/testdata/metric/tcp_client_received_bytes.yaml.tmpl new file mode 100644 index 00000000000..f3e1ece315d --- /dev/null +++ b/testdata/metric/tcp_client_received_bytes.yaml.tmpl @@ -0,0 +1,57 @@ +name: istio_tcp_received_bytes_total +type: COUNTER +metric: +- counter: + value: 60 + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings +{{- if eq .Vars.AppVersionFallback "true" }} + - name: destination_version + value: version-1 +{{- else }} + - name: destination_version + value: v1 +{{- end }} + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/tcp_client_sent_bytes.yaml.tmpl b/testdata/metric/tcp_client_sent_bytes.yaml.tmpl new file mode 100644 index 00000000000..cb1a8670c82 --- /dev/null +++ b/testdata/metric/tcp_client_sent_bytes.yaml.tmpl @@ -0,0 +1,57 @@ +name: istio_tcp_sent_bytes_total +type: COUNTER +metric: +- counter: + value: 120 + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings +{{- if eq .Vars.AppVersionFallback "true" }} + - name: destination_version + value: version-1 +{{- else }} + - name: destination_version + value: v1 +{{- end }} + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/tcp_client_sent_bytes_unknown.yaml.tmpl b/testdata/metric/tcp_client_sent_bytes_unknown.yaml.tmpl new file mode 100644 index 00000000000..b8ef83874c2 --- /dev/null +++ b/testdata/metric/tcp_client_sent_bytes_unknown.yaml.tmpl @@ -0,0 +1,57 @@ +name: istio_tcp_sent_bytes_total +type: COUNTER +metric: +- counter: + value: 0 + label: + - name: reporter + value: source + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: unknown + - name: source_app + value: productpage + - name: source_version + value: v1 + - name: source_cluster + value: client-cluster + - name: destination_workload + value: unknown + - name: destination_workload_namespace + value: unknown + - name: destination_principal + value: unknown + - name: destination_app + value: unknown +{{- if eq .Vars.AppVersionFallback "true" }} + - name: destination_version + value: version-1 +{{- else }} + - name: destination_version + value: unknown +{{- end }} + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: unknown + - name: destination_canonical_revision + value: latest + - name: destination_service_name + value: server + - name: destination_service_namespace + value: unknown + - name: destination_cluster + value: unknown + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: unknown diff --git a/testdata/metric/tcp_server_connection_close.yaml.tmpl b/testdata/metric/tcp_server_connection_close.yaml.tmpl new file mode 100644 index 00000000000..a49d480ee15 --- /dev/null +++ b/testdata/metric/tcp_server_connection_close.yaml.tmpl @@ -0,0 +1,59 @@ +name: istio_tcp_connections_closed_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client +{{- if eq .Vars.AppVersionFallback "true" }} + - name: source_app + value: productpage-v1 + - name: source_version + value: version-1 +{{- else }} + - name: source_app + value: productpage + - name: source_version + value: v1 +{{- end }} + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/tcp_server_connection_open.yaml.tmpl b/testdata/metric/tcp_server_connection_open.yaml.tmpl new file mode 100644 index 00000000000..c357095d9b2 --- /dev/null +++ b/testdata/metric/tcp_server_connection_open.yaml.tmpl @@ -0,0 +1,63 @@ +name: istio_tcp_connections_opened_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client +{{- if eq .Vars.AppVersionFallback "true" }} + - name: source_app + value: productpage-v1 + - name: source_version + value: version-1 +{{- else }} + - name: source_app + value: productpage + - name: source_version + value: v1 +{{- end }} + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls +{{- if eq .Vars.EnableAdditionalLabels "true" }} + - name: role + value: client +{{- end }} diff --git a/testdata/metric/tcp_server_connection_open_without_mx.yaml.tmpl b/testdata/metric/tcp_server_connection_open_without_mx.yaml.tmpl new file mode 100644 index 00000000000..5247c8ad583 --- /dev/null +++ b/testdata/metric/tcp_server_connection_open_without_mx.yaml.tmpl @@ -0,0 +1,56 @@ +name: istio_tcp_connections_opened_total +type: COUNTER +metric: +- counter: + value: 10 + label: + - name: reporter + value: destination + - name: source_workload + value: unknown + - name: source_canonical_service + value: unknown + - name: source_canonical_revision + value: latest + - name: source_workload_namespace +{{ if ne .Vars.WasmRuntime "" }} + value: unknown +{{ else }} + value: default +{{ end }} + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client + - name: source_app + value: unknown + - name: source_version + value: unknown + - name: source_cluster + value: unknown + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/tcp_server_mx_stats_alpn_found.yaml.tmpl b/testdata/metric/tcp_server_mx_stats_alpn_found.yaml.tmpl new file mode 100644 index 00000000000..adf0db15627 --- /dev/null +++ b/testdata/metric/tcp_server_mx_stats_alpn_found.yaml.tmpl @@ -0,0 +1,5 @@ +name: envoy_metadata_exchange_alpn_protocol_found +type: COUNTER +metric: +- counter: + value: 10 diff --git a/testdata/metric/tcp_server_mx_stats_alpn_not_found.yaml.tmpl b/testdata/metric/tcp_server_mx_stats_alpn_not_found.yaml.tmpl new file mode 100644 index 00000000000..4c07c4e43ec --- /dev/null +++ b/testdata/metric/tcp_server_mx_stats_alpn_not_found.yaml.tmpl @@ -0,0 +1,5 @@ +name: envoy_metadata_exchange_alpn_protocol_not_found +type: COUNTER +metric: +- counter: + value: 10 diff --git a/testdata/metric/tcp_server_mx_stats_metadata_added.yaml.tmpl b/testdata/metric/tcp_server_mx_stats_metadata_added.yaml.tmpl new file mode 100644 index 00000000000..7bbe030a1b8 --- /dev/null +++ b/testdata/metric/tcp_server_mx_stats_metadata_added.yaml.tmpl @@ -0,0 +1,5 @@ +name: envoy_metadata_exchange_metadata_added +type: COUNTER +metric: +- counter: + value: 10 diff --git a/testdata/metric/tcp_server_received_bytes.yaml.tmpl b/testdata/metric/tcp_server_received_bytes.yaml.tmpl new file mode 100644 index 00000000000..f905d075044 --- /dev/null +++ b/testdata/metric/tcp_server_received_bytes.yaml.tmpl @@ -0,0 +1,59 @@ +name: istio_tcp_received_bytes_total +type: COUNTER +metric: +- counter: + value: 60 + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client +{{- if eq .Vars.AppVersionFallback "true" }} + - name: source_app + value: productpage-v1 + - name: source_version + value: version-1 +{{- else }} + - name: source_app + value: productpage + - name: source_version + value: v1 +{{- end }} + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/metric/tcp_server_sent_bytes.yaml.tmpl b/testdata/metric/tcp_server_sent_bytes.yaml.tmpl new file mode 100644 index 00000000000..0e0c7ba15e0 --- /dev/null +++ b/testdata/metric/tcp_server_sent_bytes.yaml.tmpl @@ -0,0 +1,59 @@ +name: istio_tcp_sent_bytes_total +type: COUNTER +metric: +- counter: + value: 120 + label: + - name: reporter + value: destination + - name: source_workload + value: productpage-v1 + - name: source_canonical_service + value: productpage-v1 + - name: source_canonical_revision + value: version-1 + - name: source_workload_namespace + value: default + - name: source_principal + value: spiffe://cluster.local/ns/default/sa/client +{{- if eq .Vars.AppVersionFallback "true" }} + - name: source_app + value: productpage-v1 + - name: source_version + value: version-1 +{{- else }} + - name: source_app + value: productpage + - name: source_version + value: v1 +{{- end }} + - name: source_cluster + value: client-cluster + - name: destination_workload + value: ratings-v1 + - name: destination_workload_namespace + value: default + - name: destination_principal + value: spiffe://cluster.local/ns/default/sa/server + - name: destination_app + value: ratings + - name: destination_version + value: v1 + - name: destination_service + value: server.default.svc.cluster.local + - name: destination_canonical_service + value: ratings + - name: destination_canonical_revision + value: version-1 + - name: destination_service_name + value: server + - name: destination_service_namespace + value: default + - name: destination_cluster + value: server-cluster + - name: request_protocol + value: tcp + - name: response_flags + value: "-" + - name: connection_security_policy + value: mutual_tls diff --git a/testdata/secret/client.yaml.tmpl b/testdata/secret/client.yaml.tmpl new file mode 100644 index 00000000000..bbca5ca6b2c --- /dev/null +++ b/testdata/secret/client.yaml.tmpl @@ -0,0 +1,4 @@ +name: client +tls_certificate: + certificate_chain: { filename: "testdata/certs/client.cert" } + private_key: { filename: "testdata/certs/client-key.cert" } diff --git a/testdata/secret/server.yaml.tmpl b/testdata/secret/server.yaml.tmpl new file mode 100644 index 00000000000..152ebad031d --- /dev/null +++ b/testdata/secret/server.yaml.tmpl @@ -0,0 +1,5 @@ +name: server +tls_certificate: + certificate_chain: { filename: "testdata/certs/server.cert" } + private_key: { filename: "testdata/certs/server-key.cert" } + diff --git a/testdata/server_node_metadata.json.tmpl b/testdata/server_node_metadata.json.tmpl new file mode 100644 index 00000000000..93ffc71aad5 --- /dev/null +++ b/testdata/server_node_metadata.json.tmpl @@ -0,0 +1,46 @@ +"APP_CONTAINERS": "server", +"CONFIG_NAMESPACE": "default", +"INCLUDE_INBOUND_PORTS": "9080", +"INSTANCE_IPS": "10.52.0.34,fe80::a075:11ff:fe5e:f1cd", +"INTERCEPTION_MODE": "REDIRECT", +"ISTIO_PROXY_SHA": "istio-proxy:47e4559b8e4f0d516c0d17b233d127a3deb3d7ce", +"ISTIO_VERSION": "1.5-dev", +"LABELS": { + "app": "ratings", + "pod-template-hash": "84975bc778", + "version": "v1", + "service.istio.io/canonical-name": "ratings", + "service.istio.io/canonical-revision": "version-1", + "role": "server" +}, +"MESH_ID": "proj-123", +"NAME": "ratings-v1-84975bc778-pxz2w", +"NAMESPACE": "default", +"CLUSTER_ID": "server-cluster", +"OWNER": "kubernetes://apis/apps/v1/namespaces/default/deployments/ratings-v1", +"PLATFORM_METADATA": { + "gcp_gke_cluster_name": "test-cluster", + "gcp_location": "us-east4-b", + "gcp_project": "test-project" +}, +"POD_NAME": "ratings-v1-84975bc778-pxz2w", +"SERVICE_ACCOUNT": "bookinfo-ratings", +"SECURE_STACKDRIVER_ENDPOINT": "localhost:{{.Vars.SDPort }}", +"STACKDRIVER_ROOT_CA_FILE": "{{ .Vars.StackdriverRootCAFile }}", +"STACKDRIVER_TOKEN_FILE": "{{ .Vars.StackdriverTokenFile }}", +"STS_PORT": "{{ .Vars.STSPort }}", +"STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "20", +{{- if .Vars.StackdriverLoggingExportIntervalSecs }} +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "{{ .Vars.StackdriverLoggingExportIntervalSecs }}", +{{- else }} +"STACKDRIVER_LOGGING_EXPORT_INTERVAL_SECS": "20", +{{- end }} +{{- if .Vars.StackdriverTcpLogEntryTimeoutSecs }} +"STACKDRIVER_TCP_LOG_ENTRY_TIMEOUT_SECS": "{{ .Vars.StackdriverTcpLogEntryTimeoutSecs }}", +{{- end }} +"WORKLOAD_NAME": "ratings-v1", +"app": "ratings", +"istio": "sidecar", +"kubernetes.io/limit-ranger": "LimitRanger plugin set: cpu request for container ratings", +"pod-template-hash": "84975bc778", +"version": "v1" diff --git a/testdata/server_waypoint_proxy_node_metadata.json.tmpl b/testdata/server_waypoint_proxy_node_metadata.json.tmpl new file mode 100644 index 00000000000..eb1d02fb2c2 --- /dev/null +++ b/testdata/server_waypoint_proxy_node_metadata.json.tmpl @@ -0,0 +1,3 @@ +"MESH_ID": "proj-123", +"NAMESPACE": "default", +"CLUSTER_ID": "server-cluster", diff --git a/testdata/stats/client_additional_labels.yaml b/testdata/stats/client_additional_labels.yaml new file mode 100644 index 00000000000..e6a3fef34f6 --- /dev/null +++ b/testdata/stats/client_additional_labels.yaml @@ -0,0 +1,4 @@ +metrics: + - name: requests_total + dimensions: + role: filter_state.upstream_peer.labels['role'] diff --git a/testdata/stats/client_config.yaml b/testdata/stats/client_config.yaml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/testdata/stats/client_config.yaml @@ -0,0 +1 @@ +{} diff --git a/testdata/stats/client_config_customized.yaml.tmpl b/testdata/stats/client_config_customized.yaml.tmpl new file mode 100644 index 00000000000..39460b0977d --- /dev/null +++ b/testdata/stats/client_config_customized.yaml.tmpl @@ -0,0 +1,35 @@ +definitions: +- name: custom + value: "1" + type: COUNTER +metrics: + - name: request_duration_milliseconds + drop: true + - name: custom + dimensions: + reporter: "'proxy'" + - tags_to_remove: + - response_flags + - source_principal + - name: requests_total + dimensions: + configurable_metric_a: "'gateway'" + source_workload: "'_' + xds.node.metadata['WORKLOAD_NAME']" + source_workload_namespace: "'_' + xds.node.metadata['NAMESPACE']" + source_app: "'_' + xds.node.metadata['LABELS'].app" + source_version: "'_' + xds.node.metadata['LABELS'].app" # same as above expression + request_protocol: request.protocol + destination_version: "'_' + xds.node.metadata.LABELS.version" + destination_service_namespace: "'_' + filter_state.upstream_peer.service" + destination_app: "cannot _ parse | {{ .N }}" + destination_workload: "cannot_evaluate" + route_name: xds.route_name + "," + xds.cluster_name + "," + xds.cluster_metadata.filter_metadata.istio.services[0].name + tags_to_remove: + - grpc_response_status + - name: request_bytes + dimensions: + configurable_metric_b: "'test'" + tags_to_remove: + - reporter + - dimensions: + source_principal: "'malicious'" diff --git a/testdata/stats/client_config_disable_header_fallback.yaml b/testdata/stats/client_config_disable_header_fallback.yaml new file mode 100644 index 00000000000..507d73fbd7a --- /dev/null +++ b/testdata/stats/client_config_disable_header_fallback.yaml @@ -0,0 +1 @@ +disable_host_header_fallback: "true" diff --git a/testdata/stats/client_config_grpc.yaml.tmpl b/testdata/stats/client_config_grpc.yaml.tmpl new file mode 100644 index 00000000000..ed2792abcd8 --- /dev/null +++ b/testdata/stats/client_config_grpc.yaml.tmpl @@ -0,0 +1,4 @@ +tcp_reporting_duration: 1s +metrics: + - dimensions: + configurable_metric_a: xds.node.metadata.LABELS.version diff --git a/testdata/stats/request_classification_config.yaml b/testdata/stats/request_classification_config.yaml new file mode 100644 index 00000000000..40d96bfab9e --- /dev/null +++ b/testdata/stats/request_classification_config.yaml @@ -0,0 +1,4 @@ +metrics: + - name: requests_total + dimensions: + response_code: filter_state['wasm.istio_responseClass'] diff --git a/testdata/stats/server_additional_labels.yaml b/testdata/stats/server_additional_labels.yaml new file mode 100644 index 00000000000..b040fcb90c2 --- /dev/null +++ b/testdata/stats/server_additional_labels.yaml @@ -0,0 +1,4 @@ +metrics: + - name: requests_total + dimensions: + role: filter_state.downstream_peer.labels['role'] diff --git a/testdata/stats/server_config.yaml b/testdata/stats/server_config.yaml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/testdata/stats/server_config.yaml @@ -0,0 +1 @@ +{} diff --git a/testdata/stats/server_config_disable_header_fallback.yaml b/testdata/stats/server_config_disable_header_fallback.yaml new file mode 100644 index 00000000000..507d73fbd7a --- /dev/null +++ b/testdata/stats/server_config_disable_header_fallback.yaml @@ -0,0 +1 @@ +disable_host_header_fallback: "true" diff --git a/testdata/stats/server_config_grpc.yaml.tmpl b/testdata/stats/server_config_grpc.yaml.tmpl new file mode 100644 index 00000000000..c96be3188c2 --- /dev/null +++ b/testdata/stats/server_config_grpc.yaml.tmpl @@ -0,0 +1 @@ +tcp_reporting_duration: 1s diff --git a/testdata/stats/server_waypoint_proxy_config.yaml b/testdata/stats/server_waypoint_proxy_config.yaml new file mode 100644 index 00000000000..4b118bb1972 --- /dev/null +++ b/testdata/stats/server_waypoint_proxy_config.yaml @@ -0,0 +1 @@ +reporter: SERVER_GATEWAY diff --git a/testdata/testdata.gen.go b/testdata/testdata.gen.go new file mode 100644 index 00000000000..2394a643395 --- /dev/null +++ b/testdata/testdata.gen.go @@ -0,0 +1,1294 @@ +// Code generated for package testdata by go-bindata DO NOT EDIT. (@generated) +// sources: +// bootstrap/client.yaml.tmpl +// bootstrap/client_cluster_metadata_precedence.yaml.tmpl +// bootstrap/otel_stats.yaml.tmpl +// bootstrap/server.yaml.tmpl +// bootstrap/stats.yaml.tmpl +// bootstrap/stats_expiry.yaml.tmpl +// listener/baggage_peer_metadata.yaml.tmpl +// listener/client.yaml.tmpl +// listener/client_passthrough.yaml.tmpl +// listener/internal_outbound.yaml.tmpl +// listener/server.yaml.tmpl +// listener/tcp_client.yaml.tmpl +// listener/tcp_passthrough.yaml.tmpl +// listener/tcp_server.yaml.tmpl +// listener/tcp_waypoint_server.yaml.tmpl +// listener/terminate_connect.yaml.tmpl +package testdata + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// Mode return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _bootstrapClientYamlTmpl = []byte(`node: + id: client + cluster: test-cluster + metadata: { {{ .Vars.ClientMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-outbound-cluster + connect_timeout: 5s + type: STATIC + http2_protocol_options: {} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default + {{- end }} + load_assignment: + cluster_name: server-outbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} + {{- if eq .Vars.EnableEndpointMetadata "true" }} + metadata: + filter_metadata: + istio: + workload: ratings-v1;default;ratings;version-1;server-cluster + {{- end }} +{{ .Vars.ClientTLSContext | indent 4 }} +{{ .Vars.ClientStaticCluster | indent 2 }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} +`) + +func bootstrapClientYamlTmplBytes() ([]byte, error) { + return _bootstrapClientYamlTmpl, nil +} + +func bootstrapClientYamlTmpl() (*asset, error) { + bytes, err := bootstrapClientYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/client.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _bootstrapClient_cluster_metadata_precedenceYamlTmpl = []byte(`node: + id: client + cluster: test-cluster + metadata: { {{ .Vars.ClientMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-outbound-cluster + connect_timeout: 5s + type: STATIC + http2_protocol_options: {} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: server + {{- end }} + load_assignment: + cluster_name: server-outbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} + {{- if eq .Vars.EnableEndpointMetadata "true" }} + metadata: + filter_metadata: + istio: + workload: ratings-v1;default;ratings;version-1;server-cluster + {{- end }} +{{ .Vars.ClientTLSContext | indent 4 }} +{{ .Vars.ClientStaticCluster | indent 2 }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} +`) + +func bootstrapClient_cluster_metadata_precedenceYamlTmplBytes() ([]byte, error) { + return _bootstrapClient_cluster_metadata_precedenceYamlTmpl, nil +} + +func bootstrapClient_cluster_metadata_precedenceYamlTmpl() (*asset, error) { + bytes, err := bootstrapClient_cluster_metadata_precedenceYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/client_cluster_metadata_precedence.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _bootstrapOtel_statsYamlTmpl = []byte(`stats_sinks: +- name: otel + typed_config: + "@type": type.googleapis.com/envoy.extensions.stat_sinks.open_telemetry.v3.SinkConfig + grpc_service: + envoy_grpc: + cluster_name: otel +stats_config: + stats_matcher: + inclusion_list: + patterns: + - prefix: "istiocustom." +`) + +func bootstrapOtel_statsYamlTmplBytes() ([]byte, error) { + return _bootstrapOtel_statsYamlTmpl, nil +} + +func bootstrapOtel_statsYamlTmpl() (*asset, error) { + bytes, err := bootstrapOtel_statsYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/otel_stats.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _bootstrapServerYamlTmpl = []byte(`node: + id: server + cluster: test-cluster + metadata: { {{ .Vars.ServerMetadata | fill }} } +admin: + access_log_path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ServerAdmin }} +{{ .Vars.StatsConfig }} +dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + cds_config: + ads: {} + resource_api_version: V3 + lds_config: + ads: {} + resource_api_version: V3 +static_resources: + clusters: + - connect_timeout: 5s + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.XDSPort }} + http2_protocol_options: {} + name: xds_cluster + - name: server-inbound-cluster + connect_timeout: 5s + type: STATIC + {{- if eq .Vars.UsingGrpcBackend "true" }} + http2_protocol_options: {} + {{- end }} + {{- if ne .Vars.ElideServerMetadata "true" }} + metadata: + filter_metadata: + istio: + services: + - host: server.default.svc.cluster.local + name: server + namespace: default + {{- end }} + load_assignment: + cluster_name: server-inbound-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} +{{ .Vars.ServerStaticCluster | indent 2 }} +{{- if ne .Vars.DisableDirectResponse "true" }} + listeners: + - name: staticreply + address: + socket_address: + address: 127.0.0.3 + port_value: {{ .Ports.BackendPort }} + filter_chains: + - filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: staticreply + codec_type: AUTO + route_config: + name: staticreply + virtual_hosts: + - name: staticreply + domains: ["*"] + routes: + - match: + prefix: "/" + direct_response: + {{- if .Vars.DirectResponseCode }} + status: {{ .Vars.DirectResponseCode }} + {{- else }} + status: 200 + {{- end }} + body: + inline_string: "hello, world!" + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +{{- end }} +bootstrap_extensions: +- name: envoy.bootstrap.internal_listener + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.bootstrap.internal_listener.v3.InternalListener +{{- if eq .Vars.EnableMetadataDiscovery "true" }} +- name: metadata_discovery + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/istio.workload.BootstrapExtension + value: + config_source: + ads: {} +{{- end }} +`) + +func bootstrapServerYamlTmplBytes() ([]byte, error) { + return _bootstrapServerYamlTmpl, nil +} + +func bootstrapServerYamlTmpl() (*asset, error) { + bytes, err := bootstrapServerYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/server.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _bootstrapStatsYamlTmpl = []byte(`stats_config: + use_all_default_tags: true + stats_tags: + - tag_name: "reporter" + regex: "(reporter=\\.=(.*?);\\.;)" + - tag_name: "source_namespace" + regex: "(source_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_workload" + regex: "(source_workload=\\.=(.*?);\\.;)" + - tag_name: "source_canonical_service" + regex: "(source_canonical_service=\\.=(.*?);\\.;)" + - tag_name: "source_canonical_revision" + regex: "(source_canonical_revision=\\.=(.*?);\\.;)" + - tag_name: "source_workload_namespace" + regex: "(source_workload_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_principal" + regex: "(source_principal=\\.=(.*?);\\.;)" + - tag_name: "source_app" + regex: "(source_app=\\.=(.*?);\\.;)" + - tag_name: "source_version" + regex: "(source_version=\\.=(.*?);\\.;)" + - tag_name: "destination_namespace" + regex: "(destination_namespace=\\.=(.*?);\\.;)" + - tag_name: "source_cluster" + regex: "(source_cluster=\\.=(.*?);\\.;)" + - tag_name: "destination_workload" + regex: "(destination_workload=\\.=(.*?);\\.;)" + - tag_name: "destination_workload_namespace" + regex: "(destination_workload_namespace=\\.=(.*?);\\.;)" + - tag_name: "destination_principal" + regex: "(destination_principal=\\.=(.*?);\\.;)" + - tag_name: "destination_app" + regex: "(destination_app=\\.=(.*?);\\.;)" + - tag_name: "destination_version" + regex: "(destination_version=\\.=(.*?);\\.;)" + - tag_name: "destination_service" + regex: "(destination_service=\\.=(.*?);\\.;)" + - tag_name: "destination_canonical_service" + regex: "(destination_canonical_service=\\.=(.*?);\\.;)" + - tag_name: "destination_canonical_revision" + regex: "(destination_canonical_revision=\\.=(.*?);\\.;)" + - tag_name: "destination_service_name" + regex: "(destination_service_name=\\.=(.*?);\\.;)" + - tag_name: "destination_service_namespace" + regex: "(destination_service_namespace=\\.=(.*?);\\.;)" + - tag_name: "destination_cluster" + regex: "(destination_cluster=\\.=(.*?);\\.;)" + - tag_name: "request_protocol" + regex: "(request_protocol=\\.=(.*?);\\.;)" + - tag_name: "response_code" + regex: "(response_code=\\.=(.*?);\\.;)|_rq(_(\\.d{3}))$" + - tag_name: "grpc_response_status" + regex: "(grpc_response_status=\\.=(.*?);\\.;)" + - tag_name: "response_flags" + regex: "(response_flags=\\.=(.*?);\\.;)" + - tag_name: "connection_security_policy" + regex: "(connection_security_policy=\\.=(.*?);\\.;)" +# Extra regexes used for configurable metrics + - tag_name: "configurable_metric_a" + regex: "(configurable_metric_a=\\.=(.*?);\\.;)" + - tag_name: "configurable_metric_b" + regex: "(configurable_metric_b=\\.=(.*?);\\.;)" + - tag_name: "route_name" + regex: "(route_name=\\.=(.*?);\\.;)" +# Internal monitoring + - tag_name: "cache" + regex: "(cache\\.(.*?)\\.)" + - tag_name: "component" + regex: "(component\\.(.*?)\\.)" + - tag_name: "tag" + regex: "(tag\\.(.*?);\\.)" + - tag_name: "wasm_filter" + regex: "(wasm_filter\\.(.*?)\\.)" +`) + +func bootstrapStatsYamlTmplBytes() ([]byte, error) { + return _bootstrapStatsYamlTmpl, nil +} + +func bootstrapStatsYamlTmpl() (*asset, error) { + bytes, err := bootstrapStatsYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/stats.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _bootstrapStats_expiryYamlTmpl = []byte(`stats_flush_interval: 1s +stats_eviction_interval: 1s +`) + +func bootstrapStats_expiryYamlTmplBytes() ([]byte, error) { + return _bootstrapStats_expiryYamlTmpl, nil +} + +func bootstrapStats_expiryYamlTmpl() (*asset, error) { + bytes, err := bootstrapStats_expiryYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bootstrap/stats_expiry.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerBaggage_peer_metadataYamlTmpl = []byte(`name: internal_outbound +use_original_dst: false +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: envoy.filters.network.peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.network_filters.peer_metadata.Config + value: + baggage_key: "io.istio.baggage" + - name: connect_originate + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: connect_originate + cluster: original_dst + tunneling_config: + hostname: "%DOWNSTREAM_LOCAL_ADDRESS%" + propagate_response_headers: true + headers_to_add: + - header: + key: "baggage" + value: "%FILTER_STATE(io.istio.baggage:PLAIN)%" + append_action: OVERWRITE_IF_EXISTS_OR_ADD +`) + +func listenerBaggage_peer_metadataYamlTmplBytes() ([]byte, error) { + return _listenerBaggage_peer_metadataYamlTmpl, nil +} + +func listenerBaggage_peer_metadataYamlTmpl() (*asset, error) { + bytes, err := listenerBaggage_peer_metadataYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/baggage_peer_metadata.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerClientYamlTmpl = []byte(`{{- if ne .Vars.ClientListeners "" }} +{{ .Vars.ClientListeners }} +{{- else }} +name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +filter_chains: +- filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: client +{{ .Vars.ClientHTTPAccessLogs | fill | indent 6 }} + http_filters: +{{ .Vars.ClientHTTPFilters | fill | indent 6 }} + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: client + virtual_hosts: + - name: client + domains: ["*"] + routes: + - name: client_route + match: { prefix: / } + route: + {{- if .Vars.ServerClusterName }} + cluster: {{ .Vars.ServerClusterName}} + {{- else }} + cluster: server-outbound-cluster + {{- end }} + timeout: 0s +{{- end }} +`) + +func listenerClientYamlTmplBytes() ([]byte, error) { + return _listenerClientYamlTmpl, nil +} + +func listenerClientYamlTmpl() (*asset, error) { + bytes, err := listenerClientYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/client.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerClient_passthroughYamlTmpl = []byte(`name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +filter_chains: +- filters: + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: client + http_filters: + - name: connect_authority + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.set_filter_state.v3.Config + value: + on_request_headers: + - object_key: envoy.filters.listener.original_dst.local_ip + format_string: + text_format_source: + inline_string: "%REQ(:AUTHORITY)%" + omit_empty_values: true + shared_with_upstream: ONCE + skip_if_empty: true + - object_key: envoy.filters.listener.original_dst.remote_ip + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_REMOTE_ADDRESS%" + shared_with_upstream: ONCE + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: client + virtual_hosts: + - name: client + domains: ["*"] + routes: + - name: client_route + match: { prefix: / } + route: + cluster: tcp_passthrough + timeout: 0s +`) + +func listenerClient_passthroughYamlTmplBytes() ([]byte, error) { + return _listenerClient_passthroughYamlTmpl, nil +} + +func listenerClient_passthroughYamlTmpl() (*asset, error) { + bytes, err := listenerClient_passthroughYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/client_passthrough.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerInternal_outboundYamlTmpl = []byte(`name: internal_outbound +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: original_dst + tunneling_config: + hostname: "%DOWNSTREAM_LOCAL_ADDRESS%" + stat_prefix: outbound +`) + +func listenerInternal_outboundYamlTmplBytes() ([]byte, error) { + return _listenerInternal_outboundYamlTmpl, nil +} + +func listenerInternal_outboundYamlTmpl() (*asset, error) { + bytes, err := listenerInternal_outboundYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/internal_outbound.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerServerYamlTmpl = []byte(`{{- if ne .Vars.ServerListeners "" }} +{{ .Vars.ServerListeners }} +{{- else }} +{{- if ne .Vars.ServerInternalAddress "" }} +name: {{ .Vars.ServerInternalAddress }} +{{- else }} +name: server +{{- end }} +traffic_direction: INBOUND +{{- if ne .Vars.ServerInternalAddress "" }} +internal_listener: {} +{{- else }} +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +{{- end }} +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: http + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: server +{{ .Vars.ServerHTTPAccessLogs | fill | indent 6 }} + http_filters: +{{ .Vars.ServerHTTPFilters | fill | indent 6 }} + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: server + virtual_hosts: + - name: server + domains: ["*"] + routes: + - name: server_route + match: { prefix: / } + route: + cluster: server-inbound-cluster + timeout: 0s +{{ .Vars.ServerRouteRateLimits | fill | indent 14 }} +{{ .Vars.ServerTLSContext | indent 2 }} +{{- end }} +`) + +func listenerServerYamlTmplBytes() ([]byte, error) { + return _listenerServerYamlTmpl, nil +} + +func listenerServerYamlTmpl() (*asset, error) { + bytes, err := listenerServerYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/server.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerTcp_clientYamlTmpl = []byte(`name: client +traffic_direction: OUTBOUND +address: + socket_address: + address: 127.0.0.1 + port_value: {{ .Ports.ClientPort }} +listener_filters: +- name: "envoy.filters.listener.tls_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector +- name: "envoy.filters.listener.http_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector +filter_chains: +- filters: +{{ .Vars.ClientNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: outbound_tcp + {{- if .Vars.ServerClusterName }} + cluster: {{ .Vars.ServerClusterName}} + {{- else }} + cluster: outbound|9080|tcp|server.default.svc.cluster.local + {{- end }} +`) + +func listenerTcp_clientYamlTmplBytes() ([]byte, error) { + return _listenerTcp_clientYamlTmpl, nil +} + +func listenerTcp_clientYamlTmpl() (*asset, error) { + bytes, err := listenerTcp_clientYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/tcp_client.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerTcp_passthroughYamlTmpl = []byte(`name: tcp_passthrough +internal_listener: {} +listener_filters: +- name: set_dst_address + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst +filter_chains: +- filters: + - name: connect_authority + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.network.set_filter_state.v3.Config + value: + on_new_connection: + - object_key: envoy.filters.listener.original_dst.local_ip + format_string: + text_format_source: + inline_string: "%FILTER_STATE(envoy.filters.listener.original_dst.local_ip:PLAIN)%" + shared_with_upstream: ONCE + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: internal_outbound + stat_prefix: tcp_passthrough +`) + +func listenerTcp_passthroughYamlTmplBytes() ([]byte, error) { + return _listenerTcp_passthroughYamlTmpl, nil +} + +func listenerTcp_passthroughYamlTmpl() (*asset, error) { + bytes, err := listenerTcp_passthroughYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/tcp_passthrough.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerTcp_serverYamlTmpl = []byte(`name: server +traffic_direction: INBOUND +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +listener_filters: +- name: "envoy.filters.listener.tls_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector +- name: "envoy.filters.listener.http_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.http_inspector.v3.HttpInspector +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: inbound_tcp + cluster: inbound|9080|tcp|server.default.svc.cluster.local +{{ .Vars.ServerListenerTLSContext | indent 2 }} +`) + +func listenerTcp_serverYamlTmplBytes() ([]byte, error) { + return _listenerTcp_serverYamlTmpl, nil +} + +func listenerTcp_serverYamlTmpl() (*asset, error) { + bytes, err := listenerTcp_serverYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/tcp_server.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerTcp_waypoint_serverYamlTmpl = []byte(`{{- if ne .Vars.ServerListeners "" }} +{{ .Vars.ServerListeners }} +{{- else }} +{{- if ne .Vars.ServerInternalAddress "" }} +name: {{ .Vars.ServerInternalAddress }} +{{- else }} +name: server +{{- end }} +traffic_direction: INBOUND +{{- if ne .Vars.ServerInternalAddress "" }} +internal_listener: {} +{{- else }} +address: + socket_address: + address: 127.0.0.2 + port_value: {{ .Ports.ServerPort }} +{{- end }} +filter_chains: +- filters: +{{ .Vars.ServerNetworkFilters | fill | indent 2 }} + - name: tcp_proxy + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + value: + stat_prefix: server_inbound_tcp + cluster: server-inbound-cluster +{{ .Vars.ServerListenerTLSContext | indent 2 }} +{{- end }} +`) + +func listenerTcp_waypoint_serverYamlTmplBytes() ([]byte, error) { + return _listenerTcp_waypoint_serverYamlTmpl, nil +} + +func listenerTcp_waypoint_serverYamlTmpl() (*asset, error) { + bytes, err := listenerTcp_waypoint_serverYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/tcp_waypoint_server.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _listenerTerminate_connectYamlTmpl = []byte(`name: terminate_connect +address: + socket_address: +{{ if eq .Vars.quic "true" }} + protocol: UDP +{{ end }} + address: 127.0.0.2 + port_value: {{ .Ports.ServerTunnelPort }} +{{ if eq .Vars.quic "true" }} +udp_listener_config: + quic_options: {} + downstream_socket_config: + prefer_gro: true +{{ end }} +filter_chains: +- filters: + # Capture SSL info for the internal listener passthrough + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: terminate_connect +{{ if eq .Vars.quic "true" }} + codec_type: HTTP3 +{{ end }} + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: + - "*" + routes: + - match: + connect_matcher: + {} + route: + cluster: internal_inbound + upgrade_configs: + - upgrade_type: CONNECT + connect_config: + {} + http_filters: + {{ if eq .Vars.quic "true" }} + # TODO: accessing uriSanPeerCertificates() triggers a crash in quiche version. + {{ else }} + - name: authn + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.set_filter_state.v3.Config + value: + on_request_headers: + - object_key: io.istio.peer_principal + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_PEER_URI_SAN%" + shared_with_upstream: ONCE + - object_key: io.istio.local_principal + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%DOWNSTREAM_LOCAL_URI_SAN%" + shared_with_upstream: ONCE + - object_key: io.istio.baggage + factory_key: envoy.string + format_string: + text_format_source: + inline_string: "%REQ(baggage)%" + shared_with_upstream: ONCE + {{ end }} + - name: peer_metadata + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/io.istio.http.peer_metadata.Config + value: + downstream_discovery: + - workload_discovery: {} + downstream_propagation: + - baggage: {} + shared_with_upstream: true + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + http2_protocol_options: + allow_connect: true + upgrade_configs: + - upgrade_type: CONNECT + transport_socket: +{{ if eq .Vars.quic "true" }} + name: quic + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstreamTransport + value: + downstream_tls_context: + common_tls_context: + tls_certificate_sds_secret_configs: + name: server + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } +# require_client_certificate: true # XXX: This setting is ignored ATM per @danzh. +{{ else }} + name: tls + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + value: + common_tls_context: + tls_certificate_sds_secret_configs: + name: server + sds_config: + api_config_source: + api_type: GRPC + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + transport_api_version: V3 + resource_api_version: V3 + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } + require_client_certificate: true +{{ end }} +`) + +func listenerTerminate_connectYamlTmplBytes() ([]byte, error) { + return _listenerTerminate_connectYamlTmpl, nil +} + +func listenerTerminate_connectYamlTmpl() (*asset, error) { + bytes, err := listenerTerminate_connectYamlTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "listener/terminate_connect.yaml.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "bootstrap/client.yaml.tmpl": bootstrapClientYamlTmpl, + "bootstrap/client_cluster_metadata_precedence.yaml.tmpl": bootstrapClient_cluster_metadata_precedenceYamlTmpl, + "bootstrap/otel_stats.yaml.tmpl": bootstrapOtel_statsYamlTmpl, + "bootstrap/server.yaml.tmpl": bootstrapServerYamlTmpl, + "bootstrap/stats.yaml.tmpl": bootstrapStatsYamlTmpl, + "bootstrap/stats_expiry.yaml.tmpl": bootstrapStats_expiryYamlTmpl, + "listener/baggage_peer_metadata.yaml.tmpl": listenerBaggage_peer_metadataYamlTmpl, + "listener/client.yaml.tmpl": listenerClientYamlTmpl, + "listener/client_passthrough.yaml.tmpl": listenerClient_passthroughYamlTmpl, + "listener/internal_outbound.yaml.tmpl": listenerInternal_outboundYamlTmpl, + "listener/server.yaml.tmpl": listenerServerYamlTmpl, + "listener/tcp_client.yaml.tmpl": listenerTcp_clientYamlTmpl, + "listener/tcp_passthrough.yaml.tmpl": listenerTcp_passthroughYamlTmpl, + "listener/tcp_server.yaml.tmpl": listenerTcp_serverYamlTmpl, + "listener/tcp_waypoint_server.yaml.tmpl": listenerTcp_waypoint_serverYamlTmpl, + "listener/terminate_connect.yaml.tmpl": listenerTerminate_connectYamlTmpl, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "bootstrap": &bintree{nil, map[string]*bintree{ + "client.yaml.tmpl": &bintree{bootstrapClientYamlTmpl, map[string]*bintree{}}, + "client_cluster_metadata_precedence.yaml.tmpl": &bintree{bootstrapClient_cluster_metadata_precedenceYamlTmpl, map[string]*bintree{}}, + "otel_stats.yaml.tmpl": &bintree{bootstrapOtel_statsYamlTmpl, map[string]*bintree{}}, + "server.yaml.tmpl": &bintree{bootstrapServerYamlTmpl, map[string]*bintree{}}, + "stats.yaml.tmpl": &bintree{bootstrapStatsYamlTmpl, map[string]*bintree{}}, + "stats_expiry.yaml.tmpl": &bintree{bootstrapStats_expiryYamlTmpl, map[string]*bintree{}}, + }}, + "listener": &bintree{nil, map[string]*bintree{ + "baggage_peer_metadata.yaml.tmpl": &bintree{listenerBaggage_peer_metadataYamlTmpl, map[string]*bintree{}}, + "client.yaml.tmpl": &bintree{listenerClientYamlTmpl, map[string]*bintree{}}, + "client_passthrough.yaml.tmpl": &bintree{listenerClient_passthroughYamlTmpl, map[string]*bintree{}}, + "internal_outbound.yaml.tmpl": &bintree{listenerInternal_outboundYamlTmpl, map[string]*bintree{}}, + "server.yaml.tmpl": &bintree{listenerServerYamlTmpl, map[string]*bintree{}}, + "tcp_client.yaml.tmpl": &bintree{listenerTcp_clientYamlTmpl, map[string]*bintree{}}, + "tcp_passthrough.yaml.tmpl": &bintree{listenerTcp_passthroughYamlTmpl, map[string]*bintree{}}, + "tcp_server.yaml.tmpl": &bintree{listenerTcp_serverYamlTmpl, map[string]*bintree{}}, + "tcp_waypoint_server.yaml.tmpl": &bintree{listenerTcp_waypoint_serverYamlTmpl, map[string]*bintree{}}, + "terminate_connect.yaml.tmpl": &bintree{listenerTerminate_connectYamlTmpl, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/testdata/transport_socket/client.yaml.tmpl b/testdata/transport_socket/client.yaml.tmpl new file mode 100644 index 00000000000..857e8e22865 --- /dev/null +++ b/testdata/transport_socket/client.yaml.tmpl @@ -0,0 +1,17 @@ +transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + value: + common_tls_context: + {{- if .Vars.AlpnProtocol }} + alpn_protocols: + - {{ .Vars.AlpnProtocol }} + {{- end }} + tls_certificates: + - certificate_chain: { filename: "testdata/certs/client.cert" } + private_key: { filename: "testdata/certs/client-key.cert" } + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } + sni: server.com diff --git a/testdata/transport_socket/server.yaml.tmpl b/testdata/transport_socket/server.yaml.tmpl new file mode 100644 index 00000000000..54f8b9427e4 --- /dev/null +++ b/testdata/transport_socket/server.yaml.tmpl @@ -0,0 +1,17 @@ +transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + value: + common_tls_context: + {{- if .Vars.AlpnProtocol }} + alpn_protocols: + - {{ .Vars.AlpnProtocol }} + {{- end }} + tls_certificates: + - certificate_chain: { filename: "testdata/certs/server.cert" } + private_key: { filename: "testdata/certs/server-key.cert" } + validation_context: + trusted_ca: { filename: "testdata/certs/root.cert" } + require_client_certificate: true diff --git a/tools/bazel.rc b/tools/bazel.rc deleted file mode 100644 index 4ce27056965..00000000000 --- a/tools/bazel.rc +++ /dev/null @@ -1,57 +0,0 @@ -# Copied from https://github.com/envoyproxy/envoy/blob/master/tools/bazel.rc -# Envoy specific Bazel build/test options. - -#build --workspace_status_command=bazel/get_workspace_status - -# Basic ASAN/UBSAN that works for gcc -build:asan --define ENVOY_CONFIG_ASAN=1 -build:asan --copt -fsanitize=address,undefined -build:asan --linkopt -fsanitize=address,undefined -build:asan --copt -fno-sanitize=vptr -build:asan --linkopt -fno-sanitize=vptr -build:asan --linkopt -ldl -build:asan --define tcmalloc=disabled -build:asan --build_tag_filters=-no_asan -build:asan --test_tag_filters=-no_asan -build:asan --define signal_trace=disabled - -# Clang 5.0 ASAN -build:clang-asan --define ENVOY_CONFIG_ASAN=1 -build:clang-asan --copt -D__SANITIZE_ADDRESS__ -build:clang-asan --copt -fsanitize=address,undefined -build:clang-asan --linkopt -fsanitize=address,undefined -build:clang-asan --copt -fno-sanitize=vptr -build:clang-asan --linkopt -fno-sanitize=vptr -build:clang-asan --copt -fno-sanitize-recover=all -build:clang-asan --linkopt -ldl -build:clang-asan --define tcmalloc=disabled -build:clang-asan --build_tag_filters=-no_asan -build:clang-asan --test_tag_filters=-no_asan -build:clang-asan --define signal_trace=disabled -build:clang-asan --test_env=ASAN_SYMBOLIZER_PATH - -# Clang 5.0 TSAN -build:clang-tsan --define ENVOY_CONFIG_TSAN=1 -build:clang-tsan --copt -fsanitize=thread -build:clang-tsan --linkopt -fsanitize=thread -build:clang-tsan --define tcmalloc=disabled - -# Clang 5.0 MSAN - broken today since we need to rebuild lib[std]c++ and external deps with MSAN -# support (see https://github.com/envoyproxy/envoy/issues/443). -build:clang-msan --define ENVOY_CONFIG_MSAN=1 -build:clang-msan --copt -fsanitize=memory -build:clang-msan --linkopt -fsanitize=memory -build:clang-msan --define tcmalloc=disabled -build:clang-msan --copt -fsanitize-memory-track-origins=2 - -# Test options -test --test_env=HEAPCHECK=normal --test_env=PPROF_PATH - -# Release builds -build:release -c opt -build:release --strip=always - -# Add compile option for all C++ files -build --cxxopt -Wnon-virtual-dtor -build --cxxopt -Wformat -build --cxxopt -Wformat-security diff --git a/tools/bazel.rc.ci b/tools/bazel.rc.ci deleted file mode 100644 index 664f7b13380..00000000000 --- a/tools/bazel.rc.ci +++ /dev/null @@ -1,8 +0,0 @@ -# This is from Bazel's former travis setup, to avoid blowing up the RAM usage. -startup --host_jvm_args=-Xmx8192m -startup --host_jvm_args=-Xms8192m -startup --batch - -# This is so we understand failures better -build --verbose_failures - diff --git a/tools/bazel.rc.cloudbuilder b/tools/bazel.rc.cloudbuilder deleted file mode 100644 index 664f7b13380..00000000000 --- a/tools/bazel.rc.cloudbuilder +++ /dev/null @@ -1,8 +0,0 @@ -# This is from Bazel's former travis setup, to avoid blowing up the RAM usage. -startup --host_jvm_args=-Xmx8192m -startup --host_jvm_args=-Xms8192m -startup --batch - -# This is so we understand failures better -build --verbose_failures - diff --git a/tools/deb/BUILD b/tools/deb/BUILD deleted file mode 100644 index 1dc63c246fc..00000000000 --- a/tools/deb/BUILD +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar", "pkg_deb") - -# TODO: decide the proper location for binaries and configs and update the file. -# Current layout for binaries matches 0.1 and docker images. - -pkg_tar( - name = "envoy-bin", - files = [ - "//src/envoy", - ], - mode = "0755", - package_dir = "/usr/local/bin", -) - -pkg_tar( - name = "istio-conf", - files = [ - "envoy.json", - "sidecar.env", - ], - mode = "0755", - package_dir = "/var/lib/istio/envoy", -) - -pkg_tar( - name = "debian-data", - extension = "tar.gz", - deps = [ - ":envoy-bin", - ":istio-conf", - ], -) - -pkg_deb( - name = "istio-proxy", - architecture = "amd64", - built_using = "bazel", - conffiles_file = "conffiles", - data = ":debian-data", - description_file = "description", - homepage = "http://istio.io", - maintainer = "The Istio Authors ", - package = "istio-proxy", - postinst = "postinst.sh", - tags = ["manual"], - version_file = "//:deb_version", -) diff --git a/tools/deb/README.md b/tools/deb/README.md deleted file mode 100644 index 514c3d00420..00000000000 --- a/tools/deb/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# Implementation notes - -## Pilot protocol - -To debug the behavior of the VM, it helps to understand the pilot protocol and expectations. - -Envoy requests to pilot are controlled by 2 parameters: -- "--service-cluster" - typically istio-proxy, from mesh config -- "--service-node" - identifies the node. This was the IP address in Istio 0.1 - -The first format used "|" separator, with 3 components: -- IPAddress -- ID -- Domain - -In 0.2, the service name is a 4-tuple, with ~ separator. A proxy using 0.2 is not compatible with -a 0.1 pilot nor the reverse. - -- TYPE: sidecar | ingress | egress - determines the generated config. Defaults to sidecar, set as first CLI param for the agent. -- IPAdress - same as in 0.1, the IP of the endpoint/pod. This determines which local listeners to enable. - Populated from $INSTANCE_IP or --ip -- ID: the full target.uid, will be added as attribute to the server. Set as "--id" or $POD_NAME.$POD_NAMESPACE -- Domain: mesh domain, from $POD_NAMESPACE - -The parameters are set by pilot-agent, based on environment variables. - -UID is typically kubernetes://$PODNAME.$NAMESPACE - -# LDS - or listeners - -There are few types. - -1. The main bound listener on 15001 - -2. For each HTTP port in the cluster, there is one listener on tcp://0.0.0.0/port - defining mixer attributes -and asking for route config. This is for outbound requests. - -3. For services running on the local machine. Only returned if pilot knows that the IP of the endpoint belongs -to a service. The address is the ClusterIP of the service. - - ```json - - { - "address": "tcp://10.138.0.6:80", // the service IP, in cluster range - } - ... - "route_config": { - "virtual_hosts": [ - { - "name": "inbound|80", - "domains": [ - "*" - ], - "routes": [ - { - "prefix": "/", - "cluster": "in.80", - "opaque_config": { - "mixer_control": "on", - "mixer_forward": "off" - } - } - ] - } - ] - }, - - ``` -4. For every TCP service, there is a listener on SERVICE_IP:port address, with a destionatio_ip_list. - - -# RDS - or routes - -For each OUTBOUND port will define the route to the right cluster. - -curl -v http://istio-pilot:8080/v1/routes/$PORT/istio-proxy/sidecar~$[VM_IP]~$[SVC].default~cluster.local - -Note that the route generated includes an 'out' cluster - even if the IP is an endpoint for the machine. -That's because the RDS is applied on the 0.0.0.0 port - which is used for outbound. The more specific -service port will capture inbound traffic, and that has an explicit route to the inbound cluster. - -```json - -{ - "virtual_hosts": [ - { - "name": "istio-ingress.default.svc.cluster.local|http", - "domains": [ - "istio-ingress.default.svc:80", - "istio-ingress.default.svc", - "istio-ingress.default.svc.cluster:80", - "istio-ingress.default.svc.cluster", - "istio-ingress.default.svc.cluster.local:80", - "istio-ingress.default.svc.cluster.local", - "10.51.255.104:80", - "10.51.255.104" - ], - "routes": [ - { - "prefix": "/", - "cluster": "out.fd518f1d0ba070c47739cbf6b191f85eb1cdda3d" - } - ] - }, - { - "name": "rawvm.default.svc.cluster.local|http", - "domains": [ - "rawvm.default.svc:80", - "rawvm.default.svc", - "rawvm.default.svc.cluster:80", - "rawvm.default.svc.cluster", - "rawvm.default.svc.cluster.local:80", - "rawvm.default.svc.cluster.local", - "10.51.244.162:80", - "10.51.244.162" - ], - "routes": [ - { - "prefix": "/", - "cluster": "out.a94f05e263fb8c0693d9ec4d8d61c9e3b15e2c05" - } - ] - } - ] -} - -``` - -# LDS - or services (clusters in envoy) - -curl -v http://istio-pilot:8080/v1/clusters/istio-proxy/sidecar~$[VM_IP]~$[SVC].default~cluster.local - -If services are provided on the IP, expect to see an in cluster: - -```json -"clusters": [ - { - "name": "in.80", - "connect_timeout_ms": 1000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://127.0.0.1:80" - } - ] - }, - -``` - -In addition, for each name that shows up in RDS, there is one here: -The service name is the only useful info - should match the one in routes. - -```json - { - "name": "out.01b3502fc7b29750c3b185358aec68fcbb4b9cf6", - "service_name": "istio-pilot.default.svc.cluster.local|http-discovery", - "connect_timeout_ms": 1000, - "type": "sds", - "lb_type": "round_robin" - }, - -``` - - -# SDS - endpoints - -For each service, this resolves to the endpoint list. The parameter it the service name, -result should show each endpoint IP. - -This is the same as K8S endpoint API, except in different format. - - curl -v 'http://istio-pilot:8080/v1/registration/zipkin.default.svc.cluster.local|http' - - diff --git a/tools/deb/conffiles b/tools/deb/conffiles deleted file mode 100644 index d400fb6914b..00000000000 --- a/tools/deb/conffiles +++ /dev/null @@ -1,3 +0,0 @@ -/var/lib/istio/envoy/sidecar.env -/var/lib/istio/envoy/cluster.env - diff --git a/tools/deb/description b/tools/deb/description deleted file mode 100644 index 79f0f63cb76..00000000000 --- a/tools/deb/description +++ /dev/null @@ -1 +0,0 @@ -Istio proxy provides the envoy sidecar used by the http://istio.io project. \ No newline at end of file diff --git a/tools/deb/envoy.json b/tools/deb/envoy.json deleted file mode 100644 index e9d3ee6b465..00000000000 --- a/tools/deb/envoy.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "listeners": [ - ], - "lds": { - "cluster": "lds", - "refresh_delay_ms": 10000 - }, - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://0.0.0.0:15000" - }, - "cluster_manager": { - "clusters": [ - { - "name": "rds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://istio-pilot:15003" - } - ] - }, - { - "name": "lds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://istio-pilot:15003" - } - ] - }, - { - "name": "local8000", - "connect_timeout_ms": 5000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8000" - } - ] - } - ], - "sds": { - "cluster": { - "name": "sds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://istio-pilot:15003" - } - ] - }, - "refresh_delay_ms": 10000 - }, - "cds": { - "cluster": { - "name": "cds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://istio-pilot:15003" - } - ] - }, - "refresh_delay_ms": 10000 - } - } -} diff --git a/tools/deb/postinst.sh b/tools/deb/postinst.sh deleted file mode 100755 index da3c2fdb3c7..00000000000 --- a/tools/deb/postinst.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -set -e - -action="$1" -oldversion="$2" - -umask 022 - -if ! getent passwd istio-proxy >/dev/null; then - addgroup --system istio-proxy - adduser --system --group --home /var/lib/istio istio-proxy -fi - -if [ ! -e /etc/istio ]; then - # Backward compat. - ln -s /var/lib/istio /etc/istio -fi - -mkdir -p /var/lib/istio/envoy -mkdir -p /var/lib/istio/proxy -mkdir -p /var/lib/istio/config -mkdir -p /var/log/istio - -touch /var/lib/istio/config/mesh - -chown istio-proxy.istio-proxy /var/lib/istio/envoy /var/lib/istio/config /var/log/istio /var/lib/istio/config/mesh /var/lib/istio/proxy - diff --git a/tools/deb/sidecar.env b/tools/deb/sidecar.env deleted file mode 100644 index 72d728ded61..00000000000 --- a/tools/deb/sidecar.env +++ /dev/null @@ -1,53 +0,0 @@ -# Environment variables used to configure istio startup - -# Comma separated list of CIDRs used for services. If set, iptables will be run to allow istio -# sidecar to intercept outbound calls to configured addresses. If not set, outbound istio sidecar -# will not be used via iptables. -# ISTIO_SERVICE_CIDR= - -# Name of the service exposed by the machine. -# ISTIO_SERVICE=myservice - -# Comma separated list of local ports that will use Istio sidecar for inbound services. -# If set, iptables rules will be configured to intercept inbound traffic and redirect to sidecar. -# If not set, no rules will be enabled -# ISTIO_INBOUND_PORTS= - -# List of ports to exclude from inbound interception, if ISTIO_INBOUND_PORTS is set to * -# Port 22 is automatically excluded -# ISTIO_INBOUND_EXCLUDE_PORTS= - -# Namespace of the cluster. -# ISTIO_NAMESPACE=default - -# Specify the IP address used in endpoints. If not set, 'hostname --ip-address' will be used. -# Needed if the host has multiple IP. -# ISTIO_SVC_IP= - - - -# Fine tunning - useful if installing/building binaries instead of using the .deb file, or running -# multiple instances. - -# Port used by Envoy. Defaults to 15001, used in the autogenerated config -# ENVOY_PORT=15001 - -# User running Envoy. For testing you can use a regular user ID - however running iptables requires -# root or netadmin capability. The debian file creates user istio. -# ENVOY_USER=istio-proxy - -# Uncomment to enable debugging -# ISTIO_DEBUG="-l debug" - -# Directory for stdout redirection. The redirection is required because envoy attempts to open -# /dev/stdout - must be a real file. Will be used for access logs. Additional config for logsaver -# needs to be made, envoy reopens the file on SIGUSR1 -# ISTIO_LOG_DIR=/var/log/istio - -# Installation directory for istio binaries, customize in case you're using a binary. -# This is likely to change - current path matches the docker layout in 0.1 -# ISTIO_BIN_BASE=/usr/local/bin - -# Location of istio configs. -# ISTIO_CFG=/var/lib/istio - diff --git a/tools/deb/test/build_docker.sh b/tools/deb/test/build_docker.sh deleted file mode 100755 index a25031eda02..00000000000 --- a/tools/deb/test/build_docker.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# Builds a docker image with istio-proxy deb included, to be used for testing. - -# Script requires a working docker on the test machine -# It is run in the proxy dir, will create a docker image with proxy deb installed - - -bazel build tools/deb:istio-proxy - -PROJECT="istio-testing" -DATE_PART=$(date +"%Y%m%d") -SHA_PART=$(git show -q HEAD --pretty=format:%h) -DOCKER_TAG="${DATE_PART}-${SHA_PART}" -IMAGE_NAME="gcr.io/${PROJECT}/rawvm:${DOCKER_TAG}" - -DOCKER_IMAGE=${DOCKER_IMAGE:-$IMAGE_NAME} - -BAZEL_TARGET="bazel-bin/tools/deb/" - -cp -f $BAZEL_TARGET/istio-proxy_*_amd64.deb tools/deb/test/istio-proxy_amd64.deb -docker build -f tools/deb/test/Dockerfile -t "${DOCKER_IMAGE}" tools/deb/test - - diff --git a/tools/deb/test/envoy_local.json b/tools/deb/test/envoy_local.json deleted file mode 100644 index a96260c5f67..00000000000 --- a/tools/deb/test/envoy_local.json +++ /dev/null @@ -1,312 +0,0 @@ -{ - "listeners": [ - { - "address": "tcp://0.0.0.0:15001", - "name": "virtual", - "filters": [], - "bind_to_port": true, - "use_original_dst": true - }, - { - "address": "tcp://0.0.0.0:9998", - "name": "in_9998", - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "http", - "generate_request_id": true, - "route_config": { - "virtual_hosts": [ - { - "name": "inbound|9998", - "domains": [ - "*" - ], - "routes": [ - { - "prefix": "/", - "cluster": "in.9999", - "opaque_config": { - "mixer_control": "on", - "mixer_forward": "off" - } - }, - { - "prefix": "/mixeroff", - "cluster": "in.9999", - "opaque_config": { - "mixer_control": "off", - "mixer_forward": "off" - } - } - ] - } - ] - }, - "filters": [ - { - "type": "decoder", - "name": "mixer", - "config": { - "mixer_attributes": { - "target.ip": "10.128.0.5", - "target.service": "my-service.default.svc.cluster.local", - "target.uid": "kubernetes://hello-3564253481-dvc07.default" - }, - "forward_attributes": { - "source.ip": "10.128.0.5", - "source.uid": "kubernetes://hello-3564253481-dvc07.default" - }, - "quota_name": "RequestCount" - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ], - "access_log": [ - { - "path": "/dev/stdout" - } - ] - } - } - ], - "bind_to_port": true - }, - { - "address": "tcp://0.0.0.0:15003", - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "http_proxy", - - "filters": [ - { - "type": "decoder", - "name": "router", - "config": {} - } - ], - - "route_config": { - "virtual_hosts": [ - { - "name": "local", - "domains": ["*"], - "routes": [ - { - "timeout_ms": 0, - "prefix": "/", - "cluster": "in.9999", - "opaque_config": { - "mixer_control": "on", - "mixer_forward": "off" - } - } - ] - }]}, - "http1_settings": { - "allow_absolute_url": true - }, - "access_log": [ - { - "path": "/dev/null" - } - ] - } - } - ], - "bind_to_port": true - }, - { - "address": "tcp://0.0.0.0:15002", - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "http_proxy", - - "filters": [ - { - "type": "decoder", - "name": "router", - "config": {} - } - ], - "rds": { - "cluster": "rds", - "route_config_name": "9999", - "refresh_delay_ms": 1000 - }, - "http1_settings": { - "allow_absolute_url": true - }, - "access_log": [ - { - "path": "/dev/null" - } - ] - } - } - ], - "bind_to_port": true - }, - { - "address": "tcp://0.0.0.0:8000", - "name": "http_0.0.0.0_8000", - "filters": [ - { - "type": "read", - "name": "http_connection_manager", - "config": { - "codec_type": "auto", - "stat_prefix": "http", - "generate_request_id": true, - "tracing": { - "operation_name": "ingress" - }, - "rds": { - "cluster": "rds", - "route_config_name": "8000", - "refresh_delay_ms": 1000 - }, - "filters": [ - { - "type": "decoder", - "name": "mixer", - "config": { - "mixer_attributes": { - "target.ip": "10.128.0.5", - "target.service": "my-service.default.svc.cluster.local", - "target.uid": "kubernetes://hello-3564253481-dvc07.default" - }, - "forward_attributes": { - "source.ip": "10.128.0.5", - "source.uid": "kubernetes://hello-3564253481-dvc07.default" - }, - "quota_name": "RequestCount" - } - }, - { - "type": "decoder", - "name": "router", - "config": {} - } - ], - "access_log": [ - { - "path": "/dev/stdout" - } - ] - } - } - ], - "bind_to_port": false - } - ], - "admin": { - "access_log_path": "/dev/null", - "address": "tcp://0.0.0.0:15000" - }, - "cluster_manager": { - "clusters": [ - { - "name": "rds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - { - "name": "lds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - { - "name": "mixer_server", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:9091" - } - ], - "features": "http2", - "circuit_breakers": { - "default": { - "max_pending_requests": 10000, - "max_requests": 10000 - } - } - }, - { - "name": "out.local", - "service_name": "my-local-service.default.svc.cluster.local|http", - "connect_timeout_ms": 1000, - "type": "sds", - "lb_type": "round_robin" - }, - { - "name": "in.9999", - "connect_timeout_ms": 1000, - "type": "static", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://127.0.0.1:9999" - } - ] - } - ], - "sds": { - "cluster": { - "name": "sds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - "refresh_delay_ms": 10000 - }, - "cds": { - "cluster": { - "name": "cds", - "connect_timeout_ms": 1000, - "type": "strict_dns", - "lb_type": "round_robin", - "hosts": [ - { - "url": "tcp://localhost:8080" - } - ] - }, - "refresh_delay_ms": 10000 - } - } -} diff --git a/tools/deb/test/golden.cidr b/tools/deb/test/golden.cidr deleted file mode 100644 index a1ea1987298..00000000000 --- a/tools/deb/test/golden.cidr +++ /dev/null @@ -1,17 +0,0 @@ - -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -:ISTIO_OUTPUT - [0:0] -:ISTIO_REDIRECT - [0:0] --A OUTPUT -p tcp -j ISTIO_OUTPUT --A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ISTIO_REDIRECT --A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN --A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN --A ISTIO_OUTPUT -d 10.23.0.0/16 -j ISTIO_REDIRECT --A ISTIO_OUTPUT -j RETURN --A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 -COMMIT - diff --git a/tools/deb/test/golden.defaults b/tools/deb/test/golden.defaults deleted file mode 100644 index b88aa6e2203..00000000000 --- a/tools/deb/test/golden.defaults +++ /dev/null @@ -1,16 +0,0 @@ - -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -:ISTIO_OUTPUT - [0:0] -:ISTIO_REDIRECT - [0:0] --A OUTPUT -p tcp -j ISTIO_OUTPUT --A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ISTIO_REDIRECT --A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN --A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN --A ISTIO_OUTPUT -j ISTIO_REDIRECT --A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 -COMMIT - diff --git a/tools/deb/test/golden.exclude b/tools/deb/test/golden.exclude deleted file mode 100644 index 5771575df0b..00000000000 --- a/tools/deb/test/golden.exclude +++ /dev/null @@ -1,20 +0,0 @@ - -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -:ISTIO_INBOUND - [0:0] -:ISTIO_OUTPUT - [0:0] -:ISTIO_REDIRECT - [0:0] --A PREROUTING -p tcp -j ISTIO_INBOUND --A OUTPUT -p tcp -j ISTIO_OUTPUT --A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN --A ISTIO_INBOUND -p tcp -j ISTIO_REDIRECT --A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ISTIO_REDIRECT --A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN --A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN --A ISTIO_OUTPUT -j ISTIO_REDIRECT --A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 -COMMIT - diff --git a/tools/deb/test/golden.in b/tools/deb/test/golden.in deleted file mode 100644 index a9e7bf0ee00..00000000000 --- a/tools/deb/test/golden.in +++ /dev/null @@ -1,21 +0,0 @@ - -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -:ISTIO_INBOUND - [0:0] -:ISTIO_OUTPUT - [0:0] -:ISTIO_REDIRECT - [0:0] --A PREROUTING -p tcp -j ISTIO_INBOUND --A OUTPUT -p tcp -j ISTIO_OUTPUT --A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN --A ISTIO_INBOUND -p tcp -m tcp --dport 8000 -j ISTIO_REDIRECT --A ISTIO_INBOUND -p tcp -m tcp --dport 9000 -j ISTIO_REDIRECT --A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ISTIO_REDIRECT --A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN --A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN --A ISTIO_OUTPUT -j ISTIO_REDIRECT --A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 -COMMIT - diff --git a/tools/deb/test/machine_setup.sh b/tools/deb/test/machine_setup.sh deleted file mode 100755 index 6b246512bef..00000000000 --- a/tools/deb/test/machine_setup.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -set -x - -NAME=${1-istiotestrawvm} - -# Script to run on a machine to init DNS and other packages. -# Used for automated testing of raw VM setup - -apt-get update -sudo apt-get -y install dnsutils dnsmasq tcpdump netcat nginx - -# Copy config files for DNS -chmod go+r kubedns -cp kubedns /etc/dnsmasq.d -systemctl restart dnsmasq - -# Cluster settings - the CIDR in particular. -cp cluster.env /var/lib/istio/envoy - -echo "ISTIO_INBOUND_PORTS=80" > /var/lib/istio/envoy/sidecar.env - -# Update DHCP - if needed -grep "^prepend domain-name-servers 127.0.0.1;" /etc/dhcp/dhclient.conf > /dev/null -if [[ $? != 0 ]]; then - echo 'prepend domain-name-servers 127.0.0.1;' >> /etc/dhcp/dhclient.conf - # TODO: find a better way to re-trigger dhclient - dhclient -v -1 -fi - -# Install istio binaries -dpkg -i istio-*.deb; - -mkdir /var/www/html/$NAME -echo "VM $NAME" > /var/www/html/$NAME/index.html - -cat < /etc/nginx/conf.d/zipkin.conf -server { - listen 9411; - location / { - proxy_pass http://zipkin.default.svc.cluster.local:9411/; - proxy_http_version 1.1; - } - } -EOF - -# Start istio -systemctl start istio - diff --git a/tools/deb/test/run_gce_test.sh b/tools/deb/test/run_gce_test.sh deleted file mode 100755 index 220d16f84e0..00000000000 --- a/tools/deb/test/run_gce_test.sh +++ /dev/null @@ -1,352 +0,0 @@ -#!/bin/bash -# -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# Requires gcloud and access to a cluster that runs k8s. Will create a VM, install sidecar and -# run tests. - -# To run the script, needs to override the following env with the current user. -export PROJECT=${PROJECT:-costin-istio} - -# Must have iam.serviceAccountActor or owner permissions to the project. Use IAM settings. -export ACCOUNT=${ACCOUNT:-291514510799-compute@developer.gserviceaccount.com} -export ISTIO_REGION=${ISTIO_REGION:-us-west1} -export ISTIO_ZONE=${ISTIO_ZONE:-us-west1-c} - -# Name of the k8s cluster running istio control plane. Used to find the service CIDR -K8SCLUSTER=${K8SCLUSTER:-istio-auth} - -TESTVM=${TESTVM:-istiotestrawvm} - -PROXY_DIR=$(pwd) -WS=${WS:-$(pwd)/../..} - -# TODO: extend the script to use Vagrant+minikube or other ways to create the VM. The main -# issue is that we need the k8s and VM to be on same VPC - unless we run kubeapiserver+etcd -# standalone. -set -x - -# Run a command in a VM. -function istioRun() { - local NAME=$1 - local CMD=$2 - - gcloud compute ssh --project $PROJECT --zone $ISTIO_ZONE $NAME --command "$CMD" -} - -# Copy files to the VM -function istioCopy() { - # TODO: based on some env variable, use different commands for other clusters or for testing with - # bare-metal machines. - local NAME=$1 - shift - local FILES=$* - - gcloud compute scp --project $PROJECT --zone $ISTIO_ZONE $FILES ${NAME}: -} - - -# Create the raw VM. -function istioVMInit() { - # TODO: check if it exists and do "reset", to speed up the script. - local NAME=$1 - local IMAGE=${2:-debian-9-stretch-v20170816} - local IMAGE_PROJECT=${3:-debian-cloud} - - gcloud compute --project $PROJECT instances describe $NAME --zone ${ISTIO_ZONE} >/dev/null - if [[ $? == 0 ]] ; then - - gcloud compute --project $PROJECT \ - instances reset $NAME \ - --zone $ISTIO_ZONE \ - - else - - gcloud compute --project $PROJECT \ - instances create $NAME \ - --zone $ISTIO_ZONE \ - --machine-type "n1-standard-1" \ - --subnet default \ - --can-ip-forward \ - --service-account $ACCOUNT \ - --scopes "https://www.googleapis.com/auth/cloud-platform" \ - --tags "http-server","https-server" \ - --image $IMAGE \ - --image-project $IMAGE_PROJECT \ - --boot-disk-size "10" \ - --boot-disk-type "pd-standard" \ - --boot-disk-device-name "debtest" - - fi - - # Wait for machine to start up ssh - for i in {1..10} - do - istioRun $NAME 'echo hi' - if [[ $? -ne 0 ]] ; then - echo Waiting for startup $? - sleep 5 - else - break - fi - done - - # Allow access to the VM on port 80 and 9411 (where we run services) - gcloud compute firewall-rules create allow-external --allow tcp:22,tcp:80,tcp:443,tcp:9411,udp:5228,icmp --source-ranges 0.0.0.0/0 - - -} - - -function istioVMDelete() { - local NAME=${1:-$TESTVM} - gcloud compute -q --project $PROJECT --zone $ISTIO_ZONE instances delete $NAME --zone $ISTIO_ZONE -} - -# Helper to get the external IP of a raw VM -function istioVMExternalIP() { - local NAME=${1:-$TESTVM} - gcloud compute --project $PROJECT instances describe $NAME --zone $ISTIO_ZONE --format='value(networkInterfaces[0].accessConfigs[0].natIP)' -} - -function istioVMInternalIP() { - local NAME=${1:-$TESTVM} - gcloud compute --project $PROJECT instances describe $NAME --zone $ISTIO_ZONE --format='value(networkInterfaces[0].networkIP)' -} - -# Initialize the K8S cluster, generating config files for the raw VMs. -# Must be run once, will generate files in the CWD. The files must be installed on the VM. -# This assumes the recommended dnsmasq config option. -function istioPrepareCluster() { -cat < kubedns - echo "address=/istio-mixer/$MIXER_IP" >> kubedns - echo "address=/mixer-server/$MIXER_IP" >> kubedns - echo "address=/istio-pilot/$PILOT_IP" >> kubedns - - CIDR=$(gcloud container clusters describe ${K8SCLUSTER} --zone=${ISTIO_ZONE} --format "value(servicesIpv4Cidr)") - echo "ISTIO_SERVICE_CIDR=$CIDR" > cluster.env - -} - -# Configure a service running on the VM. -# Params: -# - port of the service -# - service name (default to raw vm name) -# - IP of the rawvm (will get it from gcloud if not set) -# -function istioConfigHttpService() { - local NAME=${1:-} - local PORT=${2:-} - local IP=${3:-} - - # The 'name: http' is critical - without it the service is exposed as TCP - - cat << EOF | kubectl apply -f - -kind: Service -apiVersion: v1 -metadata: - name: $NAME -spec: - ports: - - protocol: TCP - port: $PORT - name: http - ---- - -kind: Endpoints -apiVersion: v1 -metadata: - name: $NAME -subsets: - - addresses: - - ip: $IP - ports: - - port: $PORT - name: http -EOF - - -} - - -function istioRoute() { - local ROUTE=${1:-} - local SERVICE=${2:-} - local PORT=${3:-} - -cat < $LOG_DIR/pilot.pid - - ${GOPATH}/src/istio.io/mixer/bazel-bin/cmd/server/mixs server --configStoreURL=fs:${GOPATH}/src/istio.io/mixer/testdata/configroot -v=2 --logtostderr & - echo $! > $LOG_DIR/mixer.pid - - ${GOPATH}/src/istio.io/pilot/bazel-bin/test/server/server --port 9999 > $LOG_DIR/test_server.log 2>&1 & - echo $! > $LOG_DIR/test_server.pid - - # 'lds' disabled, so we can use manual config. - bazel-bin/src/envoy/envoy -c tools/deb/test/envoy_local.json --restart-epoch 0 --drain-time-s 2 --parent-shutdown-time-s 3 --service-cluster istio-proxy --service-node sidecar~172.17.0.2~mysvc.~svc.cluster.local & - echo $! > $LOG_DIR/envoy.pid -} - -# Add a service and endpoint to K8S. -function istioAddEndpoint() { - NAME=${1:-} - PORT=${2:-} - ENDPOINT_IP=${3:-$(ip route get 8.8.8.8 | head -1 | cut -d' ' -f8)} - - cat << EOF | kubectl apply -f - -kind: Service -apiVersion: v1 -metadata: - name: $NAME -spec: - ports: - - protocol: TCP - port: $PORT - name: http - ---- - -kind: Endpoints -apiVersion: v1 -metadata: - name: $NAME -subsets: - - addresses: - - ip: $ENDPOINT_IP - ports: - - port: $PORT - name: http -EOF -} - -# Standalone test verifies: -# - pilot standalone (running in a VM, outside k8s) works -# - services registered by the VM are reachable ( RDS + SDS ) -# - outgoing calls with http_proxy work. -function test_standalone() { - - # Register the service running on the local test machine. Uses the primary IP of the host running the test. - istioAddEndpoint test-proxy 9999 - - # Pending the 'merged namespaces in rds for proxy', we have the proxy use the existing config for port 9999 - - # Verify we can connect to the endpoint - using explicitly/manual configured cluster and route - # This confirms proxy mode works in the envoy build. - curl -x http://127.0.0.1:15003 http://test-proxy.default.svc.cluster.local:9999 - - # Verify we can connect using RDS and SDS. This confirms the local pilot can see the endpoint - # registration and generates correct RDS and SDS response. - echo "Service discovery: " - curl 'http://localhost:8080/v1/registration/test-proxy.default.svc.cluster.local|http' - - echo "Route discovery: " - curl 'http://localhost:8080/v1/routes/9999/istio-proxy/sidecar~10.23.253.238~test-proxy.default~cluster.local' - - # All together: envoy using proxy and RDS for the test service, and SDS to route to the cluster on the local - # machine. - curl -x http://127.0.0.1:15002 http://test-proxy.default.svc.cluster.local:9999 - -} - -# Tests require an existing kube api server, and a valid $HOME/.kube/config file -function run_tests() { - test_standalone -} - -if [[ ${1:-} == "start" ]] ; then - build_all - # Stop any previously running servers - kill_all - start_all -elif [[ ${1:-} == "stop" ]] ; then - kill_all -elif [[ ${1:-} == "test" ]] ; then - run_tests -else - build_all - # Stop any previously running servers - kill_all - start_all - run_tests - kill_all -fi diff --git a/tools/extension-check/README.md b/tools/extension-check/README.md new file mode 100644 index 00000000000..0e802ae5e98 --- /dev/null +++ b/tools/extension-check/README.md @@ -0,0 +1,33 @@ +# Extension check + +This is a simple tool to check all core extensions from envoy are enabled in [istio/proxy](https://github.com/istio/proxy). + +## Usage + +```bash +go run tools/extension-check/main.go \ + --ignore-extensions "" \ + --envoy-extensions-build-config "" \ + --proxy-extensions-build-config "" +``` + +## Example + +Envoy source code can be found at `~/Codes/istio.io/envoy`. +Proxy source code can be found at `~/Codes/istio.io/proxy`. + +First you need delete the first line of proxy extensions build config file, which is `load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")`. + +```bash +sed -i '1d' ~/Codes/istio.io/proxy/bazel/extension_config/extensions_build_config.bzl +``` + +Then you can run the following command to check all core extensions are enabled in proxy. + +```bash +cd ~/Codes/istio.io/proxy +go run tools/extension-check/main.go \ + --ignore-extensions tools/extension-check/wellknown-extensions \ + --envoy-extensions-build-config "../envoy/source/extensions/extensions_build_config.bzl" \ + --proxy-extensions-build-config "./bazel/extension_config/extensions_build_config.bzl" +``` diff --git a/tools/extension-check/main.go b/tools/extension-check/main.go new file mode 100644 index 00000000000..dfae22ba4e3 --- /dev/null +++ b/tools/extension-check/main.go @@ -0,0 +1,102 @@ +// Copyright 2020 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "strings" + + "go.starlark.net/starlark" +) + +var ( + wellknownIgnoreExtensions = flag.String("ignore-extensions", "wellknown-extensions", "file contains list of extensions to ignore") + envoyExtensionsBuildConfig = flag.String("envoy-extensions-build-config", "envoy_extensions_build_config.bzl", "envoy extensions build config file") + proxyExtensionsBuildConfig = flag.String("proxy-extensions-build-config", "proxy_extensions_build_config.bzl", "proxy extensions build config file") +) + +func main() { + flag.Parse() + envoyCoreExtensions, err := extensions(*envoyExtensionsBuildConfig, "EXTENSIONS") + if err != nil { + fmt.Printf("error: %v", err) + os.Exit(1) + } + + proxyExtensions, err := extensions(*proxyExtensionsBuildConfig, "ENVOY_EXTENSIONS") + if err != nil { + fmt.Printf("error: %v", err) + os.Exit(1) + } + + for k, expected := range envoyCoreExtensions { + if actual, ok := proxyExtensions[k]; ok && expected == actual { + delete(envoyCoreExtensions, k) + } + } + + ignoreExtensions := []string{} + if _, err := os.Stat(*wellknownIgnoreExtensions); err == nil { + if b, err := os.ReadFile(*wellknownIgnoreExtensions); err == nil { + wellkonwns := strings.Split(string(b), "\n") + ignoreExtensions = append(ignoreExtensions, wellkonwns...) + } + + fmt.Println("ignore extensions: ", len(ignoreExtensions)) + + for _, ext := range ignoreExtensions { + delete(envoyCoreExtensions, ext) + } + } else { + fmt.Println(err) + } + + if len(envoyCoreExtensions) > 0 { + for k, v := range envoyCoreExtensions { + fmt.Printf("missing extension: %v: %v\n", k, v) + } + os.Exit(1) + } +} + +// extensions returns a map of extensions from the given file with giving key. +// The file is expected to be a starlark file that defines a global variable. +// Depends on go.starlark.net/starlark. +func extensions(filename, key string) (map[string]string, error) { + thread := &starlark.Thread{ + Name: "extensions", + Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) }, + } + globals, err := starlark.ExecFile(thread, filename, nil, nil) + if err != nil { + panic(err) + } + + if v, ok := globals[key]; ok { + extensions := map[string]string{} + + if err := json.Unmarshal([]byte(v.String()), &extensions); err != nil { + return nil, err + } + + return extensions, nil + } + + return nil, errors.New("no extensions found") +} diff --git a/tools/extension-check/wellknown-extensions b/tools/extension-check/wellknown-extensions new file mode 100644 index 00000000000..1d4879cbe76 --- /dev/null +++ b/tools/extension-check/wellknown-extensions @@ -0,0 +1,29 @@ +envoy.config.validators.minimum_clusters_validator +envoy.config_mux.delta_grpc_mux_factory +envoy.config_mux.sotw_grpc_mux_factory +envoy.filters.http.custom_response +envoy.filters.http.file_system_buffer +envoy.filters.http.geoip +envoy.filters.http.rate_limit_quota +envoy.filters.listener.local_ratelimit +envoy.filters.thrift.payload_to_metadata +envoy.filters.udp.session.dynamic_forward_proxy +envoy.filters.udp.session.http_capsule +envoy.geoip_providers.maxmind +envoy.health_check.event_sinks.file +envoy.health_checkers.thrift +envoy.http.custom_response.local_response_policy +envoy.http.custom_response.redirect_policy +envoy.matching.inputs.cel_data_input +envoy.matching.inputs.filter_state +envoy.matching.matchers.cel_matcher +envoy.matching.matchers.runtime_fraction +envoy.network.dns_resolver.getaddrinfo +envoy.path.match.uri_template.uri_template_matcher +envoy.path.rewrite.uri_template.uri_template_rewriter +envoy.quic.server_preferred_address.fixed +envoy.resource_monitors.downstream_connections +envoy.route.early_data_policy.default +envoy.transport_sockets.http_11_proxy +envoy.upstreams.http.udp +envoy.tracers.opencensus diff --git a/tools/gen_compilation_database.py b/tools/gen_compilation_database.py new file mode 100755 index 00000000000..a195ca98adf --- /dev/null +++ b/tools/gen_compilation_database.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import os +import shlex +import subprocess +from pathlib import Path + + +# This method is equivalent to https://github.com/grailbio/bazel-compilation-database/blob/master/generate.py +def generate_compilation_database(args): + # We need to download all remote outputs for generated source code. This option lives here to override those + # specified in bazelrc. + bazel_startup_options = shlex.split(os.environ.get("BAZEL_STARTUP_OPTION_LIST", "")) + bazel_options = shlex.split(os.environ.get("BAZEL_BUILD_OPTION_LIST", "")) + [ + "--config=compdb", + "--remote_download_outputs=all", + ] + + source_dir_targets = args.bazel_targets + + subprocess.check_call(["bazel", *bazel_startup_options, "build"] + bazel_options + [ + "--aspects=@bazel_compdb//:aspects.bzl%compilation_database_aspect", + "--output_groups=compdb_files,header_files" + ] + source_dir_targets) + + execroot = subprocess.check_output( + ["bazel", *bazel_startup_options, "info", *bazel_options, + "execution_root"]).decode().strip() + + db_entries = [] + for db in Path(execroot).glob('**/*.compile_commands.json'): + db_entries.extend(json.loads(db.read_text())) + + def replace_execroot_marker(db_entry): + if 'directory' in db_entry and db_entry['directory'] == '__EXEC_ROOT__': + db_entry['directory'] = execroot + if 'command' in db_entry: + db_entry['command'] = ( + db_entry['command'].replace('-isysroot __BAZEL_XCODE_SDKROOT__', '')) + return db_entry + + return list(map(replace_execroot_marker, db_entries)) + + +def is_header(filename): + for ext in (".h", ".hh", ".hpp", ".hxx"): + if filename.endswith(ext): + return True + return False + + +def is_compile_target(target, args): + filename = target["file"] + if is_header(filename): + if args.include_all: + return True + if not args.include_headers: + return False + + if filename.startswith("bazel-out/"): + if args.include_all: + return True + if not args.include_genfiles: + return False + + if filename.startswith("external/"): + if args.include_all: + return True + if not args.include_external: + return False + + return True + + +def modify_compile_command(target, args): + cc, options = target["command"].split(" ", 1) + + # Workaround for bazel added C++11 options, those doesn't affect build itself but + # clang-tidy will misinterpret them. + options = options.replace("-std=c++0x ", "") + options = options.replace("-std=c++11 ", "") + + if args.vscode: + # Visual Studio Code doesn't seem to like "-iquote". Replace it with + # old-style "-I". + options = options.replace("-iquote ", "-I ") + + if args.system_clang: + if cc.find("clang"): + cc = "clang++" + + if is_header(target["file"]): + options += " -Wno-pragma-once-outside-header -Wno-unused-const-variable" + options += " -Wno-unused-function" + # By treating external/envoy* as C++ files we are able to use this script from subrepos that + # depend on Envoy targets. + if not target["file"].startswith("external/") or target["file"].startswith( + "external/envoy"): + # *.h file is treated as C header by default while our headers files are all C++20. + options = "-x c++ -std=c++20 -fexceptions " + options + + target["command"] = " ".join([cc, options]) + return target + + +def fix_compilation_database(args, db): + db = [modify_compile_command(target, args) for target in db if is_compile_target(target, args)] + + with open("compile_commands.json", "w") as db_file: + json.dump(db, db_file, indent=2) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Generate JSON compilation database') + parser.add_argument('--include_external', action='store_true') + parser.add_argument('--include_genfiles', action='store_true') + parser.add_argument('--include_headers', action='store_true') + parser.add_argument('--vscode', action='store_true') + parser.add_argument('--include_all', action='store_true') + parser.add_argument( + '--system-clang', + action='store_true', + help= + 'Use `clang++` instead of the bazel wrapper for commands. This may help if `clangd` cannot find/run the tools.' + ) + parser.add_argument( + 'bazel_targets', nargs='*', default=[ + "//source/...", + ]) + args = parser.parse_args() + fix_compilation_database(args, generate_compilation_database(args)) diff --git a/include/istio/control/tcp/BUILD b/tools/vscode/refresh_compdb.sh old mode 100644 new mode 100755 similarity index 57% rename from include/istio/control/tcp/BUILD rename to tools/vscode/refresh_compdb.sh index e55ba506dc0..0c516d3d844 --- a/include/istio/control/tcp/BUILD +++ b/tools/vscode/refresh_compdb.sh @@ -1,26 +1,23 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. +#!/usr/bin/env bash +# Copyright Istio Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 - +# http://www.apache.org/licenses/LICENSE-2.0 +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# +opts=(--vscode) -licenses(["notice"]) +# Setting TEST_TMPDIR here so the compdb headers won't be overwritten by another bazel run +TEST_TMPDIR=${BUILD_DIR:-/tmp}/envoy-compdb tools/gen_compilation_database.py \ + "${opts[@]}" -cc_library( - name = "headers_lib", - hdrs = [ - "check_data.h", - "controller.h", - "report_data.h", - "request_handler.h", - ], - visibility = ["//visibility:public"], -) +# Kill clangd to reload the compilation database +pkill clangd || : diff --git a/x_tools_imports.bzl b/x_tools_imports.bzl deleted file mode 100644 index 3f354a67b94..00000000000 --- a/x_tools_imports.bzl +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -# -load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") - -def go_x_tools_imports_repositories(): - BUILD_FILE = """ -package(default_visibility = ["//visibility:public"]) -load("@io_bazel_rules_go//go:def.bzl", "go_binary") -load("@io_bazel_rules_go//go:def.bzl", "go_prefix") - -go_prefix("golang.org/x/tools") - -licenses(["notice"]) # New BSD - -exports_files(["LICENSE"]) - -go_binary( - name = "goimports", - srcs = [ - "cmd/goimports/doc.go", - "cmd/goimports/goimports.go", - "cmd/goimports/goimports_gc.go", - "cmd/goimports/goimports_not_gc.go", - ], - deps = [ - "@org_golang_x_tools//imports:go_default_library", - ], -) -""" - # bazel rule for fixing up cfg.pb.go relies on running goimports - # we import it here as a git repository to allow projection of a - # simple build rule that will build the binary for usage (and avoid - # the need to project a more complicated BUILD file over the entire - # tools repo.) - new_git_repository( - name = "org_golang_x_tools_imports", - build_file_content = BUILD_FILE, - commit = "e6cb469339aef5b7be0c89de730d5f3cc8e47e50", # Jun 23, 2017 (no releases) - remote = "https://github.com/golang/tools.git", - )