├── .dockerignore ├── .github ├── .codecov.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ └── pr-tests.yaml ├── .gitignore ├── .go-version ├── .ko.yaml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── PROJECT ├── README.md ├── THIRD-PARTY ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── policyendpoint_types.go │ └── zz_generated.deepcopy.go ├── charts └── amazon-network-policy-controller-k8s │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── crds │ └── crds.yaml │ ├── templates │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── rbac.yaml │ └── serviceaccount.yaml │ └── values.yaml ├── cmd └── main.go ├── config ├── controller │ ├── controller.yaml │ └── kustomization.yaml ├── crd │ ├── bases │ │ └── networking.k8s.aws_policyendpoints.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_policyendpoints.yaml │ │ └── webhook_in_policyendpoints.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── policyendpoint_editor_role.yaml │ ├── policyendpoint_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── kustomization.yaml │ └── networking_v1alpha1_policyendpoint.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal ├── controllers │ ├── policy_controller.go │ └── suite_test.go └── eventhandlers │ ├── namespace.go │ ├── pod.go │ ├── policy.go │ └── service.go ├── mocks └── controller-runtime │ └── client │ └── client_mocks.go ├── pkg ├── config │ ├── configmap_manager.go │ ├── controller_config.go │ ├── runtime_config.go │ └── runtime_config_test.go ├── k8s │ ├── configmap_store.go │ ├── finalizer.go │ ├── finalizer_test.go │ ├── pod_utils.go │ ├── pod_utils_test.go │ ├── service_utils.go │ ├── service_utils_test.go │ ├── utils.go │ └── utils_test.go ├── policyendpoints │ ├── indexer.go │ ├── manager.go │ └── manager_test.go ├── resolvers │ ├── endpoints.go │ ├── endpoints_test.go │ ├── policies.go │ ├── policies_for_namespace.go │ ├── policies_for_namespace_test.go │ ├── policies_for_pod.go │ ├── policies_for_pod_test.go │ ├── policies_for_service.go │ ├── policies_for_service_test.go │ ├── policy_tracker.go │ └── policy_tracker_test.go ├── utils │ └── configmap │ │ └── configmap.go └── version │ └── version_info.go └── scripts ├── README.md ├── deploy-controller-on-dataplane.sh ├── gen_mocks.sh ├── lib ├── network-policy.sh ├── tests.sh └── verify_test_results.py ├── run-cyclonus-tests.sh └── test └── cyclonus-config.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | # To validate: 2 | # cat codecov.yml | curl --data-binary @- https://codecov.io/validate 3 | 4 | codecov: 5 | # Avoid "Missing base report" 6 | # https://docs.codecov.io/docs/comparing-commits 7 | allow_coverage_offsets: true 8 | notify: 9 | require_ci_to_pass: yes 10 | 11 | coverage: 12 | precision: 2 13 | round: down 14 | range: "50...75" 15 | 16 | status: 17 | project: 18 | default: 19 | threshold: 1 20 | unittest: 21 | threshold: 1 22 | only_pulls: true 23 | flags: 24 | - "unittest" 25 | # Disable patch since it is noisy and not correct 26 | patch: 27 | default: 28 | enabled: no 29 | if_not_found: success 30 | 31 | comment: false 32 | 33 | ignore: 34 | - "api/v1alpha1/**/*" 35 | - "hack/**/*" 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | **What type of PR is this?** 8 | 9 | 16 | 17 | **Which issue does this PR fix**: 18 | 19 | 20 | **What does this PR do / Why do we need it**: 21 | 22 | 23 | **If an issue # is not available please add steps to reproduce and the controller logs**: 24 | 25 | 26 | **Testing done on this change**: 27 | 31 | 32 | **Automation added to e2e**: 33 | 37 | 38 | **Will this PR introduce any new dependencies?**: 39 | 42 | 43 | **Will this break upgrades or downgrades. Has updating a running cluster been tested?**: 44 | 45 | 46 | **Does this PR introduce any user-facing change?**: 47 | 52 | 53 | ```release-note 54 | 55 | ``` 56 | 57 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem 2 | version: 2 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Automatic Pull Request test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | unit-test: 13 | name: Unit test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout latest commit in the PR 17 | uses: actions/checkout@v3 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version-file: go.mod 22 | check-latest: true 23 | cache-dependency-path: "**/go.sum" 24 | - uses: actions/cache@v3 25 | with: 26 | path: | 27 | ~/go/bin 28 | - name: Set up tools 29 | run: | 30 | go install golang.org/x/lint/golint@latest 31 | go install golang.org/x/tools/cmd/goimports@latest 32 | - name: Run code checks 33 | run: | 34 | make check-format 35 | make vet 36 | - name: Build 37 | run: make build 38 | - name: Unit test 39 | run: make test 40 | - name: Upload code coverage 41 | uses: codecov/codecov-action@v3 42 | docker-build: 43 | name: Build Docker images 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout latest commit in the PR 47 | uses: actions/checkout@v3 48 | - name: Set up QEMU 49 | uses: docker/setup-qemu-action@v2 50 | - name: Set up Docker Buildx 51 | uses: docker/setup-buildx-action@v2 52 | - name: Build Network Policy Controller images 53 | run: make docker-buildx 54 | deprecated-apigroups: 55 | name: Detect deprecated apiGroups 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v3 59 | - run: | 60 | version=$(curl -sL https://api.github.com/repos/FairwindsOps/pluto/releases/latest | jq -r ".tag_name") 61 | number=${version:1} 62 | wget https://github.com/FairwindsOps/pluto/releases/download/${version}/pluto_${number}_linux_amd64.tar.gz 63 | sudo tar -C /usr/local -xzf pluto_${number}_linux_amd64.tar.gz 64 | - run: | 65 | /usr/local/pluto detect-files -d . 66 | vuln_check: 67 | runs-on: ubuntu-latest 68 | timeout-minutes: 5 69 | steps: 70 | - name: Checkout 71 | uses: actions/checkout@v4 72 | - name: Set variables 73 | run: | 74 | VER=$(cat .go-version) 75 | echo "VERSION=$VER" >> $GITHUB_ENV 76 | - uses: actions/setup-go@v4 77 | with: 78 | go-version: ${{ env.VERSION }} 79 | check-latest: true 80 | cache-dependency-path: "**/go.sum" 81 | - name: Install `govulncheck` 82 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 83 | - name: Run `govulncheck` 84 | run: ~/go/bin/govulncheck ./... 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | testbin/* 9 | Dockerfile.cross 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.23 2 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | flags: 5 | - -mod=readonly 6 | ldflags: 7 | - -s 8 | - -w 9 | - -X github.com/aws/amazon-network-policy-controller-k8s/pkg/version.GitVersion={{.Env.GIT_VERSION}} 10 | - -X github.com/aws/amazon-network-policy-controller-k8s/pkg/version.GitCommit={{.Env.GIT_COMMIT}} 11 | - -X github.com/aws/amazon-network-policy-controller-k8s/pkg/version.BuildDate={{.Env.BUILD_DATE}} 12 | 13 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @aws/eks-networking 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE 2 | ARG BUILD_IMAGE 3 | ARG ARCH=amd64 4 | # Build the controller binary 5 | FROM $BUILD_IMAGE as builder 6 | 7 | WORKDIR /workspace 8 | ENV GOPROXY direct 9 | 10 | # Copy the Go Modules manifests 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | # cache deps before building and copying source so that we don't need to re-download as much 14 | # and so that source changes don't invalidate our downloaded layer 15 | RUN go mod download 16 | 17 | # Copy the go source 18 | COPY .git/ .git/ 19 | COPY cmd/main.go cmd/main.go 20 | COPY api/ api/ 21 | COPY pkg/ pkg/ 22 | COPY internal/ internal/ 23 | 24 | # Version package for passing the ldflags 25 | # TODO: change this to network controller's version 26 | ENV VERSION_PKG=github.com/aws/amazon-network-policy-controller-k8s/pkg/version 27 | # Build 28 | RUN GIT_VERSION=$(git describe --tags --always) && \ 29 | GIT_COMMIT=$(git rev-parse HEAD) && \ 30 | BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%S%z) && \ 31 | CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} GO111MODULE=on go build \ 32 | -ldflags="-X ${VERSION_PKG}.GitVersion=${GIT_VERSION} -X ${VERSION_PKG}.GitCommit=${GIT_COMMIT} -X ${VERSION_PKG}.BuildDate=${BUILD_DATE}" -a -o controller cmd/main.go 33 | 34 | FROM $BASE_IMAGE 35 | 36 | WORKDIR / 37 | COPY --from=public.ecr.aws/eks-distro/kubernetes/go-runner:v0.18.0-eks-1-32-11 /go-runner /usr/local/bin/go-runner 38 | COPY --from=builder /workspace/controller . 39 | USER 65532:65532 40 | 41 | ENTRYPOINT ["/controller"] 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Image URL to use all building/pushing image targets 16 | IMG ?= public.ecr.aws/eks/amazon-network-policy-controller-k8s:v1.0.2 17 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 18 | ENVTEST_K8S_VERSION = 1.26.1 19 | # ARCHS define the target architectures for the controller image be build 20 | ARCHS ?= amd64 arm64 21 | # IMG_SBOM defines the SBOM media type to use, we set to none since ECR doesn't support it yet 22 | IMG_SBOM ?= none 23 | 24 | # Disable the control plane network policy controller 25 | DISABLE_CP_NETWORK_POLICY_CONTROLLER=false 26 | 27 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 28 | ifeq (,$(shell go env GOBIN)) 29 | GOBIN=$(shell go env GOPATH)/bin 30 | else 31 | GOBIN=$(shell go env GOBIN) 32 | endif 33 | 34 | # Setting SHELL to bash allows bash commands to be executed by recipes. 35 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 36 | SHELL = /usr/bin/env bash -o pipefail 37 | .SHELLFLAGS = -ec 38 | 39 | .PHONY: all 40 | all: build 41 | 42 | ##@ General 43 | 44 | # The help target prints out all targets with their descriptions organized 45 | # beneath their categories. The categories are represented by '##@' and the 46 | # target descriptions by '##'. The awk commands is responsible for reading the 47 | # entire set of makefiles included in this invocation, looking for lines of the 48 | # file as xyz: ## something, and then pretty-format the target and help. Then, 49 | # if there's a line with ##@ something, that gets pretty-printed as a category. 50 | # More info on the usage of ANSI control characters for terminal formatting: 51 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 52 | # More info on the awk command: 53 | # http://linuxcommand.org/lc3_adv_awk.php 54 | 55 | .PHONY: help 56 | help: ## Display this help. 57 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 58 | 59 | ##@ Development 60 | 61 | .PHONY: manifests 62 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 63 | $(CONTROLLER_GEN) rbac:roleName=controller-k8s crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 64 | 65 | .PHONY: generate 66 | generate: controller-gen mockgen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 67 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 68 | MOCKGEN=$(MOCKGEN) ./scripts/gen_mocks.sh 69 | 70 | .PHONY: fmt 71 | fmt: ## Run go fmt against code. 72 | go fmt ./... 73 | 74 | .PHONY: vet 75 | vet: ## Run go vet against code. 76 | go vet ./... 77 | 78 | .PHONY: test 79 | test: manifests generate fmt vet envtest ## Run tests. 80 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out 81 | 82 | ##@ Build 83 | 84 | .PHONY: build 85 | build: manifests generate fmt vet ## Build controller binary. 86 | go build -o bin/controller cmd/main.go 87 | 88 | .PHONY: run 89 | run: manifests generate fmt vet ## Run a controller from your host. 90 | go run ./cmd/main.go 91 | 92 | .PHONY: docker-push 93 | docker-push: image-push 94 | 95 | .PHONY: image-push 96 | image-push: ko 97 | KO_DOCKER_REPO=$(firstword $(subst :, ,${IMG})) \ 98 | GIT_VERSION=$(shell git describe --tags --dirty --always) \ 99 | GIT_COMMIT=$(shell git rev-parse HEAD) \ 100 | BUILD_DATE=$(shell date +%Y-%m-%dT%H:%M:%S%z) \ 101 | $(KO) build --tags $(word 2,$(subst :, ,${IMG})) --platform=${PLATFORM} --bare --sbom ${IMG_SBOM} ./cmd 102 | 103 | ##@ Deployment 104 | 105 | ifndef ignore-not-found 106 | ignore-not-found = false 107 | endif 108 | 109 | .PHONY: install 110 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 111 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 112 | 113 | .PHONY: uninstall 114 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 115 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 116 | 117 | .PHONY: deploy 118 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 119 | cd config/controller && $(KUSTOMIZE) edit set image controller=${IMG} 120 | $(KUSTOMIZE) build config/default | kubectl apply -f - 121 | 122 | .PHONY: undeploy 123 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 124 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 125 | 126 | ##@ Build Dependencies 127 | 128 | ## Location to install dependencies to 129 | LOCALBIN ?= $(shell pwd)/bin 130 | $(LOCALBIN): 131 | mkdir -p $(LOCALBIN) 132 | 133 | ## Tool Binaries 134 | KUSTOMIZE ?= $(LOCALBIN)/kustomize 135 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 136 | ENVTEST ?= $(LOCALBIN)/setup-envtest 137 | KO ?= $(LOCALBIN)/ko 138 | MOCKGEN ?= $(LOCALBIN)/mockgen 139 | 140 | ## Tool Versions 141 | KUSTOMIZE_VERSION ?= v5.4.3 142 | CONTROLLER_TOOLS_VERSION ?= v0.16.3 143 | 144 | KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" 145 | .PHONY: kustomize 146 | kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. 147 | $(KUSTOMIZE): $(LOCALBIN) 148 | @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ 149 | echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ 150 | rm -rf $(LOCALBIN)/kustomize; \ 151 | fi 152 | test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) --output install_kustomize.sh && bash install_kustomize.sh $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); rm install_kustomize.sh; } 153 | 154 | .PHONY: controller-gen 155 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. 156 | $(CONTROLLER_GEN): $(LOCALBIN) 157 | test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ 158 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 159 | 160 | .PHONY: envtest 161 | envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. 162 | $(ENVTEST): $(LOCALBIN) 163 | test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 164 | 165 | .PHONY: ko 166 | ko: $(KO) 167 | $(KO): $(LOCALBIN) 168 | test -s $(LOCALBIN)/ko || GOBIN=$(LOCALBIN) go install github.com/google/ko@v0.15.2 169 | 170 | .PHONY: mockgen 171 | mockgen: $(MOCKGEN) 172 | $(MOCKGEN): $(LOCALBIN) 173 | test -s $(MOCKGEN) || GOBIN=$(LOCALBIN) go install github.com/golang/mock/mockgen@v1.6.0 174 | 175 | GO_IMAGE_TAG=$(shell cat .go-version) 176 | BUILD_IMAGE=public.ecr.aws/docker/library/golang:$(GO_IMAGE_TAG) 177 | BASE_IMAGE=public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-nonroot:latest.2 178 | GO_RUNNER_IMAGE=public.ecr.aws/eks-distro/kubernetes/go-runner:v0.18.0-eks-1-32-10 179 | .PHONY: docker-buildx 180 | docker-buildx: test 181 | for platform in $(ARCHS); do \ 182 | docker buildx build --platform=linux/$$platform -t $(IMG)-$$platform --build-arg BASE_IMAGE=$(BASE_IMAGE) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg $$platform --load .; \ 183 | done 184 | 185 | .PHONY: docker-buildx-no-test 186 | docker-buildx-no-test: 187 | for platform in $(ARCHS); do \ 188 | docker buildx build --platform=linux/$$platform -t $(IMG)_$$platform --build-arg BASE_IMAGE=$(BASE_IMAGE) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg $$platform --load .; \ 189 | done 190 | 191 | # Check formatting of source code files without modification. 192 | check-format: FORMAT_FLAGS = -l 193 | check-format: format 194 | 195 | format: ## Format all Go source code files. 196 | @command -v goimports >/dev/null || { echo "ERROR: goimports not installed"; exit 1; } 197 | @exit $(shell find ./* \ 198 | -type f \ 199 | -name '*.go' \ 200 | -print0 | sort -z | xargs -0 -- goimports $(or $(FORMAT_FLAGS),-w) | wc -l | bc) 201 | 202 | run-cyclonus-test: ## Runs cyclonus tests on an existing cluster. Call with CLUSTER_NAME= to execute cyclonus test 203 | ifdef CLUSTER_NAME 204 | CLUSTER_NAME=$(CLUSTER_NAME) ./scripts/run-cyclonus-tests.sh 205 | else 206 | @echo 'Pass CLUSTER_NAME parameter' 207 | endif 208 | 209 | ./PHONY: deploy-controller-on-dataplane 210 | deploy-controller-on-dataplane: ## Deploys the Network Policy controller on an existing cluster. Optionally call with NP_CONTROLLER_IMAGE= to update the image 211 | ./scripts/deploy-controller-on-dataplane.sh NP_CONTROLLER_IMAGE=$(NP_CONTROLLER_IMAGE) 212 | 213 | ./PHONY: deploy-and-test 214 | deploy-and-test: ## Deploys the Network Policy controller on an existing cluster and runs cyclonus tests. Call with CLUSTER_NAME= and NP_CONTROLLER_IMAGE= 215 | $(MAKE) deploy-controller-on-dataplane NP_CONTROLLER_IMAGE=$(NP_CONTROLLER_IMAGE) 216 | $(MAKE) run-cyclonus-test CLUSTER_NAME=$(CLUSTER_NAME) 217 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: k8s.aws 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: amazon-network-policy-controller-k8s 9 | repo: github.com/aws/amazon-network-policy-controller-k8s 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | domain: k8s.aws 16 | group: networking 17 | kind: PolicyEndpoint 18 | path: github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1 19 | version: v1alpha1 20 | version: "3" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Amazon Network Policy Controller for Kubernetes 2 | 3 | Controller for Kubernetes NetworkPolicy resources. 4 | 5 | Network Policy Controller resolves the configured network policies and publishes the resolved endpoints via Custom CRD (`PolicyEndpoints`) resource. 6 | 7 | ## Getting Started 8 | 9 | When you create a new Amazon EKS cluster, the network policy controller is automatically installed on the EKS control plane. It actively monitors the creation of network policies within your cluster and reconciles policy endpoints. Subsequently, the controller instructs the node agent to create or update eBPF programs on the node by publishing pod information through the policy endpoints. Network policy controller configures policies for pods in parallel to pod provisioning, until then new pods will come up with default allow policy. All ingress and egress traffic is allowed to and from the new pods until they are reconciled against the existing policies. To effectively manage network policies on self-managed Kubernetes clusters, you need to deploy a network policy controller on a node. 10 | 11 | Stay tuned for additional instructions for installing Network Policy Controller on nodes. The controller image is published to AWS ECR. 12 | 13 | The controller does not require any IAM policies. It does not make AWS API calls. 14 | 15 | ### Prerequisites 16 | 17 | - Kubernetes Version - 1.25+ 18 | - Amazon VPC CNI version - 1.14.0+ 19 | 20 | ## Security Disclosures 21 | 22 | If you think you’ve found a potential security issue, please do not post it in the Issues. Instead, please follow the 23 | instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). 24 | 25 | ## Contributing 26 | 27 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for further details. 28 | 29 | ## License 30 | 31 | This project is licensed under the Apache-2.0 License. 32 | 33 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the networking v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=networking.k8s.aws 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "networking.k8s.aws", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/policyendpoint_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | networking "k8s.io/api/networking/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | // PolicyReference is the reference to the network policy resource 26 | type PolicyReference struct { 27 | // Name is the name of the Policy 28 | Name string `json:"name"` 29 | 30 | // Namespace is the namespace of the Policy 31 | Namespace string `json:"namespace"` 32 | } 33 | 34 | type NetworkAddress string 35 | 36 | // Port contains information about the transport port/protocol 37 | type Port struct { 38 | // Protocol specifies the transport protocol, default TCP 39 | // +default="TCP" 40 | Protocol *corev1.Protocol `json:"protocol,omitempty"` 41 | 42 | // Port specifies the numerical port for the protocol. If empty applies to all ports 43 | Port *int32 `json:"port,omitempty"` 44 | 45 | // Endport specifies the port range port to endPort 46 | // port must be defined and an integer, endPort > port 47 | EndPort *int32 `json:"endPort,omitempty"` 48 | } 49 | 50 | // EndpointInfo defines the network endpoint information for the policy ingress/egress 51 | type EndpointInfo struct { 52 | // CIDR is the network address(s) of the endpoint 53 | CIDR NetworkAddress `json:"cidr"` 54 | 55 | // Except is the exceptions to the CIDR ranges mentioned above. 56 | Except []NetworkAddress `json:"except,omitempty"` 57 | 58 | // Ports is the list of ports 59 | Ports []Port `json:"ports,omitempty"` 60 | } 61 | 62 | // PodEndpoint defines the summary information for the pods 63 | type PodEndpoint struct { 64 | // HostIP is the IP address of the host the pod is currently running on 65 | HostIP NetworkAddress `json:"hostIP"` 66 | // PodIP is the IP address of the pod 67 | PodIP NetworkAddress `json:"podIP"` 68 | // Name is the pod name 69 | Name string `json:"name"` 70 | // Namespace is the pod namespace 71 | Namespace string `json:"namespace"` 72 | } 73 | 74 | // PolicyEndpointSpec defines the desired state of PolicyEndpoint 75 | type PolicyEndpointSpec struct { 76 | // PodSelector is the podSelector from the policy resource 77 | PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"` 78 | 79 | // PolicyRef is a reference to the Kubernetes NetworkPolicy resource. 80 | PolicyRef PolicyReference `json:"policyRef"` 81 | 82 | // PodIsolation specifies whether the pod needs to be isolated for a 83 | // particular traffic direction Ingress or Egress, or both. If default isolation is not 84 | // specified, and there are no ingress/egress rules, then the pod is not isolated 85 | // from the point of view of this policy. This follows the NetworkPolicy spec.PolicyTypes. 86 | PodIsolation []networking.PolicyType `json:"podIsolation,omitempty"` 87 | 88 | // PodSelectorEndpoints contains information about the pods 89 | // matching the podSelector 90 | PodSelectorEndpoints []PodEndpoint `json:"podSelectorEndpoints,omitempty"` 91 | 92 | // Ingress is the list of ingress rules containing resolved network addresses 93 | Ingress []EndpointInfo `json:"ingress,omitempty"` 94 | 95 | // Egress is the list of egress rules containing resolved network addresses 96 | Egress []EndpointInfo `json:"egress,omitempty"` 97 | } 98 | 99 | // PolicyEndpointStatus defines the observed state of PolicyEndpoint 100 | type PolicyEndpointStatus struct { 101 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 102 | // Important: Run "make" to regenerate code after modifying this file 103 | } 104 | 105 | //+kubebuilder:object:root=true 106 | //+kubebuilder:subresource:status 107 | 108 | // PolicyEndpoint is the Schema for the policyendpoints API 109 | type PolicyEndpoint struct { 110 | metav1.TypeMeta `json:",inline"` 111 | metav1.ObjectMeta `json:"metadata,omitempty"` 112 | 113 | Spec PolicyEndpointSpec `json:"spec,omitempty"` 114 | Status PolicyEndpointStatus `json:"status,omitempty"` 115 | } 116 | 117 | //+kubebuilder:object:root=true 118 | 119 | // PolicyEndpointList contains a list of PolicyEndpoint 120 | type PolicyEndpointList struct { 121 | metav1.TypeMeta `json:",inline"` 122 | metav1.ListMeta `json:"metadata,omitempty"` 123 | Items []PolicyEndpoint `json:"items"` 124 | } 125 | 126 | func init() { 127 | SchemeBuilder.Register(&PolicyEndpoint{}, &PolicyEndpointList{}) 128 | } 129 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | v1 "k8s.io/api/core/v1" 25 | networkingv1 "k8s.io/api/networking/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | runtime "k8s.io/apimachinery/pkg/runtime" 28 | ) 29 | 30 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 31 | func (in *EndpointInfo) DeepCopyInto(out *EndpointInfo) { 32 | *out = *in 33 | if in.Except != nil { 34 | in, out := &in.Except, &out.Except 35 | *out = make([]NetworkAddress, len(*in)) 36 | copy(*out, *in) 37 | } 38 | if in.Ports != nil { 39 | in, out := &in.Ports, &out.Ports 40 | *out = make([]Port, len(*in)) 41 | for i := range *in { 42 | (*in)[i].DeepCopyInto(&(*out)[i]) 43 | } 44 | } 45 | } 46 | 47 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointInfo. 48 | func (in *EndpointInfo) DeepCopy() *EndpointInfo { 49 | if in == nil { 50 | return nil 51 | } 52 | out := new(EndpointInfo) 53 | in.DeepCopyInto(out) 54 | return out 55 | } 56 | 57 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 58 | func (in *PodEndpoint) DeepCopyInto(out *PodEndpoint) { 59 | *out = *in 60 | } 61 | 62 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodEndpoint. 63 | func (in *PodEndpoint) DeepCopy() *PodEndpoint { 64 | if in == nil { 65 | return nil 66 | } 67 | out := new(PodEndpoint) 68 | in.DeepCopyInto(out) 69 | return out 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *PolicyEndpoint) DeepCopyInto(out *PolicyEndpoint) { 74 | *out = *in 75 | out.TypeMeta = in.TypeMeta 76 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 77 | in.Spec.DeepCopyInto(&out.Spec) 78 | out.Status = in.Status 79 | } 80 | 81 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyEndpoint. 82 | func (in *PolicyEndpoint) DeepCopy() *PolicyEndpoint { 83 | if in == nil { 84 | return nil 85 | } 86 | out := new(PolicyEndpoint) 87 | in.DeepCopyInto(out) 88 | return out 89 | } 90 | 91 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 92 | func (in *PolicyEndpoint) DeepCopyObject() runtime.Object { 93 | if c := in.DeepCopy(); c != nil { 94 | return c 95 | } 96 | return nil 97 | } 98 | 99 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 100 | func (in *PolicyEndpointList) DeepCopyInto(out *PolicyEndpointList) { 101 | *out = *in 102 | out.TypeMeta = in.TypeMeta 103 | in.ListMeta.DeepCopyInto(&out.ListMeta) 104 | if in.Items != nil { 105 | in, out := &in.Items, &out.Items 106 | *out = make([]PolicyEndpoint, len(*in)) 107 | for i := range *in { 108 | (*in)[i].DeepCopyInto(&(*out)[i]) 109 | } 110 | } 111 | } 112 | 113 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyEndpointList. 114 | func (in *PolicyEndpointList) DeepCopy() *PolicyEndpointList { 115 | if in == nil { 116 | return nil 117 | } 118 | out := new(PolicyEndpointList) 119 | in.DeepCopyInto(out) 120 | return out 121 | } 122 | 123 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 124 | func (in *PolicyEndpointList) DeepCopyObject() runtime.Object { 125 | if c := in.DeepCopy(); c != nil { 126 | return c 127 | } 128 | return nil 129 | } 130 | 131 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 132 | func (in *PolicyEndpointSpec) DeepCopyInto(out *PolicyEndpointSpec) { 133 | *out = *in 134 | if in.PodSelector != nil { 135 | in, out := &in.PodSelector, &out.PodSelector 136 | *out = new(metav1.LabelSelector) 137 | (*in).DeepCopyInto(*out) 138 | } 139 | out.PolicyRef = in.PolicyRef 140 | if in.PodIsolation != nil { 141 | in, out := &in.PodIsolation, &out.PodIsolation 142 | *out = make([]networkingv1.PolicyType, len(*in)) 143 | copy(*out, *in) 144 | } 145 | if in.PodSelectorEndpoints != nil { 146 | in, out := &in.PodSelectorEndpoints, &out.PodSelectorEndpoints 147 | *out = make([]PodEndpoint, len(*in)) 148 | copy(*out, *in) 149 | } 150 | if in.Ingress != nil { 151 | in, out := &in.Ingress, &out.Ingress 152 | *out = make([]EndpointInfo, len(*in)) 153 | for i := range *in { 154 | (*in)[i].DeepCopyInto(&(*out)[i]) 155 | } 156 | } 157 | if in.Egress != nil { 158 | in, out := &in.Egress, &out.Egress 159 | *out = make([]EndpointInfo, len(*in)) 160 | for i := range *in { 161 | (*in)[i].DeepCopyInto(&(*out)[i]) 162 | } 163 | } 164 | } 165 | 166 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyEndpointSpec. 167 | func (in *PolicyEndpointSpec) DeepCopy() *PolicyEndpointSpec { 168 | if in == nil { 169 | return nil 170 | } 171 | out := new(PolicyEndpointSpec) 172 | in.DeepCopyInto(out) 173 | return out 174 | } 175 | 176 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 177 | func (in *PolicyEndpointStatus) DeepCopyInto(out *PolicyEndpointStatus) { 178 | *out = *in 179 | } 180 | 181 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyEndpointStatus. 182 | func (in *PolicyEndpointStatus) DeepCopy() *PolicyEndpointStatus { 183 | if in == nil { 184 | return nil 185 | } 186 | out := new(PolicyEndpointStatus) 187 | in.DeepCopyInto(out) 188 | return out 189 | } 190 | 191 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 192 | func (in *PolicyReference) DeepCopyInto(out *PolicyReference) { 193 | *out = *in 194 | } 195 | 196 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyReference. 197 | func (in *PolicyReference) DeepCopy() *PolicyReference { 198 | if in == nil { 199 | return nil 200 | } 201 | out := new(PolicyReference) 202 | in.DeepCopyInto(out) 203 | return out 204 | } 205 | 206 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 207 | func (in *Port) DeepCopyInto(out *Port) { 208 | *out = *in 209 | if in.Protocol != nil { 210 | in, out := &in.Protocol, &out.Protocol 211 | *out = new(v1.Protocol) 212 | **out = **in 213 | } 214 | if in.Port != nil { 215 | in, out := &in.Port, &out.Port 216 | *out = new(int32) 217 | **out = **in 218 | } 219 | if in.EndPort != nil { 220 | in, out := &in.EndPort, &out.EndPort 221 | *out = new(int32) 222 | **out = **in 223 | } 224 | } 225 | 226 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Port. 227 | func (in *Port) DeepCopy() *Port { 228 | if in == nil { 229 | return nil 230 | } 231 | out := new(Port) 232 | in.DeepCopyInto(out) 233 | return out 234 | } 235 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | crds/kustomization.yaml -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: amazon-network-policy-controller-k8s 3 | version: 1.0.4 4 | appVersion: v1.0.4 5 | description: A Helm chart for Amazon Network Policy Controller K8s 6 | icon: https://raw.githubusercontent.com/aws/eks-charts/master/docs/logo/aws.png 7 | home: https://github.com/aws/amazon-network-policy-controller-k8s 8 | sources: 9 | - https://github.com/aws/amazon-network-policy-controller-k8s 10 | keywords: 11 | - eks 12 | - networking 13 | - network-policy 14 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/README.md: -------------------------------------------------------------------------------- 1 | # AMAZON NETWORK POLICY CONTROLLER 2 | 3 | This chart provides a Kubernetes deployment for the Amazon Network Policy Controller 4 | 5 | ## Prerequisites 6 | 7 | - Kubernetes 1.24+ running on AWS 8 | - Helm 3.0+ 9 | 10 | ## Installing the Chart 11 | 12 | To install the chart with the release name `amazon-network-policy-controller-k8s` and default configuration: 13 | 14 | ```shell 15 | $ helm install amazon-network-policy-controller-k8s --namespace kube-system charts/amazon-network-policy-controller-k8s 16 | ``` 17 | 18 | 19 | ## Configuration 20 | 21 | The following table lists the configurable parameters for this chart and their default values. 22 | 23 | | Parameter | Description | Default | 24 | |------------------------------|---------------------------------------------------------------|---------------------------------------------------------| 25 | | fullnameOverride | Override the fullname of the chart | amazon-network-policy-controller-k8s | 26 | | nameOverride | override for the name of the Helm Chart | amazon-network-policy-controller-k8s | 27 | | image.repository | ECR repository to use. Should match your cluster | public.ecr.aws/eks/amazon-network-policy-controller-k8s | 28 | | image.tag | Image tag | v1.0.4 | 29 | | enableConfigMapCheck | Enable configmap check to enable/disable controller in Control Plane | false | 30 | | endpointChunkSize | Number of endpoints to include in a single policy endpoints resource | 1000 | 31 | | maxConcurrentReconciles | Maximum number of concurrent reconcile loops | 3 | 32 | | podUpdateBatchPeriodDuration | Duration between batch updates of pods in seconds | 1 | 33 | | livenessProbe | Liveness Probe configuration for controller | see `values.yaml` | 34 | | readinessProbe | Readiness Probe configuration for controller | see `values.yaml` | 35 | 36 | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install` or provide a YAML file containing the values for the above parameters: 37 | 38 | ```shell 39 | $ helm install amazon-network-policy-controller-k8s --namespace kube-system ./charts/amazon-network-policy-controller-k8s --values values.yaml 40 | ``` 41 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "amazon-network-policy-controller.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "amazon-network-policy-controller.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "amazon-network-policy-controller.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "amazon-network-policy-controller.labels" -}} 38 | helm.sh/chart: {{ include "amazon-network-policy-controller.chart" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end -}} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "amazon-network-policy-controller.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "amazon-network-policy-controller.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end -}} 52 | 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "amazon-network-policy-controller.serviceAccountName" -}} 58 | {{ default (include "amazon-network-policy-controller.fullname" .) .Values.serviceAccount.name }} 59 | {{- end -}} 60 | 61 | {{/* 62 | The amazon-network-policy-controller image to use 63 | */}} 64 | {{- define "amazon-network-policy-controller.image" -}} 65 | {{- if .Values.image.override }} 66 | {{- .Values.image.override }} 67 | {{- else }} 68 | {{- printf "%s:%s" .Values.image.repository .Values.image.tag }} 69 | {{- end }} 70 | {{- end }} 71 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "amazon-network-policy-controller.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 8 | spec: 9 | replicas: {{ .Values.replicaCount }} 10 | selector: 11 | matchLabels: 12 | {{- include "amazon-network-policy-controller.selectorLabels" . | nindent 6 }} 13 | {{- with .Values.updateStrategy }} 14 | strategy: 15 | {{ toYaml . | nindent 4 }} 16 | {{- end }} 17 | template: 18 | metadata: 19 | labels: 20 | {{- include "amazon-network-policy-controller.selectorLabels" . | nindent 8 }} 21 | spec: 22 | containers: 23 | - name: {{ .Chart.Name }} 24 | image: {{ include "amazon-network-policy-controller.image" . }} 25 | args: 26 | - --enable-configmap-check={{ .Values.enableConfigMapCheck }} 27 | {{- if .Values.endpointChunkSize }} 28 | - --endpoint-chunk-size={{ .Values.endpointChunkSize }} 29 | {{- end }} 30 | {{- if .Values.maxConcurrentReconciles }} 31 | - --max-concurrent-reconciles={{ .Values.maxConcurrentReconciles }} 32 | {{- end }} 33 | {{- if .Values.podUpdateBatchPeriodDuration }} 34 | - --pod-update-batch-period-duration={{ .Values.updateBatchPeriodDuration }} 35 | {{- end }} 36 | {{- with .Values.livenessProbe }} 37 | livenessProbe: 38 | {{- toYaml . | nindent 10 }} 39 | {{- end }} 40 | {{- with .Values.readinessProbe }} 41 | readinessProbe: 42 | {{- toYaml . | nindent 10 }} 43 | {{- end }} 44 | securityContext: 45 | {{- toYaml .Values.securityContext | nindent 10 }} 46 | {{- with .Values.resources }} 47 | resources: 48 | {{- toYaml . | nindent 10 }} 49 | {{- end }} 50 | serviceAccountName: {{ include "amazon-network-policy-controller.serviceAccountName" . }} 51 | terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} 52 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: {{ template "amazon-network-policy-controller.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resourceNames: 12 | - amazon-vpc-cni 13 | resources: 14 | - configmaps 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | --- 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | kind: Role 22 | metadata: 23 | labels: 24 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 25 | name: {{ template "amazon-network-policy-controller.fullname" . }}-leader-election-role 26 | namespace: {{ .Release.Namespace }} 27 | rules: 28 | - apiGroups: 29 | - coordination.k8s.io 30 | resources: 31 | - leases 32 | verbs: 33 | - create 34 | - apiGroups: 35 | - coordination.k8s.io 36 | resourceNames: 37 | - amazon-network-policy-controller-k8s 38 | resources: 39 | - leases 40 | verbs: 41 | - get 42 | - update 43 | - patch 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - events 48 | verbs: 49 | - create 50 | - patch 51 | --- 52 | apiVersion: rbac.authorization.k8s.io/v1 53 | kind: ClusterRole 54 | metadata: 55 | labels: 56 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 57 | name: {{ template "amazon-network-policy-controller.fullname" . }} 58 | rules: 59 | - apiGroups: 60 | - "" 61 | resources: 62 | - namespaces 63 | verbs: 64 | - get 65 | - list 66 | - watch 67 | - apiGroups: 68 | - "" 69 | resources: 70 | - pods 71 | verbs: 72 | - get 73 | - list 74 | - watch 75 | - apiGroups: 76 | - "" 77 | resources: 78 | - services 79 | verbs: 80 | - get 81 | - list 82 | - watch 83 | - apiGroups: 84 | - networking.k8s.aws 85 | resources: 86 | - policyendpoints 87 | verbs: 88 | - create 89 | - delete 90 | - get 91 | - list 92 | - patch 93 | - update 94 | - watch 95 | - apiGroups: 96 | - networking.k8s.aws 97 | resources: 98 | - policyendpoints/finalizers 99 | verbs: 100 | - update 101 | - apiGroups: 102 | - networking.k8s.aws 103 | resources: 104 | - policyendpoints/status 105 | verbs: 106 | - get 107 | - patch 108 | - update 109 | - apiGroups: 110 | - networking.k8s.io 111 | resources: 112 | - networkpolicies 113 | verbs: 114 | - get 115 | - list 116 | - patch 117 | - update 118 | - watch 119 | --- 120 | apiVersion: rbac.authorization.k8s.io/v1 121 | kind: RoleBinding 122 | metadata: 123 | labels: 124 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 125 | name: {{ template "amazon-network-policy-controller.fullname" . }}-rolebinding 126 | namespace: {{ .Release.Namespace }} 127 | roleRef: 128 | apiGroup: rbac.authorization.k8s.io 129 | kind: Role 130 | name: {{ template "amazon-network-policy-controller.fullname" . }} 131 | subjects: 132 | - kind: ServiceAccount 133 | name: {{ template "amazon-network-policy-controller.fullname" . }} 134 | namespace: {{ .Release.Namespace }} 135 | --- 136 | apiVersion: rbac.authorization.k8s.io/v1 137 | kind: RoleBinding 138 | metadata: 139 | labels: 140 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 141 | name: {{ template "amazon-network-policy-controller.fullname" . }}-leader-election-rolebinding 142 | namespace: {{ .Release.Namespace }} 143 | roleRef: 144 | apiGroup: rbac.authorization.k8s.io 145 | kind: Role 146 | name: {{ template "amazon-network-policy-controller.fullname" . }}-leader-election-role 147 | subjects: 148 | - kind: ServiceAccount 149 | name: {{ template "amazon-network-policy-controller.fullname" . }} 150 | namespace: {{ .Release.Namespace }} 151 | --- 152 | apiVersion: rbac.authorization.k8s.io/v1 153 | kind: ClusterRoleBinding 154 | metadata: 155 | labels: 156 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 157 | name: {{ template "amazon-network-policy-controller.fullname" . }}-rolebinding 158 | roleRef: 159 | apiGroup: rbac.authorization.k8s.io 160 | kind: ClusterRole 161 | name: {{ template "amazon-network-policy-controller.fullname" . }} 162 | subjects: 163 | - kind: ServiceAccount 164 | name: {{ template "amazon-network-policy-controller.fullname" . }} 165 | namespace: {{ .Release.Namespace }} -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | {{- include "amazon-network-policy-controller.labels" . | nindent 4 }} 6 | {{- with .Values.serviceAccount.annotations }} 7 | annotations: 8 | {{- toYaml . | nindent 4 }} 9 | {{- end }} 10 | name: {{ include "amazon-network-policy-controller.serviceAccountName" . }} 11 | namespace: {{ .Release.Namespace }} 12 | -------------------------------------------------------------------------------- /charts/amazon-network-policy-controller-k8s/values.yaml: -------------------------------------------------------------------------------- 1 | 2 | replicaCount: 2 3 | 4 | image: 5 | repository: public.ecr.aws/eks/amazon-network-policy-controller-k8s 6 | tag: v1.0.4 7 | pullPolicy: IfNotPresent 8 | override: "" 9 | 10 | nameOverride: "" 11 | fullnameOverride: "" 12 | 13 | serviceAccount: 14 | name: "" 15 | annotations: {} 16 | 17 | enableConfigMapCheck: true 18 | endpointChunkSize: "" 19 | maxConcurrentReconciles: "" 20 | podUpdateBatchPeriodDuration: "" 21 | 22 | securityContext: 23 | allowPrivilegeEscalation: false 24 | capabilities: 25 | drop: 26 | - ALL 27 | 28 | updateStrategy: 29 | type: RollingUpdate 30 | rollingUpdate: 31 | maxSurge: 25% 32 | maxUnavailable: 25% 33 | 34 | # Time period for the controller pod to do a graceful shutdown 35 | terminationGracePeriodSeconds: 10 36 | 37 | # Liveness probe configuration for the controller 38 | livenessProbe: 39 | httpGet: 40 | path: /healthz 41 | port: 8081 42 | initialDelaySeconds: 15 43 | periodSeconds: 20 44 | 45 | # Readiness probe configuration for the controller 46 | readinessProbe: 47 | httpGet: 48 | path: /readyz 49 | port: 8081 50 | initialDelaySeconds: 5 51 | periodSeconds: 10 52 | 53 | resources: 54 | limits: 55 | memory: 1Gi 56 | requests: 57 | memory: 30Mi 58 | cpu: 0.3 59 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | "os" 23 | 24 | "github.com/go-logr/logr" 25 | "github.com/spf13/pflag" 26 | "go.uber.org/zap/zapcore" 27 | 28 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 29 | // to ensure that exec-entrypoint and run can make use of them. 30 | _ "net/http/pprof" 31 | 32 | _ "k8s.io/client-go/plugin/pkg/client/auth" 33 | 34 | "k8s.io/apimachinery/pkg/runtime" 35 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 36 | "k8s.io/client-go/kubernetes" 37 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 38 | ctrl "sigs.k8s.io/controller-runtime" 39 | "sigs.k8s.io/controller-runtime/pkg/healthz" 40 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 41 | 42 | policyinfo "github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1" 43 | "github.com/aws/amazon-network-policy-controller-k8s/internal/controllers" 44 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/config" 45 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 46 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/policyendpoints" 47 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/utils/configmap" 48 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/version" 49 | //+kubebuilder:scaffold:imports 50 | ) 51 | 52 | var ( 53 | scheme = runtime.NewScheme() 54 | setupLog = ctrl.Log.WithName("setup") 55 | ) 56 | 57 | func init() { 58 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 59 | 60 | utilruntime.Must(policyinfo.AddToScheme(scheme)) 61 | //+kubebuilder:scaffold:scheme 62 | } 63 | 64 | func main() { 65 | infoLogger := getLoggerWithLogLevel("info") 66 | infoLogger.Info("version", 67 | "GitVersion", version.GitVersion, 68 | "GitCommit", version.GitCommit, 69 | "BuildDate", version.BuildDate, 70 | ) 71 | controllerCFG, err := loadControllerConfig() 72 | if err != nil { 73 | infoLogger.Error(err, "unable to load controller config") 74 | os.Exit(1) 75 | } 76 | ctrlLogger := getLoggerWithLogLevel(controllerCFG.LogLevel) 77 | ctrl.SetLogger(ctrlLogger) 78 | 79 | restCFG, err := config.BuildRestConfig(controllerCFG.RuntimeConfig) 80 | if err != nil { 81 | setupLog.Error(err, "unable to build REST config") 82 | os.Exit(1) 83 | } 84 | 85 | clientSetRestConfig, err := config.BuildRestConfig(controllerCFG.RuntimeConfig) 86 | if err != nil { 87 | setupLog.Error(err, "unable to build REST config") 88 | os.Exit(1) 89 | } 90 | clientSetRestConfig.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json" 91 | clientSetRestConfig.ContentType = "application/vnd.kubernetes.protobuf" 92 | 93 | clientSet, err := kubernetes.NewForConfig(clientSetRestConfig) 94 | if err != nil { 95 | setupLog.Error(err, "unable to obtain clientSet") 96 | os.Exit(1) 97 | } 98 | 99 | ctx := ctrl.SetupSignalHandler() 100 | enableNetworkPolicyController := true 101 | setupLog.Info("Checking args for enabling CM", "ConfigMapEnabled", controllerCFG.EnableConfigMapCheck) 102 | setupLog.Info("Checking args for PE chunk size", "PEChunkSize", controllerCFG.EndpointChunkSize) 103 | setupLog.Info("Checking args for policy batch time", "NPBatchTime", controllerCFG.PodUpdateBatchPeriodDuration) 104 | setupLog.Info("Checking args for reconciler count", "ReconcilerCount", controllerCFG.MaxConcurrentReconciles) 105 | 106 | if controllerCFG.EnableConfigMapCheck { 107 | var cancelFn context.CancelFunc 108 | ctx, cancelFn = context.WithCancel(ctx) 109 | setupLog.Info("Enable network policy controller based on configuration", "configmap", configmap.GetControllerConfigMapId()) 110 | configMapManager := config.NewConfigmapManager(configmap.GetControllerConfigMapId(), 111 | clientSet, cancelFn, configmap.GetConfigmapCheckFn(), ctrl.Log.WithName("configmap-manager")) 112 | if err := configMapManager.MonitorConfigMap(ctx); err != nil { 113 | setupLog.Error(err, "Unable to monitor configmap for checking if controller is enabled") 114 | os.Exit(1) 115 | } 116 | enableNetworkPolicyController = configMapManager.IsControllerEnabled() 117 | if !enableNetworkPolicyController { 118 | setupLog.Info("Disabling leader election since network policy controller is not enabled") 119 | controllerCFG.RuntimeConfig.EnableLeaderElection = false 120 | } 121 | } 122 | 123 | rtOpts := config.BuildRuntimeOptions(controllerCFG.RuntimeConfig, scheme) 124 | 125 | mgr, err := ctrl.NewManager(restCFG, rtOpts) 126 | if err != nil { 127 | setupLog.Error(err, "unable to create controller manager") 128 | os.Exit(1) 129 | } 130 | 131 | policyEndpointsManager := policyendpoints.NewPolicyEndpointsManager(mgr.GetClient(), 132 | controllerCFG.EndpointChunkSize, ctrl.Log.WithName("endpoints-manager")) 133 | finalizerManager := k8s.NewDefaultFinalizerManager(mgr.GetClient(), ctrl.Log.WithName("finalizer-manager")) 134 | policyController := controllers.NewPolicyReconciler(mgr.GetClient(), policyEndpointsManager, 135 | controllerCFG, finalizerManager, ctrl.Log.WithName("controllers").WithName("policy")) 136 | if enableNetworkPolicyController { 137 | setupLog.Info("Network Policy controller is enabled, starting watches") 138 | if err := policyController.SetupWithManager(ctx, mgr); err != nil { 139 | setupLog.Error(err, "Unable to setup network policy controller") 140 | os.Exit(1) 141 | } 142 | } 143 | 144 | //+kubebuilder:scaffold:builder 145 | 146 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 147 | setupLog.Error(err, "unable to set up health check") 148 | os.Exit(1) 149 | } 150 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 151 | setupLog.Error(err, "unable to set up ready check") 152 | os.Exit(1) 153 | } 154 | 155 | if controllerCFG.EnableGoProfiling { 156 | go func() { 157 | if err := http.ListenAndServe("localhost:6060", nil); err != nil { 158 | setupLog.Error(err, "Error starting HTTP server") 159 | } 160 | }() 161 | } 162 | 163 | setupLog.Info("starting controller manager") 164 | if err := mgr.Start(ctx); err != nil { 165 | setupLog.Error(err, "problem running controller manager") 166 | os.Exit(1) 167 | } 168 | setupLog.Info("controller manager stopped") 169 | 170 | } 171 | 172 | // loadControllerConfig loads the controller configuration 173 | func loadControllerConfig() (config.ControllerConfig, error) { 174 | controllerConfig := config.ControllerConfig{} 175 | fs := pflag.NewFlagSet("", pflag.ExitOnError) 176 | controllerConfig.BindFlags(fs) 177 | 178 | if err := fs.Parse(os.Args); err != nil { 179 | return controllerConfig, err 180 | } 181 | 182 | return controllerConfig, nil 183 | } 184 | 185 | // getLoggerWithLogLevel returns logger with specific log level. 186 | func getLoggerWithLogLevel(logLevel string) logr.Logger { 187 | var zapLevel zapcore.Level 188 | switch logLevel { 189 | case "info": 190 | zapLevel = zapcore.InfoLevel 191 | case "debug": 192 | zapLevel = zapcore.DebugLevel 193 | default: 194 | zapLevel = zapcore.InfoLevel 195 | } 196 | return zap.New(zap.UseDevMode(false), 197 | zap.Level(zapLevel), 198 | zap.StacktraceLevel(zapcore.FatalLevel), 199 | ) 200 | } 201 | -------------------------------------------------------------------------------- /config/controller/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-k8s 5 | labels: 6 | app.kubernetes.io/component: controller 7 | spec: 8 | selector: 9 | matchLabels: 10 | app.kubernetes.io/component: controller 11 | replicas: 1 12 | template: 13 | metadata: 14 | annotations: 15 | kubectl.kubernetes.io/default-container: controller 16 | labels: 17 | app.kubernetes.io/component: controller 18 | spec: 19 | containers: 20 | - image: controller:latest 21 | args: 22 | - --enable-configmap-check=false 23 | name: controller 24 | securityContext: 25 | allowPrivilegeEscalation: false 26 | capabilities: 27 | drop: 28 | - "ALL" 29 | livenessProbe: 30 | httpGet: 31 | path: /healthz 32 | port: 8081 33 | initialDelaySeconds: 15 34 | periodSeconds: 20 35 | readinessProbe: 36 | httpGet: 37 | path: /readyz 38 | port: 8081 39 | initialDelaySeconds: 5 40 | periodSeconds: 10 41 | serviceAccountName: controller-k8s 42 | terminationGracePeriodSeconds: 10 -------------------------------------------------------------------------------- /config/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - controller.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: public.ecr.aws/eks/amazon-network-policy-controller-k8s 8 | newTag: v0.5.0 9 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/networking.k8s.aws_policyendpoints.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_policyendpoints.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-controller, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_policyendpoints.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_policyendpoints.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 7 | name: policyendpoints.networking.k8s.aws 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_policyendpoints.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: policyendpoints.networking.k8s.aws 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: kube-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: amazon-network-policy- 10 | 11 | # Labels to add to all resources and selectors. 12 | # Labels to add to all resources and selectors. 13 | commonLabels: 14 | app.kubernetes.io/name: amazon-network-policy-controller-k8s 15 | 16 | resources: 17 | - ../crd 18 | - ../rbac 19 | - ../controller 20 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 21 | # crd/kustomization.yaml 22 | #- ../webhook 23 | # [CERTMANAGER] To enable cert-controller, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 24 | #- ../certmanager 25 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 26 | #- ../prometheus 27 | 28 | patchesStrategicMerge: 29 | # Protect the /metrics endpoint by putting it behind auth. 30 | # If you want your controller-controller to expose the /metrics 31 | # endpoint w/o any authn/z, please comment the following line. 32 | # - manager_auth_proxy_patch.yaml 33 | 34 | 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 37 | # crd/kustomization.yaml 38 | #- manager_webhook_patch.yaml 39 | 40 | # [CERTMANAGER] To enable cert-controller, uncomment all sections with 'CERTMANAGER'. 41 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 42 | # 'CERTMANAGER' needs to be enabled to use ca injection 43 | #- webhookcainjection_patch.yaml 44 | 45 | # [CERTMANAGER] To enable cert-controller, uncomment all sections with 'CERTMANAGER' prefix. 46 | # Uncomment the following replacements to add the cert-controller CA injection annotations 47 | #replacements: 48 | # - source: # Add cert-controller annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs 49 | # kind: Certificate 50 | # group: cert-controller.io 51 | # version: v1 52 | # name: serving-cert # this name should match the one in certificate.yaml 53 | # fieldPath: .metadata.namespace # namespace of the certificate CR 54 | # targets: 55 | # - select: 56 | # kind: ValidatingWebhookConfiguration 57 | # fieldPaths: 58 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 59 | # options: 60 | # delimiter: '/' 61 | # index: 0 62 | # create: true 63 | # - select: 64 | # kind: MutatingWebhookConfiguration 65 | # fieldPaths: 66 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 67 | # options: 68 | # delimiter: '/' 69 | # index: 0 70 | # create: true 71 | # - select: 72 | # kind: CustomResourceDefinition 73 | # fieldPaths: 74 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 75 | # options: 76 | # delimiter: '/' 77 | # index: 0 78 | # create: true 79 | # - source: 80 | # kind: Certificate 81 | # group: cert-controller.io 82 | # version: v1 83 | # name: serving-cert # this name should match the one in certificate.yaml 84 | # fieldPath: .metadata.name 85 | # targets: 86 | # - select: 87 | # kind: ValidatingWebhookConfiguration 88 | # fieldPaths: 89 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 90 | # options: 91 | # delimiter: '/' 92 | # index: 1 93 | # create: true 94 | # - select: 95 | # kind: MutatingWebhookConfiguration 96 | # fieldPaths: 97 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 98 | # options: 99 | # delimiter: '/' 100 | # index: 1 101 | # create: true 102 | # - select: 103 | # kind: CustomResourceDefinition 104 | # fieldPaths: 105 | # - .metadata.annotations.[cert-controller.io/inject-ca-from] 106 | # options: 107 | # delimiter: '/' 108 | # index: 1 109 | # create: true 110 | # - source: # Add cert-controller annotation to the webhook Service 111 | # kind: Service 112 | # version: v1 113 | # name: webhook-service 114 | # fieldPath: .metadata.name # namespace of the service 115 | # targets: 116 | # - select: 117 | # kind: Certificate 118 | # group: cert-controller.io 119 | # version: v1 120 | # fieldPaths: 121 | # - .spec.dnsNames.0 122 | # - .spec.dnsNames.1 123 | # options: 124 | # delimiter: '.' 125 | # index: 0 126 | # create: true 127 | # - source: 128 | # kind: Service 129 | # version: v1 130 | # name: webhook-service 131 | # fieldPath: .metadata.namespace # namespace of the service 132 | # targets: 133 | # - select: 134 | # kind: Certificate 135 | # group: cert-controller.io 136 | # version: v1 137 | # fieldPaths: 138 | # - .spec.dnsNames.0 139 | # - .spec.dnsNames.1 140 | # options: 141 | # delimiter: '.' 142 | # index: 1 143 | # create: true 144 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller controller, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-controller 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | affinity: 12 | nodeAffinity: 13 | requiredDuringSchedulingIgnoredDuringExecution: 14 | nodeSelectorTerms: 15 | - matchExpressions: 16 | - key: kubernetes.io/arch 17 | operator: In 18 | values: 19 | - amd64 20 | - arm64 21 | - ppc64le 22 | - s390x 23 | - key: kubernetes.io/os 24 | operator: In 25 | values: 26 | - linux 27 | containers: 28 | - name: kube-rbac-proxy 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - "ALL" 34 | image: registry.k8s.io/kubebuilder/kube-rbac-proxy:v0.13.1 35 | args: 36 | - "--secure-listen-address=0.0.0.0:8443" 37 | - "--upstream=http://127.0.0.1:8080/" 38 | - "--logtostderr=true" 39 | - "--v=0" 40 | ports: 41 | - containerPort: 8443 42 | protocol: TCP 43 | name: https 44 | resources: 45 | limits: 46 | cpu: 500m 47 | memory: 128Mi 48 | requests: 49 | cpu: 5m 50 | memory: 64Mi 51 | - name: controller 52 | args: 53 | - "--health-probe-bind-address=:8081" 54 | - "--metrics-bind-address=127.0.0.1:8080" 55 | - "--leader-elect" 56 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-controller 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: controller 11 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-controller 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-controller-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 12 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-controller-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-controller 27 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 9 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 9 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 9 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-controller 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-controller 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-controller-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-controller-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-controller 22 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your controller will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | # - auth_proxy_service.yaml 16 | # - auth_proxy_role.yaml 17 | # - auth_proxy_role_binding.yaml 18 | # - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-k8s-leader-election-role 13 | rules: 14 | - apiGroups: 15 | - coordination.k8s.io 16 | resources: 17 | - leases 18 | verbs: 19 | - create 20 | - apiGroups: 21 | - coordination.k8s.io 22 | resources: 23 | - leases 24 | resourceNames: 25 | - amazon-network-policy-controller-k8s 26 | verbs: 27 | - get 28 | - update 29 | - patch 30 | - apiGroups: 31 | - "" 32 | resources: 33 | - events 34 | verbs: 35 | - create 36 | - patch -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 9 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: controller-k8s-leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-k8s -------------------------------------------------------------------------------- /config/rbac/policyendpoint_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit policyendpoints. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: policyendpoint-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 11 | app.kubernetes.io/managed-by: kustomize 12 | name: policyendpoint-editor-role 13 | rules: 14 | - apiGroups: 15 | - networking.k8s.aws 16 | resources: 17 | - policyendpoints 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - networking.k8s.aws 28 | resources: 29 | - policyendpoints/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/policyendpoint_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view policyendpoints. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: policyendpoint-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 11 | app.kubernetes.io/managed-by: kustomize 12 | name: policyendpoint-viewer-role 13 | rules: 14 | - apiGroups: 15 | - networking.k8s.aws 16 | resources: 17 | - policyendpoints 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - networking.k8s.aws 24 | resources: 25 | - policyendpoints/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: controller-k8s 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - namespaces 11 | - pods 12 | - services 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - networking.k8s.aws 19 | resources: 20 | - policyendpoints 21 | verbs: 22 | - create 23 | - delete 24 | - get 25 | - list 26 | - patch 27 | - update 28 | - watch 29 | - apiGroups: 30 | - networking.k8s.aws 31 | resources: 32 | - policyendpoints/finalizers 33 | verbs: 34 | - update 35 | - apiGroups: 36 | - networking.k8s.aws 37 | resources: 38 | - policyendpoints/status 39 | verbs: 40 | - get 41 | - patch 42 | - update 43 | - apiGroups: 44 | - networking.k8s.io 45 | resources: 46 | - networkpolicies 47 | verbs: 48 | - get 49 | - list 50 | - patch 51 | - update 52 | - watch 53 | --- 54 | apiVersion: rbac.authorization.k8s.io/v1 55 | kind: Role 56 | metadata: 57 | name: controller-k8s 58 | namespace: system 59 | rules: 60 | - apiGroups: 61 | - "" 62 | resourceNames: 63 | - amazon-vpc-cni 64 | resources: 65 | - configmaps 66 | verbs: 67 | - get 68 | - list 69 | - watch 70 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: controller-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 9 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-k8s-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: controller-k8s 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-k8s 19 | --- 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | kind: RoleBinding 22 | metadata: 23 | labels: 24 | app.kubernetes.io/name: rolebinding 25 | app.kubernetes.io/instance: configmap-rolebinding 26 | app.kubernetes.io/component: rbac 27 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 28 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 29 | app.kubernetes.io/managed-by: kustomize 30 | name: controller-k8s-rolebinding 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: Role 34 | name: controller-k8s 35 | subjects: 36 | - kind: ServiceAccount 37 | name: controller-k8s 38 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 9 | name: controller-k8s 10 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - networking_v1alpha1_policyendpoint.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/samples/networking_v1alpha1_policyendpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.aws/v1alpha1 2 | kind: PolicyEndpoint 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: policyendpoint 6 | app.kubernetes.io/instance: policyendpoint-sample 7 | app.kubernetes.io/part-of: amazon-network-policy-controller-k8s 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: amazon-network-policy-controller-k8s 10 | name: policyendpoint-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws/amazon-network-policy-controller-k8s 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.6 6 | 7 | require ( 8 | github.com/go-logr/logr v1.4.2 9 | github.com/golang/mock v1.6.0 10 | github.com/google/go-cmp v0.7.0 11 | github.com/onsi/ginkgo/v2 v2.22.2 12 | github.com/onsi/gomega v1.36.2 13 | github.com/pkg/errors v0.9.1 14 | github.com/samber/lo v1.49.1 15 | github.com/spf13/pflag v1.0.6 16 | github.com/stretchr/testify v1.10.0 17 | go.uber.org/zap v1.27.0 18 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 19 | k8s.io/api v0.32.2 20 | k8s.io/apimachinery v0.32.2 21 | k8s.io/client-go v0.32.2 22 | sigs.k8s.io/controller-runtime v0.20.2 23 | ) 24 | 25 | require ( 26 | github.com/beorn7/perks v1.0.1 // indirect 27 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 28 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 29 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 30 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 31 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 32 | github.com/fsnotify/fsnotify v1.7.0 // indirect 33 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 34 | github.com/go-logr/zapr v1.3.0 // indirect 35 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 36 | github.com/go-openapi/jsonreference v0.20.2 // indirect 37 | github.com/go-openapi/swag v0.23.0 // indirect 38 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 39 | github.com/gogo/protobuf v1.3.2 // indirect 40 | github.com/golang/protobuf v1.5.4 // indirect 41 | github.com/google/btree v1.1.3 // indirect 42 | github.com/google/gnostic-models v0.6.8 // indirect 43 | github.com/google/gofuzz v1.2.0 // indirect 44 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 45 | github.com/google/uuid v1.6.0 // indirect 46 | github.com/josharian/intern v1.0.0 // indirect 47 | github.com/json-iterator/go v1.1.12 // indirect 48 | github.com/mailru/easyjson v0.7.7 // indirect 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 50 | github.com/modern-go/reflect2 v1.0.2 // indirect 51 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 52 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 53 | github.com/prometheus/client_golang v1.19.1 // indirect 54 | github.com/prometheus/client_model v0.6.1 // indirect 55 | github.com/prometheus/common v0.55.0 // indirect 56 | github.com/prometheus/procfs v0.15.1 // indirect 57 | github.com/x448/float16 v0.8.4 // indirect 58 | go.uber.org/multierr v1.11.0 // indirect 59 | golang.org/x/net v0.38.0 // indirect 60 | golang.org/x/oauth2 v0.27.0 // indirect 61 | golang.org/x/sync v0.12.0 // indirect 62 | golang.org/x/sys v0.31.0 // indirect 63 | golang.org/x/term v0.30.0 // indirect 64 | golang.org/x/text v0.23.0 // indirect 65 | golang.org/x/time v0.7.0 // indirect 66 | golang.org/x/tools v0.28.0 // indirect 67 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 68 | google.golang.org/protobuf v1.36.1 // indirect 69 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 70 | gopkg.in/inf.v0 v0.9.1 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/apiextensions-apiserver v0.32.1 // indirect 73 | k8s.io/klog/v2 v2.130.1 // indirect 74 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 75 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 76 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 77 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 78 | sigs.k8s.io/yaml v1.4.0 // indirect 79 | ) 80 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /internal/controllers/policy_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | policyinfo "github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1" 24 | 25 | "github.com/go-logr/logr" 26 | corev1 "k8s.io/api/core/v1" 27 | networking "k8s.io/api/networking/v1" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/controller" 31 | "sigs.k8s.io/controller-runtime/pkg/event" 32 | "sigs.k8s.io/controller-runtime/pkg/healthz" 33 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 34 | "sigs.k8s.io/controller-runtime/pkg/source" 35 | 36 | "github.com/aws/amazon-network-policy-controller-k8s/internal/eventhandlers" 37 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/config" 38 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 39 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/policyendpoints" 40 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/resolvers" 41 | ) 42 | 43 | const ( 44 | controllerName = "policy" 45 | policyFinalizerName = "networking.k8s.aws/resources" 46 | ) 47 | 48 | func NewPolicyReconciler(k8sClient client.Client, policyEndpointsManager policyendpoints.PolicyEndpointsManager, 49 | controllerConfig config.ControllerConfig, finalizerManager k8s.FinalizerManager, logger logr.Logger) *policyReconciler { 50 | policyTracker := resolvers.NewPolicyTracker(logger.WithName("policy-tracker")) 51 | policyResolver := resolvers.NewPolicyReferenceResolver(k8sClient, policyTracker, logger.WithName("policy-resolver")) 52 | return &policyReconciler{ 53 | k8sClient: k8sClient, 54 | policyResolver: policyResolver, 55 | policyTracker: policyTracker, 56 | policyEndpointsManager: policyEndpointsManager, 57 | podUpdateBatchPeriodDuration: controllerConfig.PodUpdateBatchPeriodDuration, 58 | finalizerManager: finalizerManager, 59 | maxConcurrentReconciles: controllerConfig.MaxConcurrentReconciles, 60 | logger: logger, 61 | } 62 | } 63 | 64 | var _ reconcile.Reconciler = (*policyReconciler)(nil) 65 | 66 | type policyReconciler struct { 67 | k8sClient client.Client 68 | policyResolver resolvers.PolicyReferenceResolver 69 | policyTracker resolvers.PolicyTracker 70 | policyEndpointsManager policyendpoints.PolicyEndpointsManager 71 | podUpdateBatchPeriodDuration time.Duration 72 | finalizerManager k8s.FinalizerManager 73 | 74 | maxConcurrentReconciles int 75 | logger logr.Logger 76 | } 77 | 78 | //+kubebuilder:rbac:groups=networking.k8s.aws,resources=policyendpoints,verbs=get;list;watch;create;update;patch;delete 79 | //+kubebuilder:rbac:groups=networking.k8s.aws,resources=policyendpoints/status,verbs=get;update;patch 80 | //+kubebuilder:rbac:groups=networking.k8s.aws,resources=policyendpoints/finalizers,verbs=update 81 | //+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch 82 | //+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch 83 | //+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch 84 | //+kubebuilder:rbac:groups="networking.k8s.io",resources=networkpolicies,verbs=get;list;watch;update;patch 85 | 86 | func (r *policyReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 87 | r.logger.Info("Got reconcile request", "resource", request) 88 | return ctrl.Result{}, r.reconcile(ctx, request) 89 | } 90 | 91 | func (r *policyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { 92 | if err := r.setupIndexes(ctx, mgr.GetFieldIndexer()); err != nil { 93 | return err 94 | } 95 | policyEventChan := make(chan event.GenericEvent) 96 | policyEventHandler := eventhandlers.NewEnqueueRequestForPolicyEvent(r.policyTracker, r.podUpdateBatchPeriodDuration, 97 | r.logger.WithName("eventHandler").WithName("policy")) 98 | podEventHandler := eventhandlers.NewEnqueueRequestForPodEvent(policyEventChan, r.k8sClient, r.policyResolver, 99 | r.logger.WithName("eventHandler").WithName("pod")) 100 | nsEventHandler := eventhandlers.NewEnqueueRequestForNamespaceEvent(policyEventChan, r.k8sClient, r.policyResolver, 101 | r.logger.WithName("eventHandler").WithName("namespace")) 102 | svcEventHandler := eventhandlers.NewEnqueueRequestForServiceEvent(policyEventChan, r.k8sClient, r.policyResolver, 103 | r.logger.WithName("eventHandler").WithName("service")) 104 | 105 | if err := mgr.AddHealthzCheck("policy-controller", healthz.Ping); err != nil { 106 | r.logger.Error(err, "Failed to setup the policy controller healthz check") 107 | return err 108 | } 109 | 110 | return ctrl.NewControllerManagedBy(mgr). 111 | Named(controllerName). 112 | Watches(&networking.NetworkPolicy{}, policyEventHandler). 113 | Watches(&corev1.Pod{}, podEventHandler). 114 | Watches(&corev1.Namespace{}, nsEventHandler). 115 | Watches(&corev1.Service{}, svcEventHandler). 116 | WatchesRawSource(source.Channel(policyEventChan, policyEventHandler)). 117 | WithOptions(controller.Options{ 118 | MaxConcurrentReconciles: r.maxConcurrentReconciles, 119 | }).Complete(r) 120 | } 121 | 122 | func (r *policyReconciler) reconcile(ctx context.Context, request reconcile.Request) error { 123 | policy := &networking.NetworkPolicy{} 124 | if err := r.k8sClient.Get(ctx, request.NamespacedName, policy); err != nil { 125 | r.logger.Info("Unable to get policy", "resource", policy, "err", err) 126 | return client.IgnoreNotFound(err) 127 | } 128 | if !policy.DeletionTimestamp.IsZero() { 129 | return r.cleanupPolicy(ctx, policy) 130 | } 131 | return r.reconcilePolicy(ctx, policy) 132 | } 133 | 134 | func (r *policyReconciler) reconcilePolicy(ctx context.Context, policy *networking.NetworkPolicy) error { 135 | if err := r.finalizerManager.AddFinalizers(ctx, policy, policyFinalizerName); err != nil { 136 | return err 137 | } 138 | return r.policyEndpointsManager.Reconcile(ctx, policy) 139 | } 140 | 141 | func (r *policyReconciler) cleanupPolicy(ctx context.Context, policy *networking.NetworkPolicy) error { 142 | if k8s.HasFinalizer(policy, policyFinalizerName) { 143 | r.policyTracker.RemovePolicy(policy) 144 | if err := r.policyEndpointsManager.Cleanup(ctx, policy); err != nil { 145 | return err 146 | } 147 | if err := r.finalizerManager.RemoveFinalizers(ctx, policy, policyFinalizerName); err != nil { 148 | return err 149 | } 150 | } 151 | return nil 152 | } 153 | 154 | func (r *policyReconciler) setupIndexes(ctx context.Context, fieldIndexer client.FieldIndexer) error { 155 | if err := fieldIndexer.IndexField(ctx, &policyinfo.PolicyEndpoint{}, policyendpoints.IndexKeyPolicyReferenceName, 156 | policyendpoints.IndexFunctionPolicyReferenceName); err != nil { 157 | return err 158 | } 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /internal/controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | 26 | "k8s.io/client-go/kubernetes/scheme" 27 | "k8s.io/client-go/rest" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | policyinfo "github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1" 34 | //+kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecs(t, "Controller Suite") 48 | } 49 | 50 | var _ = BeforeSuite(func() { 51 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 52 | 53 | By("bootstrapping test environment") 54 | testEnv = &envtest.Environment{ 55 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 56 | ErrorIfCRDPathMissing: true, 57 | } 58 | 59 | var err error 60 | // cfg is defined in this file globally. 61 | cfg, err = testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = policyinfo.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | //+kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).NotTo(HaveOccurred()) 72 | Expect(k8sClient).NotTo(BeNil()) 73 | 74 | }) 75 | 76 | var _ = AfterSuite(func() { 77 | By("tearing down the test environment") 78 | err := testEnv.Stop() 79 | Expect(err).NotTo(HaveOccurred()) 80 | }) 81 | -------------------------------------------------------------------------------- /internal/eventhandlers/namespace.go: -------------------------------------------------------------------------------- 1 | package eventhandlers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 7 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/resolvers" 8 | 9 | "github.com/go-logr/logr" 10 | corev1 "k8s.io/api/core/v1" 11 | "k8s.io/apimachinery/pkg/api/equality" 12 | "k8s.io/client-go/util/workqueue" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | "sigs.k8s.io/controller-runtime/pkg/event" 15 | "sigs.k8s.io/controller-runtime/pkg/handler" 16 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 17 | ) 18 | 19 | // NewEnqueueRequestForNamespaceEvent construct enqueueRequestsForNamespaceEvent 20 | func NewEnqueueRequestForNamespaceEvent(policyEventChan chan<- event.GenericEvent, k8sClient client.Client, 21 | policyResolver resolvers.PolicyReferenceResolver, logger logr.Logger) handler.EventHandler { 22 | return &enqueueRequestForNamespaceEvent{ 23 | k8sClient: k8sClient, 24 | policyEventChan: policyEventChan, 25 | policyResolver: policyResolver, 26 | logger: logger, 27 | } 28 | } 29 | 30 | var _ handler.EventHandler = (*enqueueRequestForNamespaceEvent)(nil) 31 | 32 | type enqueueRequestForNamespaceEvent struct { 33 | k8sClient client.Client 34 | policyEventChan chan<- event.GenericEvent 35 | logger logr.Logger 36 | policyResolver resolvers.PolicyReferenceResolver 37 | } 38 | 39 | func (h *enqueueRequestForNamespaceEvent) Create(ctx context.Context, event event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 40 | ns := event.Object.(*corev1.Namespace) 41 | h.logger.V(1).Info("Handling create event", "namespace", k8s.NamespacedName(ns)) 42 | h.enqueueReferredPolicies(ctx, q, ns, nil) 43 | } 44 | 45 | func (h *enqueueRequestForNamespaceEvent) Update(ctx context.Context, event event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 46 | nsNew := event.ObjectNew.(*corev1.Namespace) 47 | nsOld := event.ObjectOld.(*corev1.Namespace) 48 | 49 | h.logger.V(1).Info("Handling update event", "namespace", k8s.NamespacedName(nsNew)) 50 | if equality.Semantic.DeepEqual(nsOld.Labels, nsNew.Labels) && 51 | equality.Semantic.DeepEqual(nsOld.DeletionTimestamp.IsZero(), nsNew.DeletionTimestamp.IsZero()) { 52 | return 53 | } 54 | h.enqueueReferredPolicies(ctx, q, nsNew, nsOld) 55 | } 56 | 57 | func (h *enqueueRequestForNamespaceEvent) Delete(ctx context.Context, event event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 58 | ns := event.Object.(*corev1.Namespace) 59 | h.logger.V(1).Info("Handling delete event", "namespace", k8s.NamespacedName(ns)) 60 | h.enqueueReferredPolicies(ctx, q, ns, nil) 61 | } 62 | 63 | func (h *enqueueRequestForNamespaceEvent) Generic(_ context.Context, _ event.GenericEvent, _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { 64 | return 65 | } 66 | 67 | func (h *enqueueRequestForNamespaceEvent) enqueueReferredPolicies(ctx context.Context, _ workqueue.TypedRateLimitingInterface[reconcile.Request], ns, nsOld *corev1.Namespace) { 68 | referredPolicies, err := h.policyResolver.GetReferredPoliciesForNamespace(ctx, ns, nsOld) 69 | if err != nil { 70 | h.logger.Error(err, "Unable to get referred policies", "namespace", k8s.NamespacedName(ns)) 71 | return 72 | } 73 | for i := range referredPolicies { 74 | policy := &referredPolicies[i] 75 | h.logger.V(1).Info("Enqueue from namespace reference", "policy", k8s.NamespacedName(policy), "namespace", k8s.NamespacedName(ns)) 76 | h.policyEventChan <- event.GenericEvent{ 77 | Object: policy, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/eventhandlers/pod.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package eventhandlers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 23 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/resolvers" 24 | "github.com/go-logr/logr" 25 | corev1 "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/api/equality" 27 | "k8s.io/client-go/util/workqueue" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/event" 30 | "sigs.k8s.io/controller-runtime/pkg/handler" 31 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 32 | ) 33 | 34 | // NewEnqueueRequestForPodEvent constructs new enqueueRequestsForPodEvent 35 | func NewEnqueueRequestForPodEvent(policyEventChan chan<- event.GenericEvent, k8sClient client.Client, 36 | policyResolver resolvers.PolicyReferenceResolver, logger logr.Logger) handler.EventHandler { 37 | return &enqueueRequestForPodEvent{ 38 | k8sClient: k8sClient, 39 | policyResolver: policyResolver, 40 | policyEventChan: policyEventChan, 41 | logger: logger, 42 | } 43 | } 44 | 45 | var _ handler.EventHandler = (*enqueueRequestForPodEvent)(nil) 46 | 47 | type enqueueRequestForPodEvent struct { 48 | k8sClient client.Client 49 | policyResolver resolvers.PolicyReferenceResolver 50 | policyEventChan chan<- event.GenericEvent 51 | logger logr.Logger 52 | } 53 | 54 | func (h *enqueueRequestForPodEvent) Create(ctx context.Context, event event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 55 | podNew := event.Object.(*corev1.Pod) 56 | h.logger.V(1).Info("Handling pod create event", "pod", k8s.NamespacedName(podNew)) 57 | h.enqueueReferredPolicies(ctx, q, podNew, nil) 58 | } 59 | 60 | func (h *enqueueRequestForPodEvent) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 61 | podOld := e.ObjectOld.(*corev1.Pod) 62 | podNew := e.ObjectNew.(*corev1.Pod) 63 | 64 | h.logger.V(1).Info("Handling pod update event", "pod", k8s.NamespacedName(podNew)) 65 | if equality.Semantic.DeepEqual(podOld.Annotations, podNew.Annotations) && 66 | equality.Semantic.DeepEqual(podOld.Labels, podNew.Labels) && 67 | equality.Semantic.DeepEqual(podOld.DeletionTimestamp.IsZero(), podNew.DeletionTimestamp.IsZero()) && 68 | equality.Semantic.DeepEqual(podOld.Status.PodIP, podNew.Status.PodIP) { 69 | return 70 | } 71 | h.enqueueReferredPolicies(ctx, q, podNew, podOld) 72 | } 73 | 74 | func (h *enqueueRequestForPodEvent) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 75 | pod := e.Object.(*corev1.Pod) 76 | h.logger.V(1).Info("Handling delete event", "pod", k8s.NamespacedName(pod)) 77 | h.enqueueReferredPolicies(ctx, q, pod, nil) 78 | } 79 | 80 | func (h *enqueueRequestForPodEvent) Generic(_ context.Context, _ event.GenericEvent, _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { 81 | return 82 | } 83 | 84 | func (h *enqueueRequestForPodEvent) enqueueReferredPolicies(ctx context.Context, _ workqueue.TypedRateLimitingInterface[reconcile.Request], pod *corev1.Pod, podOld *corev1.Pod) { 85 | if len(k8s.GetPodIP(pod)) == 0 { 86 | h.logger.V(1).Info("Pod does not have an IP yet", "pod", k8s.NamespacedName(pod)) 87 | return 88 | } 89 | referredPolicies, err := h.policyResolver.GetReferredPoliciesForPod(ctx, pod, podOld) 90 | if err != nil { 91 | h.logger.Error(err, "Unable to get referred policies", "pod", k8s.NamespacedName(pod)) 92 | return 93 | } 94 | for i := range referredPolicies { 95 | policy := &referredPolicies[i] 96 | h.logger.V(1).Info("Enqueue from pod reference", "policy", k8s.NamespacedName(policy), "pod", k8s.NamespacedName(pod)) 97 | h.policyEventChan <- event.GenericEvent{ 98 | Object: policy, 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /internal/eventhandlers/policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package eventhandlers 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/resolvers" 24 | 25 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 26 | "github.com/go-logr/logr" 27 | networking "k8s.io/api/networking/v1" 28 | "k8s.io/apimachinery/pkg/api/equality" 29 | "k8s.io/apimachinery/pkg/types" 30 | "k8s.io/client-go/util/workqueue" 31 | "sigs.k8s.io/controller-runtime/pkg/event" 32 | "sigs.k8s.io/controller-runtime/pkg/handler" 33 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 34 | ) 35 | 36 | // NewEnqueueRequestForPolicyEvent constructs new enqueueRequestsForPolicyEvent 37 | func NewEnqueueRequestForPolicyEvent(policyTracker resolvers.PolicyTracker, podUpdateBatchPeriodDuration time.Duration, 38 | logger logr.Logger) handler.EventHandler { 39 | return &enqueueRequestForPolicyEvent{ 40 | policyTracker: policyTracker, 41 | podUpdateBatchPeriodDuration: podUpdateBatchPeriodDuration, 42 | logger: logger, 43 | } 44 | } 45 | 46 | var _ handler.EventHandler = (*enqueueRequestForPolicyEvent)(nil) 47 | 48 | type enqueueRequestForPolicyEvent struct { 49 | policyTracker resolvers.PolicyTracker 50 | podUpdateBatchPeriodDuration time.Duration 51 | logger logr.Logger 52 | } 53 | 54 | func (h *enqueueRequestForPolicyEvent) Create(_ context.Context, e event.CreateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { 55 | policy := e.Object.(*networking.NetworkPolicy) 56 | h.logger.V(1).Info("Handling create event", "policy", k8s.NamespacedName(policy)) 57 | h.enqueuePolicy(queue, policy, 0) 58 | } 59 | 60 | func (h *enqueueRequestForPolicyEvent) Update(_ context.Context, e event.UpdateEvent, queue workqueue.TypedRateLimitingInterface[reconcile.Request]) { 61 | oldPolicy := e.ObjectOld.(*networking.NetworkPolicy) 62 | newPolicy := e.ObjectNew.(*networking.NetworkPolicy) 63 | 64 | h.logger.V(1).Info("Handling update event", "policy", k8s.NamespacedName(newPolicy)) 65 | if !equality.Semantic.DeepEqual(newPolicy.ResourceVersion, oldPolicy.ResourceVersion) && equality.Semantic.DeepEqual(oldPolicy.Spec, newPolicy.Spec) && 66 | equality.Semantic.DeepEqual(oldPolicy.DeletionTimestamp.IsZero(), newPolicy.DeletionTimestamp.IsZero()) { 67 | return 68 | } 69 | h.enqueuePolicy(queue, newPolicy, 0) 70 | } 71 | 72 | func (h *enqueueRequestForPolicyEvent) Delete(_ context.Context, e event.DeleteEvent, _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { 73 | policy := e.Object.(*networking.NetworkPolicy) 74 | h.logger.V(1).Info("Handling delete event", "policy", k8s.NamespacedName(policy)) 75 | h.policyTracker.RemovePolicy(policy) 76 | } 77 | 78 | func (h *enqueueRequestForPolicyEvent) Generic(_ context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 79 | policy := e.Object.(*networking.NetworkPolicy) 80 | h.logger.V(1).Info("Handling generic event", "policy", k8s.NamespacedName(policy)) 81 | h.enqueuePolicy(q, policy, h.podUpdateBatchPeriodDuration) 82 | } 83 | 84 | func (h *enqueueRequestForPolicyEvent) enqueuePolicy(queue workqueue.TypedRateLimitingInterface[reconcile.Request], policy *networking.NetworkPolicy, addAfter time.Duration) { 85 | h.policyTracker.UpdatePolicy(policy) 86 | queue.AddAfter(reconcile.Request{ 87 | NamespacedName: types.NamespacedName{ 88 | Namespace: policy.Namespace, 89 | Name: policy.Name, 90 | }, 91 | }, addAfter) 92 | } 93 | -------------------------------------------------------------------------------- /internal/eventhandlers/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package eventhandlers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 23 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/resolvers" 24 | "github.com/go-logr/logr" 25 | corev1 "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/api/equality" 27 | "k8s.io/client-go/util/workqueue" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/event" 30 | "sigs.k8s.io/controller-runtime/pkg/handler" 31 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 32 | ) 33 | 34 | // NewEnqueueRequestForServiceEvent constructs a new enqueueRequestForServiceEvent 35 | func NewEnqueueRequestForServiceEvent(policyEventChan chan<- event.GenericEvent, k8sClient client.Client, 36 | policyResolver resolvers.PolicyReferenceResolver, logger logr.Logger) handler.EventHandler { 37 | return &enqueueRequestForServiceEvent{ 38 | k8sClient: k8sClient, 39 | policyEventChan: policyEventChan, 40 | policyResolver: policyResolver, 41 | logger: logger, 42 | } 43 | } 44 | 45 | var _ handler.EventHandler = (*enqueueRequestForServiceEvent)(nil) 46 | 47 | type enqueueRequestForServiceEvent struct { 48 | k8sClient client.Client 49 | policyEventChan chan<- event.GenericEvent 50 | policyResolver resolvers.PolicyReferenceResolver 51 | logger logr.Logger 52 | } 53 | 54 | func (h *enqueueRequestForServiceEvent) Create(ctx context.Context, createEvent event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 55 | serviceNew := createEvent.Object.(*corev1.Service) 56 | h.logger.V(1).Info("handling service create event", "service", k8s.NamespacedName(serviceNew)) 57 | h.enqueueReferredPolicies(ctx, q, serviceNew, nil) 58 | } 59 | 60 | func (h *enqueueRequestForServiceEvent) Update(ctx context.Context, updateEvent event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 61 | serviceOld := updateEvent.ObjectOld.(*corev1.Service) 62 | serviceNew := updateEvent.ObjectNew.(*corev1.Service) 63 | 64 | h.logger.V(1).Info("handling service update event", "service", k8s.NamespacedName(serviceNew)) 65 | if equality.Semantic.DeepEqual(serviceOld.Spec, serviceNew.Spec) && 66 | equality.Semantic.DeepEqual(serviceOld.DeletionTimestamp.IsZero(), serviceNew.DeletionTimestamp.IsZero()) { 67 | return 68 | } 69 | h.enqueueReferredPolicies(ctx, q, serviceNew, serviceOld) 70 | } 71 | 72 | func (h *enqueueRequestForServiceEvent) Delete(ctx context.Context, deleteEvent event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { 73 | serviceNew := deleteEvent.Object.(*corev1.Service) 74 | h.logger.V(1).Info("handling service delete event", "service", k8s.NamespacedName(serviceNew)) 75 | h.enqueueReferredPolicies(ctx, q, serviceNew, nil) 76 | } 77 | 78 | func (h *enqueueRequestForServiceEvent) Generic(_ context.Context, _ event.GenericEvent, _ workqueue.TypedRateLimitingInterface[reconcile.Request]) { 79 | return 80 | } 81 | 82 | func (h *enqueueRequestForServiceEvent) enqueueReferredPolicies(ctx context.Context, _ workqueue.TypedRateLimitingInterface[reconcile.Request], svc *corev1.Service, svcOld *corev1.Service) { 83 | referredPolicies, err := h.policyResolver.GetReferredPoliciesForService(ctx, svc, svcOld) 84 | if err != nil { 85 | h.logger.Error(err, "Unable to get referred policies", "service", k8s.NamespacedName(svc)) 86 | } 87 | for i := range referredPolicies { 88 | policy := &referredPolicies[i] 89 | h.logger.V(1).Info("Enqueue policies from service reference", "policy", k8s.NamespacedName(policy), "svc", k8s.NamespacedName(svc)) 90 | h.policyEventChan <- event.GenericEvent{ 91 | Object: policy, 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mocks/controller-runtime/client/client_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client) 3 | 4 | // Package mock_client is a generated GoMock package. 5 | package mock_client 6 | 7 | import ( 8 | context "context" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | meta "k8s.io/apimachinery/pkg/api/meta" 13 | runtime "k8s.io/apimachinery/pkg/runtime" 14 | schema "k8s.io/apimachinery/pkg/runtime/schema" 15 | types "k8s.io/apimachinery/pkg/types" 16 | client "sigs.k8s.io/controller-runtime/pkg/client" 17 | ) 18 | 19 | // MockClient is a mock of Client interface. 20 | type MockClient struct { 21 | ctrl *gomock.Controller 22 | recorder *MockClientMockRecorder 23 | } 24 | 25 | // MockClientMockRecorder is the mock recorder for MockClient. 26 | type MockClientMockRecorder struct { 27 | mock *MockClient 28 | } 29 | 30 | // NewMockClient creates a new mock instance. 31 | func NewMockClient(ctrl *gomock.Controller) *MockClient { 32 | mock := &MockClient{ctrl: ctrl} 33 | mock.recorder = &MockClientMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockClient) EXPECT() *MockClientMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // Create mocks base method. 43 | func (m *MockClient) Create(arg0 context.Context, arg1 client.Object, arg2 ...client.CreateOption) error { 44 | m.ctrl.T.Helper() 45 | varargs := []interface{}{arg0, arg1} 46 | for _, a := range arg2 { 47 | varargs = append(varargs, a) 48 | } 49 | ret := m.ctrl.Call(m, "Create", varargs...) 50 | ret0, _ := ret[0].(error) 51 | return ret0 52 | } 53 | 54 | // Create indicates an expected call of Create. 55 | func (mr *MockClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 56 | mr.mock.ctrl.T.Helper() 57 | varargs := append([]interface{}{arg0, arg1}, arg2...) 58 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) 59 | } 60 | 61 | // Delete mocks base method. 62 | func (m *MockClient) Delete(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteOption) error { 63 | m.ctrl.T.Helper() 64 | varargs := []interface{}{arg0, arg1} 65 | for _, a := range arg2 { 66 | varargs = append(varargs, a) 67 | } 68 | ret := m.ctrl.Call(m, "Delete", varargs...) 69 | ret0, _ := ret[0].(error) 70 | return ret0 71 | } 72 | 73 | // Delete indicates an expected call of Delete. 74 | func (mr *MockClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 75 | mr.mock.ctrl.T.Helper() 76 | varargs := append([]interface{}{arg0, arg1}, arg2...) 77 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) 78 | } 79 | 80 | // DeleteAllOf mocks base method. 81 | func (m *MockClient) DeleteAllOf(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteAllOfOption) error { 82 | m.ctrl.T.Helper() 83 | varargs := []interface{}{arg0, arg1} 84 | for _, a := range arg2 { 85 | varargs = append(varargs, a) 86 | } 87 | ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) 88 | ret0, _ := ret[0].(error) 89 | return ret0 90 | } 91 | 92 | // DeleteAllOf indicates an expected call of DeleteAllOf. 93 | func (mr *MockClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 94 | mr.mock.ctrl.T.Helper() 95 | varargs := append([]interface{}{arg0, arg1}, arg2...) 96 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) 97 | } 98 | 99 | // Get mocks base method. 100 | func (m *MockClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 client.Object, arg3 ...client.GetOption) error { 101 | m.ctrl.T.Helper() 102 | varargs := []interface{}{arg0, arg1, arg2} 103 | for _, a := range arg3 { 104 | varargs = append(varargs, a) 105 | } 106 | ret := m.ctrl.Call(m, "Get", varargs...) 107 | ret0, _ := ret[0].(error) 108 | return ret0 109 | } 110 | 111 | // Get indicates an expected call of Get. 112 | func (mr *MockClientMockRecorder) Get(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { 113 | mr.mock.ctrl.T.Helper() 114 | varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) 115 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) 116 | } 117 | 118 | // GroupVersionKindFor mocks base method. 119 | func (m *MockClient) GroupVersionKindFor(arg0 runtime.Object) (schema.GroupVersionKind, error) { 120 | m.ctrl.T.Helper() 121 | ret := m.ctrl.Call(m, "GroupVersionKindFor", arg0) 122 | ret0, _ := ret[0].(schema.GroupVersionKind) 123 | ret1, _ := ret[1].(error) 124 | return ret0, ret1 125 | } 126 | 127 | // GroupVersionKindFor indicates an expected call of GroupVersionKindFor. 128 | func (mr *MockClientMockRecorder) GroupVersionKindFor(arg0 interface{}) *gomock.Call { 129 | mr.mock.ctrl.T.Helper() 130 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), arg0) 131 | } 132 | 133 | // IsObjectNamespaced mocks base method. 134 | func (m *MockClient) IsObjectNamespaced(arg0 runtime.Object) (bool, error) { 135 | m.ctrl.T.Helper() 136 | ret := m.ctrl.Call(m, "IsObjectNamespaced", arg0) 137 | ret0, _ := ret[0].(bool) 138 | ret1, _ := ret[1].(error) 139 | return ret0, ret1 140 | } 141 | 142 | // IsObjectNamespaced indicates an expected call of IsObjectNamespaced. 143 | func (mr *MockClientMockRecorder) IsObjectNamespaced(arg0 interface{}) *gomock.Call { 144 | mr.mock.ctrl.T.Helper() 145 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), arg0) 146 | } 147 | 148 | // List mocks base method. 149 | func (m *MockClient) List(arg0 context.Context, arg1 client.ObjectList, arg2 ...client.ListOption) error { 150 | m.ctrl.T.Helper() 151 | varargs := []interface{}{arg0, arg1} 152 | for _, a := range arg2 { 153 | varargs = append(varargs, a) 154 | } 155 | ret := m.ctrl.Call(m, "List", varargs...) 156 | ret0, _ := ret[0].(error) 157 | return ret0 158 | } 159 | 160 | // List indicates an expected call of List. 161 | func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 162 | mr.mock.ctrl.T.Helper() 163 | varargs := append([]interface{}{arg0, arg1}, arg2...) 164 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) 165 | } 166 | 167 | // Patch mocks base method. 168 | func (m *MockClient) Patch(arg0 context.Context, arg1 client.Object, arg2 client.Patch, arg3 ...client.PatchOption) error { 169 | m.ctrl.T.Helper() 170 | varargs := []interface{}{arg0, arg1, arg2} 171 | for _, a := range arg3 { 172 | varargs = append(varargs, a) 173 | } 174 | ret := m.ctrl.Call(m, "Patch", varargs...) 175 | ret0, _ := ret[0].(error) 176 | return ret0 177 | } 178 | 179 | // Patch indicates an expected call of Patch. 180 | func (mr *MockClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { 181 | mr.mock.ctrl.T.Helper() 182 | varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) 183 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) 184 | } 185 | 186 | // RESTMapper mocks base method. 187 | func (m *MockClient) RESTMapper() meta.RESTMapper { 188 | m.ctrl.T.Helper() 189 | ret := m.ctrl.Call(m, "RESTMapper") 190 | ret0, _ := ret[0].(meta.RESTMapper) 191 | return ret0 192 | } 193 | 194 | // RESTMapper indicates an expected call of RESTMapper. 195 | func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { 196 | mr.mock.ctrl.T.Helper() 197 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) 198 | } 199 | 200 | // Scheme mocks base method. 201 | func (m *MockClient) Scheme() *runtime.Scheme { 202 | m.ctrl.T.Helper() 203 | ret := m.ctrl.Call(m, "Scheme") 204 | ret0, _ := ret[0].(*runtime.Scheme) 205 | return ret0 206 | } 207 | 208 | // Scheme indicates an expected call of Scheme. 209 | func (mr *MockClientMockRecorder) Scheme() *gomock.Call { 210 | mr.mock.ctrl.T.Helper() 211 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) 212 | } 213 | 214 | // Status mocks base method. 215 | func (m *MockClient) Status() client.SubResourceWriter { 216 | m.ctrl.T.Helper() 217 | ret := m.ctrl.Call(m, "Status") 218 | ret0, _ := ret[0].(client.SubResourceWriter) 219 | return ret0 220 | } 221 | 222 | // Status indicates an expected call of Status. 223 | func (mr *MockClientMockRecorder) Status() *gomock.Call { 224 | mr.mock.ctrl.T.Helper() 225 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) 226 | } 227 | 228 | // SubResource mocks base method. 229 | func (m *MockClient) SubResource(arg0 string) client.SubResourceClient { 230 | m.ctrl.T.Helper() 231 | ret := m.ctrl.Call(m, "SubResource", arg0) 232 | ret0, _ := ret[0].(client.SubResourceClient) 233 | return ret0 234 | } 235 | 236 | // SubResource indicates an expected call of SubResource. 237 | func (mr *MockClientMockRecorder) SubResource(arg0 interface{}) *gomock.Call { 238 | mr.mock.ctrl.T.Helper() 239 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), arg0) 240 | } 241 | 242 | // Update mocks base method. 243 | func (m *MockClient) Update(arg0 context.Context, arg1 client.Object, arg2 ...client.UpdateOption) error { 244 | m.ctrl.T.Helper() 245 | varargs := []interface{}{arg0, arg1} 246 | for _, a := range arg2 { 247 | varargs = append(varargs, a) 248 | } 249 | ret := m.ctrl.Call(m, "Update", varargs...) 250 | ret0, _ := ret[0].(error) 251 | return ret0 252 | } 253 | 254 | // Update indicates an expected call of Update. 255 | func (mr *MockClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { 256 | mr.mock.ctrl.T.Helper() 257 | varargs := append([]interface{}{arg0, arg1}, arg2...) 258 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) 259 | } 260 | -------------------------------------------------------------------------------- /pkg/config/configmap_manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 8 | "github.com/go-logr/logr" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/fields" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/types" 14 | "k8s.io/apimachinery/pkg/watch" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/tools/cache" 17 | ) 18 | 19 | // +kubebuilder:rbac:groups="",resources=configmaps,namespace="system",resourceNames=amazon-vpc-cni,verbs=get;list;watch 20 | 21 | type ConfigmapManager interface { 22 | MonitorConfigMap(ctx context.Context) error 23 | IsControllerEnabled() bool 24 | } 25 | 26 | var _ ConfigmapManager = (*defaultConfigmapManager)(nil) 27 | 28 | type defaultConfigmapManager struct { 29 | initialState bool 30 | resourceRef types.NamespacedName 31 | store cache.Store 32 | rt *cache.Reflector 33 | clientSet *kubernetes.Clientset 34 | logger logr.Logger 35 | cancelFn context.CancelFunc 36 | monitorStopChan chan struct{} 37 | storeNotifyChan chan struct{} 38 | configMapCheckFunction func(*corev1.ConfigMap) bool 39 | } 40 | 41 | func NewConfigmapManager(resourceRef types.NamespacedName, clientSet *kubernetes.Clientset, 42 | cancelFn context.CancelFunc, configmapCheckFunction func(configMap *corev1.ConfigMap) bool, logger logr.Logger) *defaultConfigmapManager { 43 | storeNotifyChan := make(chan struct{}) 44 | cmStore := k8s.NewConfigMapStore(storeNotifyChan) 45 | return &defaultConfigmapManager{ 46 | clientSet: clientSet, 47 | resourceRef: resourceRef, 48 | store: cmStore, 49 | logger: logger, 50 | cancelFn: cancelFn, 51 | monitorStopChan: make(chan struct{}), 52 | storeNotifyChan: storeNotifyChan, 53 | configMapCheckFunction: configmapCheckFunction, 54 | } 55 | } 56 | 57 | // IsControllerEnabled returns the initial state of the policy controller. 58 | func (m *defaultConfigmapManager) IsControllerEnabled() bool { 59 | m.logger.V(1).Info("IsControllerEnabled() returning", "value", m.initialState) 60 | return m.initialState 61 | } 62 | 63 | // MonitorConfigMap starts cache reflector and watches for configmap updates. 64 | func (m *defaultConfigmapManager) MonitorConfigMap(ctx context.Context) error { 65 | fieldSelector := fields.Set{"metadata.name": m.resourceRef.Name}.AsSelector().String() 66 | listFunc := func(options metav1.ListOptions) (runtime.Object, error) { 67 | options.FieldSelector = fieldSelector 68 | return m.clientSet.CoreV1().ConfigMaps(m.resourceRef.Namespace).List(ctx, options) 69 | } 70 | watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { 71 | options.FieldSelector = fieldSelector 72 | return m.clientSet.CoreV1().ConfigMaps(m.resourceRef.Namespace).Watch(ctx, options) 73 | } 74 | m.rt = cache.NewReflector(&cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}, 75 | &corev1.ConfigMap{}, 76 | m.store, 77 | 0, 78 | ) 79 | go m.rt.Run(m.monitorStopChan) 80 | go m.listenForConfigMapUpdates() 81 | 82 | if _, err := m.setInitialControllerState(); err != nil { 83 | m.logger.Info("Failed to set initial state", "err", err) 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | // listen for the messages in the storeNotifyChan in a loop and update the state of the policy controller accordingly. 90 | func (m *defaultConfigmapManager) listenForConfigMapUpdates() { 91 | defer func() { 92 | m.logger.Info("Controller detected changes to the configmap, cancelling manager context") 93 | close(m.monitorStopChan) 94 | m.cancelFn() 95 | }() 96 | 97 | for { 98 | select { 99 | case <-m.storeNotifyChan: 100 | enabled, err := m.getCurrentEnabledConfig() 101 | if err != nil { 102 | m.logger.Error(err, "Failed to get controller state from configmap") 103 | return 104 | } 105 | m.logger.V(1).Info("Received configmap notification", "initial", m.initialState, 106 | "new", enabled) 107 | if m.initialState != enabled { 108 | m.logger.Info("Controller state changed", "initial", m.initialState, 109 | "new", enabled) 110 | return 111 | } 112 | } 113 | } 114 | } 115 | 116 | // getCurrentEnabledConfig gets the current state of the policy controller from the configmap 117 | func (m *defaultConfigmapManager) getCurrentEnabledConfig() (bool, error) { 118 | cm, exists, err := m.store.GetByKey(m.resourceRef.String()) 119 | if err != nil { 120 | return false, err 121 | } 122 | if !exists { 123 | return false, nil 124 | } 125 | return m.configMapCheckFunction(cm.(*corev1.ConfigMap)), nil 126 | } 127 | 128 | // setInitialControllerState sets the initial state of the policy controller based on the configmap 129 | func (m *defaultConfigmapManager) setInitialControllerState() (retVal bool, err error) { 130 | defer func() { 131 | m.logger.V(1).Info("setInitialControllerState", "retVal", retVal, "err", err) 132 | m.initialState = retVal 133 | }() 134 | // Wait for cache sync 135 | if !cache.WaitForCacheSync(m.monitorStopChan, func() bool { 136 | return m.rt.LastSyncResourceVersion() != "" 137 | }) { 138 | return false, errors.New("failed to sync configmap cache") 139 | } 140 | return m.getCurrentEnabledConfig() 141 | } 142 | -------------------------------------------------------------------------------- /pkg/config/controller_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/spf13/pflag" 7 | ) 8 | 9 | const ( 10 | flagLogLevel = "log-level" 11 | flagMaxConcurrentReconciles = "max-concurrent-reconciles" 12 | flagEnableConfigMapCheck = "enable-configmap-check" 13 | flagEndpointChunkSize = "endpoint-chunk-size" 14 | flagEnableGoProfiling = "enable-goprofiling" 15 | defaultLogLevel = "info" 16 | defaultMaxConcurrentReconciles = 3 17 | defaultEndpointsChunkSize = 200 18 | defaultEnableConfigMapCheck = true 19 | flagPodUpdateBatchPeriodDuration = "pod-update-batch-period-duration" 20 | defaultBatchPeriodDuration = 1 * time.Second 21 | defaultEnableGoProfiling = false 22 | ) 23 | 24 | // ControllerConfig contains the controller configuration 25 | type ControllerConfig struct { 26 | // Log level for the controller logs 27 | LogLevel string 28 | // EndpointChunkSize specifies the number of endpoints to include in a single chunk 29 | EndpointChunkSize int 30 | // EnableConfigMapCheck enables checking the configmap for starting the NP controller 31 | EnableConfigMapCheck bool 32 | // MaxConcurrentReconciles specifies the max number of reconcile loops 33 | MaxConcurrentReconciles int 34 | // PodUpdateBatchPeriodDuration specifies the duration between batch updates of pods 35 | PodUpdateBatchPeriodDuration time.Duration 36 | // Configurations for the Controller Runtime 37 | RuntimeConfig RuntimeConfig 38 | // EnableGoProfiling enables the goprofiling for dev purpose 39 | EnableGoProfiling bool 40 | } 41 | 42 | func (cfg *ControllerConfig) BindFlags(fs *pflag.FlagSet) { 43 | fs.StringVar(&cfg.LogLevel, flagLogLevel, defaultLogLevel, 44 | "Set the controller log level - info, debug") 45 | fs.IntVar(&cfg.MaxConcurrentReconciles, flagMaxConcurrentReconciles, defaultMaxConcurrentReconciles, ""+ 46 | "Maximum number of concurrent reconcile loops") 47 | fs.IntVar(&cfg.EndpointChunkSize, flagEndpointChunkSize, defaultEndpointsChunkSize, ""+ 48 | "Number of endpoints to include in a single policy endpoints resource") 49 | fs.BoolVar(&cfg.EnableConfigMapCheck, flagEnableConfigMapCheck, defaultEnableConfigMapCheck, 50 | "Enable checking the configmap for starting the network policy controller") 51 | fs.DurationVar(&cfg.PodUpdateBatchPeriodDuration, flagPodUpdateBatchPeriodDuration, defaultBatchPeriodDuration, ""+ 52 | "Duration between batch updates of pods") 53 | fs.BoolVar(&cfg.EnableGoProfiling, flagEnableGoProfiling, defaultEnableGoProfiling, 54 | "Enable goprofiling for develop purpose") 55 | cfg.RuntimeConfig.BindFlags(fs) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/config/runtime_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1" 7 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 8 | networkingv1 "k8s.io/api/networking/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | 11 | corev1 "k8s.io/api/core/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/client-go/rest" 14 | "k8s.io/client-go/tools/clientcmd" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/cache" 17 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 18 | 19 | "github.com/spf13/pflag" 20 | ) 21 | 22 | const ( 23 | flagKubeconfig = "kubeconfig" 24 | flagMetricsBindAddr = "metrics-bind-addr" 25 | flagHealthProbeBindAddr = "health-probe-bind-addr" 26 | flagEnableLeaderElection = "enable-leader-election" 27 | flagLeaderElectionID = "leader-election-id" 28 | flagLeaderElectionNamespace = "leader-election-namespace" 29 | flagWatchNamespace = "watch-namespace" 30 | 31 | defaultKubeconfig = "" 32 | defaultLeaderElectionID = "amazon-network-policy-controller-k8s" 33 | defaultLeaderElectionNamespace = "" 34 | defaultWatchNamespace = corev1.NamespaceAll 35 | defaultMetricsAddr = ":8080" 36 | defaultHealthProbeBindAddress = ":8081" 37 | defaultQPS = 20 38 | defaultBurst = 100 39 | ) 40 | 41 | // RuntimeConfig stores the configuration for the controller-runtime 42 | type RuntimeConfig struct { 43 | APIServer string 44 | KubeConfig string 45 | MetricsBindAddress string 46 | HealthProbeBindAddress string 47 | EnableLeaderElection bool 48 | LeaderElectionID string 49 | LeaderElectionNamespace string 50 | WatchNamespace string 51 | SyncPeriod time.Duration 52 | } 53 | 54 | func (c *RuntimeConfig) BindFlags(fs *pflag.FlagSet) { 55 | fs.StringVar(&c.KubeConfig, flagKubeconfig, defaultKubeconfig, 56 | "Path to the kubeconfig file containing authorization and API server information.") 57 | fs.StringVar(&c.MetricsBindAddress, flagMetricsBindAddr, defaultMetricsAddr, 58 | "The address the metric endpoint binds to.") 59 | fs.StringVar(&c.HealthProbeBindAddress, flagHealthProbeBindAddr, defaultHealthProbeBindAddress, 60 | "The address the health probes binds to.") 61 | fs.BoolVar(&c.EnableLeaderElection, flagEnableLeaderElection, true, 62 | "Enable leader election for controller manager. "+ 63 | "Enabling this will ensure there is only one active controller manager.") 64 | fs.StringVar(&c.LeaderElectionID, flagLeaderElectionID, defaultLeaderElectionID, 65 | "Name of the leader election ID to use for this controller") 66 | fs.StringVar(&c.LeaderElectionNamespace, flagLeaderElectionNamespace, defaultLeaderElectionNamespace, 67 | "Name of the leader election ID to use for this controller") 68 | fs.StringVar(&c.WatchNamespace, flagWatchNamespace, defaultWatchNamespace, 69 | "Namespace the controller watches for updates to Kubernetes objects, If empty, all namespaces are watched.") 70 | } 71 | 72 | // BuildRestConfig builds the REST config for the controller runtime 73 | // Note: the ByObject opts should include all the objects that the controller watches for 74 | func BuildRestConfig(rtCfg RuntimeConfig) (*rest.Config, error) { 75 | var restCFG *rest.Config 76 | var err error 77 | if rtCfg.KubeConfig == "" { 78 | restCFG, err = rest.InClusterConfig() 79 | } else { 80 | restCFG, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 81 | &clientcmd.ClientConfigLoadingRules{ExplicitPath: rtCfg.KubeConfig}, &clientcmd.ConfigOverrides{}).ClientConfig() 82 | } 83 | if err != nil { 84 | return nil, err 85 | } 86 | restCFG.QPS = defaultQPS 87 | restCFG.Burst = defaultBurst 88 | return restCFG, nil 89 | } 90 | 91 | // BuildCacheOptions returns a cache.Options struct for this controller. 92 | func BuildCacheOptions() cache.Options { 93 | cacheOptions := cache.Options{ 94 | ReaderFailOnMissingInformer: true, 95 | ByObject: map[client.Object]cache.ByObject{ 96 | &corev1.Pod{}: { 97 | Transform: k8s.StripDownPodTransformFunc, 98 | }, 99 | &corev1.Service{}: { 100 | Transform: k8s.StripDownServiceTransformFunc, 101 | }, 102 | &corev1.Namespace{}: {}, 103 | &networkingv1.NetworkPolicy{}: {}, 104 | &v1alpha1.PolicyEndpoint{}: {}, 105 | }, 106 | } 107 | return cacheOptions 108 | } 109 | 110 | // BuildRuntimeOptions builds the options for the controller runtime based on config 111 | func BuildRuntimeOptions(rtCfg RuntimeConfig, scheme *runtime.Scheme) ctrl.Options { 112 | cacheOptions := BuildCacheOptions() 113 | // if WatchNamespace in Options is not set, cache will watch for all namespaces 114 | if rtCfg.WatchNamespace != corev1.NamespaceAll { 115 | cacheOptions.DefaultNamespaces = map[string]cache.Config{ 116 | rtCfg.WatchNamespace: {}, 117 | } 118 | } 119 | return ctrl.Options{ 120 | Scheme: scheme, 121 | Metrics: metricsserver.Options{BindAddress: rtCfg.MetricsBindAddress}, 122 | HealthProbeBindAddress: rtCfg.HealthProbeBindAddress, 123 | LeaderElection: rtCfg.EnableLeaderElection, 124 | LeaderElectionID: rtCfg.LeaderElectionID, 125 | LeaderElectionNamespace: rtCfg.LeaderElectionNamespace, 126 | Cache: cacheOptions, 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/config/runtime_config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | func Test_buildCacheOptions(t *testing.T) { 10 | cacheOptions := BuildCacheOptions() 11 | g := NewWithT(t) 12 | g.Expect(cacheOptions.ReaderFailOnMissingInformer).To(BeTrue()) 13 | g.Expect(cacheOptions.ByObject).To(HaveLen(5)) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/k8s/configmap_store.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "k8s.io/client-go/tools/cache" 5 | ) 6 | 7 | // NewConfigMapStore constructs new conversionStore 8 | func NewConfigMapStore(notifyChan chan<- struct{}) *ConfigMapStore { 9 | return &ConfigMapStore{ 10 | store: cache.NewStore(cache.MetaNamespaceKeyFunc), 11 | notifyChannel: notifyChan, 12 | } 13 | } 14 | 15 | var _ cache.Store = &ConfigMapStore{} 16 | 17 | type ConfigMapStore struct { 18 | store cache.Store 19 | notifyChannel chan<- struct{} 20 | } 21 | 22 | // Add adds the given object to the accumulator associated with the given object's key 23 | func (s *ConfigMapStore) Add(obj interface{}) error { 24 | if err := s.store.Add(obj); err != nil { 25 | return err 26 | } 27 | s.notifyChannel <- struct{}{} 28 | return nil 29 | } 30 | 31 | // Update updates the given object in the accumulator associated with the given object's key 32 | func (s *ConfigMapStore) Update(obj interface{}) error { 33 | if err := s.store.Update(obj); err != nil { 34 | return err 35 | } 36 | s.notifyChannel <- struct{}{} 37 | return nil 38 | } 39 | 40 | // Delete deletes the given object from the accumulator associated with the given object's key 41 | func (s *ConfigMapStore) Delete(obj interface{}) error { 42 | if err := s.store.Delete(obj); err != nil { 43 | return err 44 | } 45 | s.notifyChannel <- struct{}{} 46 | return nil 47 | } 48 | 49 | // List returns a list of all the objects 50 | func (s *ConfigMapStore) List() []interface{} { 51 | return s.store.List() 52 | } 53 | 54 | // ListKeys returns a list of all the keys 55 | func (s *ConfigMapStore) ListKeys() []string { 56 | return s.store.ListKeys() 57 | } 58 | 59 | // Get returns the object with the given key 60 | func (s *ConfigMapStore) Get(obj interface{}) (item interface{}, exists bool, err error) { 61 | return s.store.Get(obj) 62 | } 63 | 64 | // GetByKey returns the object with the given key 65 | func (s *ConfigMapStore) GetByKey(key string) (item interface{}, exists bool, err error) { 66 | return s.store.GetByKey(key) 67 | } 68 | 69 | // Replace will delete the contents of the store, using instead the given list. 70 | func (s *ConfigMapStore) Replace(list []interface{}, resourceVersion string) error { 71 | return s.store.Replace(list, resourceVersion) 72 | } 73 | 74 | // Resync invokes the cache.store Resync method 75 | func (s *ConfigMapStore) Resync() error { 76 | return s.store.Resync() 77 | } 78 | -------------------------------------------------------------------------------- /pkg/k8s/finalizer.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-logr/logr" 7 | "github.com/samber/lo" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/client-go/util/retry" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 12 | ) 13 | 14 | type FinalizerManager interface { 15 | AddFinalizers(ctx context.Context, object client.Object, finalizers ...string) error 16 | RemoveFinalizers(ctx context.Context, object client.Object, finalizers ...string) error 17 | } 18 | 19 | func NewDefaultFinalizerManager(k8sClient client.Client, log logr.Logger) FinalizerManager { 20 | return &defaultFinalizerManager{ 21 | k8sClient: k8sClient, 22 | log: log, 23 | } 24 | } 25 | 26 | type defaultFinalizerManager struct { 27 | k8sClient client.Client 28 | 29 | log logr.Logger 30 | } 31 | 32 | func (m *defaultFinalizerManager) AddFinalizers(ctx context.Context, obj client.Object, finalizers ...string) error { 33 | return retry.RetryOnConflict(retry.DefaultBackoff, func() error { 34 | if err := m.k8sClient.Get(ctx, NamespacedName(obj), obj); err != nil { 35 | return err 36 | } 37 | 38 | oldObj := obj.DeepCopyObject().(client.Object) 39 | needsUpdate := false 40 | for _, finalizer := range finalizers { 41 | if !HasFinalizer(obj, finalizer) { 42 | controllerutil.AddFinalizer(obj, finalizer) 43 | needsUpdate = true 44 | } 45 | } 46 | if !needsUpdate { 47 | return nil 48 | } 49 | return m.k8sClient.Patch(ctx, obj, client.MergeFromWithOptions(oldObj, client.MergeFromWithOptimisticLock{})) 50 | }) 51 | } 52 | 53 | func (m *defaultFinalizerManager) RemoveFinalizers(ctx context.Context, obj client.Object, finalizers ...string) error { 54 | return retry.RetryOnConflict(retry.DefaultBackoff, func() error { 55 | if err := m.k8sClient.Get(ctx, NamespacedName(obj), obj); err != nil { 56 | return err 57 | } 58 | 59 | oldObj := obj.DeepCopyObject().(client.Object) 60 | needsUpdate := false 61 | for _, finalizer := range finalizers { 62 | if HasFinalizer(obj, finalizer) { 63 | controllerutil.RemoveFinalizer(obj, finalizer) 64 | needsUpdate = true 65 | } 66 | } 67 | if !needsUpdate { 68 | return nil 69 | } 70 | return m.k8sClient.Patch(ctx, obj, client.MergeFromWithOptions(oldObj, client.MergeFromWithOptimisticLock{})) 71 | }) 72 | } 73 | 74 | // HasFinalizer tests whether k8s object has specified finalizer 75 | func HasFinalizer(obj metav1.Object, finalizer string) bool { 76 | f := obj.GetFinalizers() 77 | return lo.Contains(f, finalizer) 78 | } 79 | -------------------------------------------------------------------------------- /pkg/k8s/finalizer_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/go-logr/logr" 8 | "github.com/google/go-cmp/cmp" 9 | "github.com/google/go-cmp/cmp/cmpopts" 10 | "github.com/stretchr/testify/assert" 11 | networking "k8s.io/api/networking/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 15 | testclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 16 | "sigs.k8s.io/controller-runtime/pkg/log" 17 | ) 18 | 19 | func TestHasFinalizer(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | obj metav1.Object 23 | finalizer string 24 | want bool 25 | }{ 26 | { 27 | name: "finalizer exists and matches", 28 | obj: &networking.Ingress{ 29 | ObjectMeta: metav1.ObjectMeta{ 30 | Finalizers: []string{"alb.ingress.k8s.aws/group"}, 31 | }, 32 | }, 33 | finalizer: "alb.ingress.k8s.aws/group", 34 | want: true, 35 | }, 36 | { 37 | name: "finalizer not exists", 38 | obj: &networking.Ingress{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | Finalizers: []string{}, 41 | }, 42 | }, 43 | finalizer: "alb.ingress.k8s.aws/group", 44 | want: false, 45 | }, 46 | { 47 | name: "finalizer exists but not matches", 48 | obj: &networking.Ingress{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Finalizers: []string{"alb.ingress.k8s.aws/group-b"}, 51 | }, 52 | }, 53 | finalizer: "alb.ingress.k8s.aws/group-a", 54 | want: false, 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | got := HasFinalizer(tt.obj, tt.finalizer) 60 | assert.Equal(t, tt.want, got) 61 | }) 62 | } 63 | } 64 | 65 | // IgnoreFakeClientPopulatedFields is an option to ignore fields populated by fakeK8sClient for a comparison. 66 | // Use this when comparing k8s objects in test cases. 67 | // These fields are ignored: TypeMeta and ObjectMeta.ResourceVersion 68 | func IgnoreFakeClientPopulatedFields() cmp.Option { 69 | return cmp.Options{ 70 | // ignore unset fields in left hand side 71 | cmpopts.IgnoreTypes(metav1.TypeMeta{}), 72 | cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion"), 73 | } 74 | } 75 | 76 | func Test_defaultFinalizerManager_AddFinalizers(t *testing.T) { 77 | type args struct { 78 | obj *networking.Ingress 79 | finalizers []string 80 | } 81 | tests := []struct { 82 | name string 83 | args args 84 | wantObj *networking.Ingress 85 | wantErr error 86 | }{ 87 | { 88 | name: "add one finalizer", 89 | args: args{ 90 | obj: &networking.Ingress{ 91 | ObjectMeta: metav1.ObjectMeta{ 92 | Namespace: "my-ns", 93 | Name: "my-mesh", 94 | }, 95 | }, 96 | finalizers: []string{"finalizer-1"}, 97 | }, 98 | wantObj: &networking.Ingress{ 99 | ObjectMeta: metav1.ObjectMeta{ 100 | Namespace: "my-ns", 101 | Name: "my-mesh", 102 | Finalizers: []string{"finalizer-1"}, 103 | }, 104 | }, 105 | }, 106 | { 107 | name: "add one finalizer + added finalizer already exists", 108 | args: args{ 109 | obj: &networking.Ingress{ 110 | ObjectMeta: metav1.ObjectMeta{ 111 | Namespace: "my-ns", 112 | Name: "my-mesh", 113 | Finalizers: []string{"finalizer-1"}, 114 | }, 115 | }, 116 | finalizers: []string{"finalizer-1"}, 117 | }, 118 | wantObj: &networking.Ingress{ 119 | ObjectMeta: metav1.ObjectMeta{ 120 | Namespace: "my-ns", 121 | Name: "my-mesh", 122 | Finalizers: []string{"finalizer-1"}, 123 | }, 124 | }, 125 | }, 126 | { 127 | name: "add one finalizer + other finalizer already exists", 128 | args: args{ 129 | obj: &networking.Ingress{ 130 | ObjectMeta: metav1.ObjectMeta{ 131 | Namespace: "my-ns", 132 | Name: "my-mesh", 133 | Finalizers: []string{"finalizer-2"}, 134 | }, 135 | }, 136 | finalizers: []string{"finalizer-1"}, 137 | }, 138 | wantObj: &networking.Ingress{ 139 | ObjectMeta: metav1.ObjectMeta{ 140 | Namespace: "my-ns", 141 | Name: "my-mesh", 142 | Finalizers: []string{"finalizer-2", "finalizer-1"}, 143 | }, 144 | }, 145 | }, 146 | { 147 | name: "add two finalizer", 148 | args: args{ 149 | obj: &networking.Ingress{ 150 | ObjectMeta: metav1.ObjectMeta{ 151 | Namespace: "my-ns", 152 | Name: "my-mesh", 153 | }, 154 | }, 155 | finalizers: []string{"finalizer-1", "finalizer-2"}, 156 | }, 157 | wantObj: &networking.Ingress{ 158 | ObjectMeta: metav1.ObjectMeta{ 159 | Namespace: "my-ns", 160 | Name: "my-mesh", 161 | Finalizers: []string{"finalizer-1", "finalizer-2"}, 162 | }, 163 | }, 164 | }, 165 | { 166 | name: "add two finalizer + one added finalizer already exists", 167 | args: args{ 168 | obj: &networking.Ingress{ 169 | ObjectMeta: metav1.ObjectMeta{ 170 | Namespace: "my-ns", 171 | Name: "my-mesh", 172 | Finalizers: []string{"finalizer-2"}, 173 | }, 174 | }, 175 | finalizers: []string{"finalizer-1", "finalizer-2"}, 176 | }, 177 | wantObj: &networking.Ingress{ 178 | ObjectMeta: metav1.ObjectMeta{ 179 | Namespace: "my-ns", 180 | Name: "my-mesh", 181 | Finalizers: []string{"finalizer-2", "finalizer-1"}, 182 | }, 183 | }, 184 | }, 185 | } 186 | for _, tt := range tests { 187 | t.Run(tt.name, func(t *testing.T) { 188 | ctx := context.Background() 189 | k8sSchema := runtime.NewScheme() 190 | clientgoscheme.AddToScheme(k8sSchema) 191 | k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build() 192 | m := NewDefaultFinalizerManager(k8sClient, logr.New(&log.NullLogSink{})) 193 | 194 | err := k8sClient.Create(ctx, tt.args.obj.DeepCopy()) 195 | assert.NoError(t, err) 196 | 197 | err = m.AddFinalizers(ctx, tt.args.obj, tt.args.finalizers...) 198 | if tt.wantErr != nil { 199 | assert.EqualError(t, err, tt.wantErr.Error()) 200 | } else { 201 | assert.NoError(t, err) 202 | gotObj := &networking.Ingress{} 203 | err = k8sClient.Get(ctx, NamespacedName(tt.args.obj), gotObj) 204 | assert.NoError(t, err) 205 | opts := IgnoreFakeClientPopulatedFields() 206 | assert.True(t, cmp.Equal(tt.wantObj, gotObj, opts), "diff", cmp.Diff(tt.wantObj, gotObj, opts)) 207 | } 208 | }) 209 | } 210 | } 211 | 212 | func Test_defaultFinalizerManager_RemoveFinalizers(t *testing.T) { 213 | type args struct { 214 | obj *networking.Ingress 215 | finalizers []string 216 | } 217 | tests := []struct { 218 | name string 219 | args args 220 | wantObj *networking.Ingress 221 | wantErr error 222 | }{ 223 | { 224 | name: "remove one finalizer", 225 | args: args{ 226 | obj: &networking.Ingress{ 227 | ObjectMeta: metav1.ObjectMeta{ 228 | Namespace: "my-ns", 229 | Name: "my-mesh", 230 | Finalizers: []string{"finalizer-1"}, 231 | }, 232 | }, 233 | finalizers: []string{"finalizer-1"}, 234 | }, 235 | wantObj: &networking.Ingress{ 236 | ObjectMeta: metav1.ObjectMeta{ 237 | Namespace: "my-ns", 238 | Name: "my-mesh", 239 | Finalizers: nil, 240 | }, 241 | }, 242 | }, 243 | { 244 | name: "remove one finalizer + removed finalizer didn't exists", 245 | args: args{ 246 | obj: &networking.Ingress{ 247 | ObjectMeta: metav1.ObjectMeta{ 248 | Namespace: "my-ns", 249 | Name: "my-mesh", 250 | }, 251 | }, 252 | finalizers: []string{"finalizer-1"}, 253 | }, 254 | wantObj: &networking.Ingress{ 255 | ObjectMeta: metav1.ObjectMeta{ 256 | Namespace: "my-ns", 257 | Name: "my-mesh", 258 | Finalizers: nil, 259 | }, 260 | }, 261 | }, 262 | { 263 | name: "remove one finalizer + other finalizer already exists", 264 | args: args{ 265 | obj: &networking.Ingress{ 266 | ObjectMeta: metav1.ObjectMeta{ 267 | Namespace: "my-ns", 268 | Name: "my-mesh", 269 | Finalizers: []string{"finalizer-1", "finalizer-2"}, 270 | }, 271 | }, 272 | finalizers: []string{"finalizer-1"}, 273 | }, 274 | wantObj: &networking.Ingress{ 275 | ObjectMeta: metav1.ObjectMeta{ 276 | Namespace: "my-ns", 277 | Name: "my-mesh", 278 | Finalizers: []string{"finalizer-2"}, 279 | }, 280 | }, 281 | }, 282 | { 283 | name: "remove two finalizer", 284 | args: args{ 285 | obj: &networking.Ingress{ 286 | ObjectMeta: metav1.ObjectMeta{ 287 | Namespace: "my-ns", 288 | Name: "my-mesh", 289 | Finalizers: []string{"finalizer-1", "finalizer-2"}, 290 | }, 291 | }, 292 | finalizers: []string{"finalizer-1", "finalizer-2"}, 293 | }, 294 | wantObj: &networking.Ingress{ 295 | ObjectMeta: metav1.ObjectMeta{ 296 | Namespace: "my-ns", 297 | Name: "my-mesh", 298 | Finalizers: nil, 299 | }, 300 | }, 301 | }, 302 | { 303 | name: "remove two finalizer + one removed finalizer already exists", 304 | args: args{ 305 | obj: &networking.Ingress{ 306 | ObjectMeta: metav1.ObjectMeta{ 307 | Namespace: "my-ns", 308 | Name: "my-mesh", 309 | Finalizers: []string{"finalizer-2", "finalizer-3"}, 310 | }, 311 | }, 312 | finalizers: []string{"finalizer-1", "finalizer-2"}, 313 | }, 314 | wantObj: &networking.Ingress{ 315 | ObjectMeta: metav1.ObjectMeta{ 316 | Namespace: "my-ns", 317 | Name: "my-mesh", 318 | Finalizers: []string{"finalizer-3"}, 319 | }, 320 | }, 321 | }, 322 | } 323 | for _, tt := range tests { 324 | t.Run(tt.name, func(t *testing.T) { 325 | ctx := context.Background() 326 | k8sSchema := runtime.NewScheme() 327 | clientgoscheme.AddToScheme(k8sSchema) 328 | 329 | k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build() 330 | m := NewDefaultFinalizerManager(k8sClient, logr.New(&log.NullLogSink{})) 331 | 332 | err := k8sClient.Create(ctx, tt.args.obj.DeepCopy()) 333 | assert.NoError(t, err) 334 | 335 | err = m.RemoveFinalizers(ctx, tt.args.obj, tt.args.finalizers...) 336 | if tt.wantErr != nil { 337 | assert.EqualError(t, err, tt.wantErr.Error()) 338 | } else { 339 | assert.NoError(t, err) 340 | gotObj := &networking.Ingress{} 341 | err = k8sClient.Get(ctx, NamespacedName(tt.args.obj), gotObj) 342 | assert.NoError(t, err) 343 | opts := IgnoreFakeClientPopulatedFields() 344 | assert.True(t, cmp.Equal(tt.wantObj, gotObj, opts), "diff", cmp.Diff(tt.wantObj, gotObj, opts)) 345 | } 346 | }) 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /pkg/k8s/pod_utils.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | corev1 "k8s.io/api/core/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/util/intstr" 8 | ) 9 | 10 | const ( 11 | podIPAnnotation = "vpc.amazonaws.com/pod-ips" 12 | ) 13 | 14 | // GetPodIP returns the pod IP from the pod status or from the pod annotation. 15 | func GetPodIP(pod *corev1.Pod) string { 16 | if len(pod.Status.PodIP) > 0 { 17 | return pod.Status.PodIP 18 | } else { 19 | return pod.Annotations[podIPAnnotation] 20 | } 21 | } 22 | 23 | // LookupContainerPortAndName returns numerical containerPort and portName for specific port and protocol 24 | func LookupContainerPortAndName(pod *corev1.Pod, port intstr.IntOrString, protocol corev1.Protocol) (int32, string, error) { 25 | for _, podContainer := range pod.Spec.Containers { 26 | for _, podPort := range podContainer.Ports { 27 | if podPort.Protocol != protocol { 28 | continue 29 | } 30 | switch port.Type { 31 | case intstr.String: 32 | if podPort.Name == port.StrVal { 33 | return podPort.ContainerPort, podPort.Name, nil 34 | } 35 | case intstr.Int: 36 | if podPort.ContainerPort == port.IntVal { 37 | return podPort.ContainerPort, podPort.Name, nil 38 | } 39 | } 40 | } 41 | } 42 | if port.Type == intstr.Int { 43 | return port.IntVal, "", nil 44 | } 45 | return 0, "", errors.Errorf("unable to find port %s on pod %s", port.String(), NamespacedName(pod)) 46 | } 47 | 48 | // StripDownPodTransformFunc is a transform function that strips down pod to reduce memory usage. 49 | // see details in [stripDownPodObject]. 50 | func StripDownPodTransformFunc(obj interface{}) (interface{}, error) { 51 | if pod, ok := obj.(*corev1.Pod); ok { 52 | return stripDownPodObject(pod), nil 53 | } 54 | return obj, nil 55 | } 56 | 57 | // stripDownPodObject provides an stripDown version of pod to reduce memory usage. 58 | // NOTE: if the controller needs to refer to more pod fields in the future these fields need to be added to the cache 59 | func stripDownPodObject(pod *corev1.Pod) *corev1.Pod { 60 | pod.ObjectMeta = metav1.ObjectMeta{ 61 | Name: pod.Name, 62 | Namespace: pod.Namespace, 63 | UID: pod.UID, 64 | DeletionTimestamp: pod.DeletionTimestamp, 65 | Labels: pod.Labels, 66 | Annotations: pod.Annotations, 67 | ResourceVersion: pod.ResourceVersion, 68 | Finalizers: pod.Finalizers, 69 | } 70 | // Extract only the Name and Ports in spec.Container 71 | strippedContainers := make([]corev1.Container, 0, len(pod.Spec.Containers)) 72 | for _, container := range pod.Spec.Containers { 73 | strippedContainers = append(strippedContainers, corev1.Container{ 74 | Name: container.Name, 75 | Ports: container.Ports, 76 | }) 77 | } 78 | pod.Spec = corev1.PodSpec{ 79 | Containers: strippedContainers, 80 | } 81 | pod.Status = corev1.PodStatus{ 82 | HostIP: pod.Status.HostIP, 83 | HostIPs: pod.Status.HostIPs, 84 | PodIP: pod.Status.PodIP, 85 | PodIPs: pod.Status.PodIPs, 86 | } 87 | return pod 88 | } 89 | -------------------------------------------------------------------------------- /pkg/k8s/pod_utils_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/util/intstr" 10 | ) 11 | 12 | func Test_GetPodIP(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | pod *corev1.Pod 16 | want string 17 | }{ 18 | { 19 | name: "pod with status IP", 20 | pod: &corev1.Pod{ 21 | Status: corev1.PodStatus{ 22 | PodIP: "192.168.11.22", 23 | }, 24 | }, 25 | want: "192.168.11.22", 26 | }, 27 | { 28 | name: "pod with annotation IP", 29 | pod: &corev1.Pod{ 30 | ObjectMeta: metav1.ObjectMeta{ 31 | Annotations: map[string]string{ 32 | podIPAnnotation: "1.2.3.4", 33 | }, 34 | }, 35 | }, 36 | want: "1.2.3.4", 37 | }, 38 | { 39 | name: "pod without status IP or annotation IP", 40 | pod: &corev1.Pod{}, 41 | }, 42 | } 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | got := GetPodIP(tt.pod) 46 | assert.Equal(t, tt.want, got) 47 | }) 48 | } 49 | } 50 | 51 | func Test_LookupContainerPortAndName(t *testing.T) { 52 | pod := &corev1.Pod{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Namespace: "default", 55 | Name: "pod", 56 | }, 57 | Spec: corev1.PodSpec{ 58 | Containers: []corev1.Container{ 59 | { 60 | Ports: []corev1.ContainerPort{ 61 | { 62 | Name: "http", 63 | ContainerPort: 80, 64 | Protocol: corev1.ProtocolTCP, 65 | }, 66 | }, 67 | }, 68 | { 69 | Ports: []corev1.ContainerPort{ 70 | { 71 | Name: "https", 72 | ContainerPort: 443, 73 | Protocol: corev1.ProtocolTCP, 74 | }, 75 | { 76 | ContainerPort: 8080, 77 | Protocol: corev1.ProtocolTCP, 78 | }, 79 | }, 80 | }, 81 | }, 82 | }, 83 | } 84 | type want struct { 85 | port int32 86 | name string 87 | } 88 | type args struct { 89 | pod *corev1.Pod 90 | protocol corev1.Protocol 91 | port intstr.IntOrString 92 | } 93 | tests := []struct { 94 | name string 95 | args args 96 | want want 97 | wantErr string 98 | }{ 99 | { 100 | name: "resolve numeric pod", 101 | args: args{ 102 | pod: pod, 103 | port: intstr.FromInt(8080), 104 | }, 105 | want: want{ 106 | port: 8080, 107 | }, 108 | }, 109 | { 110 | name: "numeric pod not in pod spec can still be resolved", 111 | args: args{ 112 | pod: pod, 113 | port: intstr.FromInt(9090), 114 | }, 115 | want: want{ 116 | port: 9090, 117 | }, 118 | }, 119 | { 120 | name: "lookup based on port name", 121 | args: args{ 122 | pod: pod, 123 | port: intstr.FromString("http"), 124 | }, 125 | want: want{ 126 | port: 80, 127 | name: "http", 128 | }, 129 | }, 130 | { 131 | name: "lookup based on port name in another container", 132 | args: args{ 133 | pod: pod, 134 | port: intstr.FromString("https"), 135 | }, 136 | want: want{ 137 | port: 443, 138 | name: "https", 139 | }, 140 | }, 141 | { 142 | name: "port matches, but protocol does not", 143 | args: args{ 144 | pod: pod, 145 | port: intstr.FromString("https"), 146 | protocol: corev1.ProtocolUDP, 147 | }, 148 | wantErr: "unable to find port https on pod default/pod", 149 | }, 150 | { 151 | name: "numeric port lookup ignores the protocol", 152 | args: args{ 153 | pod: pod, 154 | port: intstr.FromInt(443), 155 | protocol: corev1.ProtocolUDP, 156 | }, 157 | want: want{ 158 | port: 443, 159 | }, 160 | }, 161 | { 162 | name: "nonexistent port name", 163 | args: args{ 164 | pod: pod, 165 | port: intstr.FromString("nonexistent"), 166 | }, 167 | wantErr: "unable to find port nonexistent on pod default/pod", 168 | }, 169 | } 170 | for _, tt := range tests { 171 | t.Run(tt.name, func(t *testing.T) { 172 | protocol := tt.args.protocol 173 | if len(protocol) == 0 { 174 | protocol = corev1.ProtocolTCP 175 | } 176 | got := want{} 177 | var err error 178 | got.port, got.name, err = LookupContainerPortAndName(tt.args.pod, tt.args.port, protocol) 179 | if len(tt.wantErr) > 0 { 180 | assert.EqualError(t, err, tt.wantErr) 181 | } else { 182 | assert.NoError(t, err) 183 | assert.Equal(t, tt.want, got) 184 | } 185 | }) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pkg/k8s/service_utils.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | corev1 "k8s.io/api/core/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/util/intstr" 8 | ) 9 | 10 | // LookupServiceListenPort returns the numerical port for the service listen port if the target port name matches 11 | // or the port number and the protocol matches the target port. If no matching port is found, it returns a 0 and an error. 12 | func LookupServiceListenPort(svc *corev1.Service, port intstr.IntOrString, protocol corev1.Protocol) (int32, error) { 13 | for _, svcPort := range svc.Spec.Ports { 14 | if svcPort.TargetPort.Type == port.Type && svcPort.TargetPort.String() == port.String() && svcPort.Protocol == protocol { 15 | return svcPort.Port, nil 16 | } 17 | } 18 | return 0, errors.Errorf("unable to find port %s on service %s", port.String(), NamespacedName(svc)) 19 | } 20 | 21 | // LookupListenPortFromPodSpec returns the numerical listener port from the service spec if the input port matches the target port 22 | // in the pod spec 23 | func LookupListenPortFromPodSpec(svc *corev1.Service, pod *corev1.Pod, port intstr.IntOrString, protocol corev1.Protocol) (int32, error) { 24 | containerPort, containerPortName, err := LookupContainerPortAndName(pod, port, protocol) 25 | if err != nil { 26 | return 0, err 27 | } 28 | for _, svcPort := range svc.Spec.Ports { 29 | if svcPort.Protocol != protocol { 30 | continue 31 | } 32 | switch svcPort.TargetPort.Type { 33 | case intstr.String: 34 | if containerPortName == svcPort.TargetPort.StrVal { 35 | return svcPort.Port, nil 36 | } 37 | 38 | case intstr.Int: 39 | if containerPort == svcPort.TargetPort.IntVal { 40 | return svcPort.Port, nil 41 | } 42 | } 43 | } 44 | return 0, errors.Errorf("unable to find listener port for port %s on service %s", port.String(), NamespacedName(svc)) 45 | } 46 | 47 | // IsServiceHeadless returns true if the service is headless 48 | func IsServiceHeadless(svc *corev1.Service) bool { 49 | if svc.Spec.ClusterIP == "" || svc.Spec.ClusterIP == "None" { 50 | return true 51 | } 52 | return false 53 | } 54 | 55 | // StripDownServiceTransformFunc is a transform function that strips down service to reduce memory usage. 56 | // see details in [stripDownServiceObject]. 57 | func StripDownServiceTransformFunc(obj interface{}) (interface{}, error) { 58 | if service, ok := obj.(*corev1.Service); ok { 59 | return stripDownServiceObject(service), nil 60 | } 61 | return obj, nil 62 | } 63 | 64 | // stripDownServiceObject provides an stripDown version of service to reduce memory usage. 65 | // NOTE: if the controller needs to refer to more service fields in the future 66 | // these fields need to be added to the cache 67 | func stripDownServiceObject(service *corev1.Service) *corev1.Service { 68 | service.ObjectMeta = metav1.ObjectMeta{ 69 | Name: service.Name, 70 | Namespace: service.Namespace, 71 | UID: service.UID, 72 | DeletionTimestamp: service.DeletionTimestamp, 73 | ResourceVersion: service.ResourceVersion, 74 | Finalizers: service.Finalizers, 75 | } 76 | service.Spec = corev1.ServiceSpec{ 77 | Selector: service.Spec.Selector, 78 | ClusterIP: service.Spec.ClusterIP, 79 | ClusterIPs: service.Spec.ClusterIPs, 80 | Ports: service.Spec.Ports, 81 | Type: service.Spec.Type, 82 | } 83 | service.Status = corev1.ServiceStatus{ 84 | LoadBalancer: service.Status.LoadBalancer, 85 | } 86 | return service 87 | } 88 | -------------------------------------------------------------------------------- /pkg/k8s/service_utils_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/util/intstr" 10 | ) 11 | 12 | func Test_LookupServicePort(t *testing.T) { 13 | svc := &corev1.Service{ 14 | ObjectMeta: metav1.ObjectMeta{ 15 | Name: "svc", 16 | Namespace: "default", 17 | }, 18 | Spec: corev1.ServiceSpec{ 19 | Ports: []corev1.ServicePort{ 20 | { 21 | Name: "http", 22 | Port: 80, 23 | TargetPort: intstr.FromInt(8080), 24 | Protocol: corev1.ProtocolTCP, 25 | }, 26 | { 27 | Name: "https", 28 | Port: 443, 29 | TargetPort: intstr.IntOrString{ 30 | Type: intstr.String, 31 | StrVal: "pod-https", 32 | }, 33 | Protocol: corev1.ProtocolTCP, 34 | }, 35 | { 36 | Name: "dns-lookup", 37 | Port: 53, 38 | TargetPort: intstr.IntOrString{ 39 | Type: intstr.String, 40 | StrVal: "pod-dns-lookup", 41 | }, 42 | Protocol: corev1.ProtocolUDP, 43 | }, 44 | }, 45 | }, 46 | } 47 | type args struct { 48 | svc *corev1.Service 49 | port intstr.IntOrString 50 | protocol corev1.Protocol 51 | } 52 | tests := []struct { 53 | name string 54 | args args 55 | want int32 56 | wantErr string 57 | }{ 58 | { 59 | name: "resolve numeric service port", 60 | args: args{ 61 | svc: svc, 62 | port: intstr.FromInt(8080), 63 | }, 64 | want: 80, 65 | }, 66 | { 67 | name: "numeric port protocol mismatch", 68 | args: args{ 69 | svc: svc, 70 | port: intstr.FromInt(8080), 71 | protocol: corev1.ProtocolUDP, 72 | }, 73 | wantErr: "unable to find port 8080 on service default/svc", 74 | }, 75 | { 76 | name: "numeric port not in service spec", 77 | args: args{ 78 | svc: svc, 79 | port: intstr.FromInt(9090), 80 | }, 81 | wantErr: "unable to find port 9090 on service default/svc", 82 | }, 83 | { 84 | name: "resolve named service target port", 85 | args: args{ 86 | svc: svc, 87 | port: intstr.FromString("pod-https"), 88 | }, 89 | want: 443, 90 | }, 91 | { 92 | name: "named service target port, protocol mismatch", 93 | args: args{ 94 | svc: svc, 95 | port: intstr.FromString("pod-dns-lookup"), 96 | }, 97 | wantErr: "unable to find port pod-dns-lookup on service default/svc", 98 | }, 99 | { 100 | name: "nonexistent port name", 101 | args: args{ 102 | svc: svc, 103 | port: intstr.FromString("nonexistent"), 104 | }, 105 | wantErr: "unable to find port nonexistent on service default/svc", 106 | }, 107 | } 108 | for _, tt := range tests { 109 | t.Run(tt.name, func(t *testing.T) { 110 | protocol := corev1.ProtocolTCP 111 | if len(tt.args.protocol) > 0 { 112 | protocol = tt.args.protocol 113 | } 114 | got, err := LookupServiceListenPort(tt.args.svc, tt.args.port, protocol) 115 | if len(tt.wantErr) > 0 { 116 | assert.EqualError(t, err, tt.wantErr) 117 | } else { 118 | assert.NoError(t, err) 119 | assert.Equal(t, tt.want, got) 120 | } 121 | }) 122 | } 123 | } 124 | 125 | func Test_LookupListenPortFromPodSpec(t *testing.T) { 126 | svc := &corev1.Service{ 127 | ObjectMeta: metav1.ObjectMeta{ 128 | Name: "svc", 129 | Namespace: "app", 130 | }, 131 | Spec: corev1.ServiceSpec{ 132 | Ports: []corev1.ServicePort{ 133 | { 134 | Name: "http", 135 | Port: 80, 136 | TargetPort: intstr.FromInt(8080), 137 | Protocol: corev1.ProtocolTCP, 138 | }, 139 | { 140 | Name: "https", 141 | Port: 443, 142 | TargetPort: intstr.IntOrString{ 143 | Type: intstr.String, 144 | StrVal: "pod-https", 145 | }, 146 | Protocol: corev1.ProtocolTCP, 147 | }, 148 | { 149 | Name: "dns-lookup", 150 | Port: 53, 151 | TargetPort: intstr.IntOrString{ 152 | Type: intstr.String, 153 | StrVal: "pod-dns-lookup", 154 | }, 155 | Protocol: corev1.ProtocolUDP, 156 | }, 157 | { 158 | Name: "dns-alt", 159 | Port: 853, 160 | TargetPort: intstr.IntOrString{ 161 | Type: intstr.Int, 162 | IntVal: 5853, 163 | }, 164 | Protocol: corev1.ProtocolTCP, 165 | }, 166 | }, 167 | }, 168 | } 169 | pod := &corev1.Pod{ 170 | ObjectMeta: metav1.ObjectMeta{ 171 | Name: "pod", 172 | Namespace: "app", 173 | }, 174 | Spec: corev1.PodSpec{ 175 | Containers: []corev1.Container{ 176 | { 177 | Ports: []corev1.ContainerPort{ 178 | { 179 | Name: "pod-https", 180 | ContainerPort: 8443, 181 | Protocol: corev1.ProtocolTCP, 182 | }, 183 | }, 184 | }, 185 | { 186 | Ports: []corev1.ContainerPort{ 187 | { 188 | Name: "pod-dns-lookup", 189 | ContainerPort: 5353, 190 | Protocol: corev1.ProtocolUDP, 191 | }, 192 | { 193 | Name: "pod-http", 194 | ContainerPort: 8080, 195 | Protocol: corev1.ProtocolTCP, 196 | }, 197 | }, 198 | }, 199 | { 200 | Ports: []corev1.ContainerPort{ 201 | { 202 | Name: "unexposed-http", 203 | ContainerPort: 8081, 204 | Protocol: corev1.ProtocolTCP, 205 | }, 206 | { 207 | Name: "dns-secure", 208 | ContainerPort: 5853, 209 | Protocol: corev1.ProtocolUDP, 210 | }, 211 | }, 212 | }, 213 | }, 214 | }, 215 | } 216 | type args struct { 217 | svc *corev1.Service 218 | pod *corev1.Pod 219 | port intstr.IntOrString 220 | protocol corev1.Protocol 221 | } 222 | tests := []struct { 223 | name string 224 | args args 225 | want int32 226 | wantErr string 227 | }{ 228 | { 229 | name: "service port target port string, input port is int matching pod target port", 230 | args: args{ 231 | svc: svc, 232 | pod: pod, 233 | port: intstr.FromInt(8443), 234 | }, 235 | want: 443, 236 | }, 237 | { 238 | name: "service target port int, input port is string matching pod target port", 239 | args: args{ 240 | svc: svc, 241 | pod: pod, 242 | port: intstr.FromString("pod-http"), 243 | }, 244 | want: 80, 245 | }, 246 | { 247 | name: "service and container port int, input port is string", 248 | args: args{ 249 | svc: svc, 250 | pod: pod, 251 | port: intstr.FromString("pod-http"), 252 | }, 253 | want: 80, 254 | }, 255 | { 256 | name: "service and container port string, input port is string", 257 | args: args{ 258 | svc: svc, 259 | pod: pod, 260 | port: intstr.FromString("pod-https"), 261 | }, 262 | want: 443, 263 | }, 264 | { 265 | name: "container port not found", 266 | args: args{ 267 | svc: svc, 268 | pod: pod, 269 | port: intstr.FromString("port-nonexistent"), 270 | }, 271 | wantErr: "unable to find port port-nonexistent on pod app/pod", 272 | }, 273 | { 274 | name: "service port not found", 275 | args: args{ 276 | svc: svc, 277 | pod: pod, 278 | port: intstr.FromString("unexposed-http"), 279 | }, 280 | wantErr: "unable to find listener port for port unexposed-http on service app/svc", 281 | }, 282 | { 283 | name: "protocol mismatch", 284 | args: args{ 285 | svc: svc, 286 | pod: pod, 287 | port: intstr.FromString("dns-secure"), 288 | protocol: corev1.ProtocolUDP, 289 | }, 290 | wantErr: "unable to find listener port for port dns-secure on service app/svc", 291 | }, 292 | } 293 | for _, tt := range tests { 294 | t.Run(tt.name, func(t *testing.T) { 295 | protocol := corev1.ProtocolTCP 296 | if len(tt.args.protocol) > 0 { 297 | protocol = tt.args.protocol 298 | } 299 | got, err := LookupListenPortFromPodSpec(tt.args.svc, tt.args.pod, tt.args.port, protocol) 300 | if len(tt.wantErr) > 0 { 301 | assert.EqualError(t, err, tt.wantErr) 302 | } else { 303 | assert.NoError(t, err) 304 | assert.Equal(t, tt.want, got) 305 | } 306 | }) 307 | } 308 | } 309 | 310 | func Test_IsServiceHeadless(t *testing.T) { 311 | tests := []struct { 312 | name string 313 | svc *corev1.Service 314 | want bool 315 | }{ 316 | { 317 | name: "headless service", 318 | svc: &corev1.Service{ 319 | Spec: corev1.ServiceSpec{ 320 | ClusterIP: corev1.ClusterIPNone, 321 | }, 322 | }, 323 | want: true, 324 | }, 325 | { 326 | name: "empty IP", 327 | svc: &corev1.Service{ 328 | Spec: corev1.ServiceSpec{}, 329 | }, 330 | want: true, 331 | }, 332 | { 333 | name: "some cluster IP", 334 | svc: &corev1.Service{ 335 | Spec: corev1.ServiceSpec{ 336 | ClusterIP: "10.100.0.209", 337 | }, 338 | }, 339 | }, 340 | } 341 | for _, tt := range tests { 342 | t.Run(tt.name, func(t *testing.T) { 343 | assert.Equal(t, tt.want, IsServiceHeadless(tt.svc)) 344 | }) 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /pkg/k8s/utils.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "sigs.k8s.io/controller-runtime/pkg/client" 5 | ) 6 | 7 | // NamespacedName returns the namespaced name for k8s objects 8 | func NamespacedName(obj client.Object) client.ObjectKey { 9 | return client.ObjectKeyFromObject(obj) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/k8s/utils_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "testing" 5 | 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | 8 | "github.com/stretchr/testify/assert" 9 | networking "k8s.io/api/networking/v1" 10 | rbac "k8s.io/api/rbac/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/types" 13 | ) 14 | 15 | func TestNamespacedName(t *testing.T) { 16 | tests := []struct { 17 | name string 18 | obj client.Object 19 | want types.NamespacedName 20 | }{ 21 | { 22 | name: "cluster-scoped object", 23 | obj: &rbac.ClusterRole{ 24 | ObjectMeta: metav1.ObjectMeta{ 25 | Name: "ingress", 26 | }, 27 | }, 28 | want: types.NamespacedName{ 29 | Namespace: "", 30 | Name: "ingress", 31 | }, 32 | }, 33 | { 34 | name: "namespace-scoped object", 35 | obj: &networking.Ingress{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Namespace: "namespace", 38 | Name: "ingress", 39 | }, 40 | }, 41 | want: types.NamespacedName{ 42 | Namespace: "namespace", 43 | Name: "ingress", 44 | }, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | got := NamespacedName(tt.obj) 50 | assert.Equal(t, tt.want, got) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/policyendpoints/indexer.go: -------------------------------------------------------------------------------- 1 | package policyendpoints 2 | 3 | import ( 4 | policyinfo "github.com/aws/amazon-network-policy-controller-k8s/api/v1alpha1" 5 | "sigs.k8s.io/controller-runtime/pkg/client" 6 | ) 7 | 8 | const ( 9 | IndexKeyPolicyReferenceName = "spec.policyRef.name" 10 | ) 11 | 12 | // IndexFunctionPolicyReferenceName is IndexFunc for "PolicyReference" index. 13 | func IndexFunctionPolicyReferenceName(obj client.Object) []string { 14 | policyEndpoint := obj.(*policyinfo.PolicyEndpoint) 15 | return []string{policyEndpoint.Spec.PolicyRef.Name} 16 | } 17 | -------------------------------------------------------------------------------- /pkg/resolvers/policies.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-logr/logr" 7 | corev1 "k8s.io/api/core/v1" 8 | networking "k8s.io/api/networking/v1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | // PolicyReferenceResolver resolves the referred network policies for a given pod, namespace or service. 13 | type PolicyReferenceResolver interface { 14 | GetReferredPoliciesForPod(ctx context.Context, pod, podOld *corev1.Pod) ([]networking.NetworkPolicy, error) 15 | GetReferredPoliciesForNamespace(ctx context.Context, ns, nsOld *corev1.Namespace) ([]networking.NetworkPolicy, error) 16 | GetReferredPoliciesForService(ctx context.Context, svc, svcOld *corev1.Service) ([]networking.NetworkPolicy, error) 17 | } 18 | 19 | func NewPolicyReferenceResolver(k8sClient client.Client, policyTracker PolicyTracker, logger logr.Logger) *defaultPolicyReferenceResolver { 20 | return &defaultPolicyReferenceResolver{ 21 | k8sClient: k8sClient, 22 | policyTracker: policyTracker, 23 | logger: logger, 24 | } 25 | } 26 | 27 | var _ PolicyReferenceResolver = (*defaultPolicyReferenceResolver)(nil) 28 | 29 | type defaultPolicyReferenceResolver struct { 30 | logger logr.Logger 31 | k8sClient client.Client 32 | policyTracker PolicyTracker 33 | } 34 | 35 | // GetReferredPoliciesForPod returns the network policies matching the pod's labels. The podOld resource is the old 36 | // resource for update events and is used to determine the policies to reconcile for the label changes. 37 | // In case of the pods, the pod labels are matched against the policy's podSelector or the ingress or egress rules. 38 | func (r *defaultPolicyReferenceResolver) GetReferredPoliciesForPod(ctx context.Context, pod *corev1.Pod, podOld *corev1.Pod) ([]networking.NetworkPolicy, error) { 39 | return r.getReferredPoliciesForPod(ctx, pod, podOld) 40 | } 41 | 42 | // GetReferredPoliciesForNamespace returns the network policies matching the namespace's labels in the ingress or egress 43 | // rules. The nsOld resources is to account for the namespace label changes during update. 44 | func (r *defaultPolicyReferenceResolver) GetReferredPoliciesForNamespace(ctx context.Context, ns *corev1.Namespace, nsOld *corev1.Namespace) ([]networking.NetworkPolicy, error) { 45 | return r.getReferredPoliciesForNamespace(ctx, ns, nsOld) 46 | } 47 | 48 | // GetReferredPoliciesForService returns the network policies matching the service's pod selector in the egress rules. 49 | // The svcOld resource is to account for the service label changes during update. 50 | func (r *defaultPolicyReferenceResolver) GetReferredPoliciesForService(ctx context.Context, svc *corev1.Service, svcOld *corev1.Service) ([]networking.NetworkPolicy, error) { 51 | return r.getReferredPoliciesForService(ctx, svc, svcOld) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/resolvers/policies_for_namespace.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | corev1 "k8s.io/api/core/v1" 8 | networking "k8s.io/api/networking/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/labels" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | func (r *defaultPolicyReferenceResolver) getReferredPoliciesForNamespace(ctx context.Context, ns *corev1.Namespace, nsOld *corev1.Namespace) ([]networking.NetworkPolicy, error) { 15 | var referredPolicies []networking.NetworkPolicy 16 | for _, policyRef := range r.policyTracker.GetPoliciesWithNamespaceReferences().UnsortedList() { 17 | policy := &networking.NetworkPolicy{} 18 | if err := r.k8sClient.Get(ctx, policyRef, policy); err != nil { 19 | if client.IgnoreNotFound(err) != nil { 20 | return nil, errors.Wrap(err, "failed to get policies") 21 | } 22 | r.logger.Info("Tracked policy not found", "reference", policyRef) 23 | continue 24 | } 25 | if r.isNamespaceReferredInPolicy(ns, policy) { 26 | referredPolicies = append(referredPolicies, *policy) 27 | continue 28 | } 29 | if nsOld != nil && r.isNamespaceReferredInPolicy(nsOld, policy) { 30 | referredPolicies = append(referredPolicies, *policy) 31 | } 32 | } 33 | 34 | return referredPolicies, nil 35 | } 36 | 37 | func (r *defaultPolicyReferenceResolver) isNamespaceReferredInPolicy(ns *corev1.Namespace, policy *networking.NetworkPolicy) bool { 38 | for _, ingRule := range policy.Spec.Ingress { 39 | for _, peer := range ingRule.From { 40 | if r.isNameSpaceLabelMatchPeer(ns, &peer) { 41 | return true 42 | } 43 | } 44 | } 45 | for _, egrRule := range policy.Spec.Egress { 46 | for _, peer := range egrRule.To { 47 | if r.isNameSpaceLabelMatchPeer(ns, &peer) { 48 | return true 49 | } 50 | } 51 | } 52 | return false 53 | } 54 | 55 | func (r *defaultPolicyReferenceResolver) isNameSpaceLabelMatchPeer(ns *corev1.Namespace, peer *networking.NetworkPolicyPeer) bool { 56 | if peer.NamespaceSelector == nil { 57 | return false 58 | } 59 | nsSelector, err := metav1.LabelSelectorAsSelector(peer.NamespaceSelector) 60 | if err != nil { 61 | r.logger.Error(err, "unable to get namespace selector") 62 | return false 63 | } 64 | if nsSelector.Matches(labels.Set(ns.Labels)) { 65 | return true 66 | } 67 | return false 68 | } 69 | -------------------------------------------------------------------------------- /pkg/resolvers/policies_for_pod.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 7 | "github.com/pkg/errors" 8 | corev1 "k8s.io/api/core/v1" 9 | networking "k8s.io/api/networking/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/labels" 12 | "k8s.io/apimachinery/pkg/types" 13 | "k8s.io/apimachinery/pkg/util/sets" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | ) 16 | 17 | func (r *defaultPolicyReferenceResolver) getReferredPoliciesForPod(ctx context.Context, pod *corev1.Pod, podOld *corev1.Pod) ([]networking.NetworkPolicy, error) { 18 | policyList := &networking.NetworkPolicyList{} 19 | if err := r.k8sClient.List(ctx, policyList, client.InNamespace(pod.Namespace)); err != nil { 20 | return nil, errors.Wrap(err, "failed to fetch policies") 21 | } 22 | processedPolicies := sets.Set[types.NamespacedName]{} 23 | var referredPolicies []networking.NetworkPolicy 24 | for _, pol := range policyList.Items { 25 | if r.isPodMatchesPolicySelector(pod, podOld, &pol) { 26 | referredPolicies = append(referredPolicies, pol) 27 | processedPolicies.Insert(k8s.NamespacedName(&pol)) 28 | continue 29 | } 30 | if r.isPodReferredOnIngressEgress(ctx, pod, &pol) { 31 | referredPolicies = append(referredPolicies, pol) 32 | processedPolicies.Insert(k8s.NamespacedName(&pol)) 33 | continue 34 | } 35 | if podOld != nil && r.isPodReferredOnIngressEgress(ctx, podOld, &pol) { 36 | referredPolicies = append(referredPolicies, pol) 37 | processedPolicies.Insert(k8s.NamespacedName(&pol)) 38 | } 39 | } 40 | r.logger.V(1).Info("Policies referred on the same namespace", "pod", k8s.NamespacedName(pod), 41 | "policies", referredPolicies) 42 | 43 | for _, ref := range r.policyTracker.GetPoliciesWithNamespaceReferences().UnsortedList() { 44 | r.logger.V(1).Info("Policy containing namespace selectors", "ref", ref) 45 | if processedPolicies.Has(ref) { 46 | continue 47 | } 48 | policy := &networking.NetworkPolicy{} 49 | if err := r.k8sClient.Get(ctx, ref, policy); err != nil { 50 | if client.IgnoreNotFound(err) != nil { 51 | return nil, errors.Wrap(err, "failed to get policy") 52 | } 53 | r.logger.V(1).Info("Policy not found", "reference", ref) 54 | continue 55 | } 56 | 57 | if r.isPodReferredOnIngressEgress(ctx, pod, policy) { 58 | referredPolicies = append(referredPolicies, *policy) 59 | processedPolicies.Insert(k8s.NamespacedName(policy)) 60 | continue 61 | } 62 | if podOld != nil && r.isPodReferredOnIngressEgress(ctx, podOld, policy) { 63 | referredPolicies = append(referredPolicies, *policy) 64 | processedPolicies.Insert(k8s.NamespacedName(policy)) 65 | } 66 | } 67 | 68 | r.logger.V(1).Info("All referred policies", "pod", k8s.NamespacedName(pod), "policies", referredPolicies) 69 | return referredPolicies, nil 70 | } 71 | 72 | func (r *defaultPolicyReferenceResolver) isPodMatchesPolicySelector(pod *corev1.Pod, podOld *corev1.Pod, policy *networking.NetworkPolicy) bool { 73 | ps, err := metav1.LabelSelectorAsSelector(&policy.Spec.PodSelector) 74 | if err != nil { 75 | r.logger.Info("Unable to get pod label selector from policy", "policy", k8s.NamespacedName(policy), "err", err) 76 | return false 77 | } 78 | if ps.Matches(labels.Set(pod.Labels)) { 79 | return true 80 | } 81 | if podOld != nil && ps.Matches(labels.Set(podOld.Labels)) { 82 | return true 83 | } 84 | return false 85 | } 86 | 87 | func (r *defaultPolicyReferenceResolver) isPodReferredOnIngressEgress(ctx context.Context, pod *corev1.Pod, policy *networking.NetworkPolicy) bool { 88 | for _, ingRule := range policy.Spec.Ingress { 89 | for _, peer := range ingRule.From { 90 | if r.isPodLabelMatchPeer(ctx, pod, &peer, policy.Namespace) { 91 | return true 92 | } 93 | } 94 | } 95 | for _, egrRule := range policy.Spec.Egress { 96 | for _, peer := range egrRule.To { 97 | if r.isPodLabelMatchPeer(ctx, pod, &peer, policy.Namespace) { 98 | return true 99 | } 100 | } 101 | } 102 | return false 103 | } 104 | 105 | func (r *defaultPolicyReferenceResolver) isPodLabelMatchPeer(ctx context.Context, pod *corev1.Pod, peer *networking.NetworkPolicyPeer, policyNamespace string) bool { 106 | if peer.NamespaceSelector != nil { 107 | ns := &corev1.Namespace{} 108 | if err := r.k8sClient.Get(ctx, types.NamespacedName{Name: pod.Namespace}, ns); err != nil { 109 | r.logger.Info("Unable to get namespace", "ns", pod.Namespace, "err", err) 110 | return false 111 | } 112 | nsSelector, err := metav1.LabelSelectorAsSelector(peer.NamespaceSelector) 113 | if err != nil { 114 | r.logger.Info("Unable to get namespace selector", "selector", peer.NamespaceSelector, "err", err) 115 | return false 116 | } 117 | if !nsSelector.Matches(labels.Set(ns.Labels)) { 118 | r.logger.V(1).Info("nsSelector does not match ns labels", "selector", nsSelector, 119 | "ns", ns) 120 | return false 121 | } 122 | 123 | if peer.PodSelector == nil { 124 | r.logger.V(1).Info("nsSelector matches ns labels", "selector", nsSelector, 125 | "ns", ns) 126 | return true 127 | } 128 | } else if pod.Namespace != policyNamespace { 129 | r.logger.V(1).Info("Pod and policy namespace mismatch", "pod", k8s.NamespacedName(pod), 130 | "policy ns", policyNamespace) 131 | return false 132 | } 133 | podSelector, err := metav1.LabelSelectorAsSelector(peer.PodSelector) 134 | if err != nil { 135 | r.logger.Info("Unable to get pod selector", "err", err) 136 | return false 137 | } 138 | if podSelector.Matches(labels.Set(pod.Labels)) { 139 | return true 140 | } 141 | return false 142 | } 143 | -------------------------------------------------------------------------------- /pkg/resolvers/policies_for_service.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 7 | "github.com/pkg/errors" 8 | corev1 "k8s.io/api/core/v1" 9 | networking "k8s.io/api/networking/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/labels" 12 | "k8s.io/apimachinery/pkg/types" 13 | "k8s.io/apimachinery/pkg/util/sets" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | ) 16 | 17 | // getReferredPoliciesForService returns the list of policies that refer to the service. 18 | func (r *defaultPolicyReferenceResolver) getReferredPoliciesForService(ctx context.Context, svc, svcOld *corev1.Service) ([]networking.NetworkPolicy, error) { 19 | if k8s.IsServiceHeadless(svc) { 20 | r.logger.Info("Ignoring headless service", "svc", k8s.NamespacedName(svc)) 21 | return nil, nil 22 | } 23 | policiesWithEgressRules := r.policyTracker.GetPoliciesWithEgressRules() 24 | potentialMatches := sets.Set[types.NamespacedName]{} 25 | for pol := range policiesWithEgressRules { 26 | if pol.Namespace == svc.Namespace { 27 | potentialMatches.Insert(pol) 28 | } 29 | } 30 | namespacedPoliciesSet := r.policyTracker.GetPoliciesWithNamespaceReferences() 31 | potentialMatches = potentialMatches.Union(policiesWithEgressRules.Intersection(namespacedPoliciesSet)) 32 | r.logger.V(1).Info("Potential matches", "policies", potentialMatches.UnsortedList(), "svc", k8s.NamespacedName(svc)) 33 | var networkPolicyList []networking.NetworkPolicy 34 | for policyRef := range potentialMatches { 35 | r.logger.V(1).Info("Checking policy", "reference", policyRef) 36 | policy := &networking.NetworkPolicy{} 37 | if err := r.k8sClient.Get(ctx, policyRef, policy); err != nil { 38 | if client.IgnoreNotFound(err) != nil { 39 | return nil, errors.Wrap(err, "failed to get policy") 40 | } 41 | r.logger.V(1).Info("Policy not found", "reference", policyRef) 42 | continue 43 | } 44 | if r.isServiceReferredOnEgress(ctx, svc, policy) { 45 | networkPolicyList = append(networkPolicyList, *policy) 46 | continue 47 | } 48 | if svcOld != nil && r.isServiceReferredOnEgress(ctx, svcOld, policy) { 49 | networkPolicyList = append(networkPolicyList, *policy) 50 | } 51 | 52 | } 53 | return networkPolicyList, nil 54 | } 55 | 56 | // isServiceReferredOnEgress returns true if the service is referred in the policy 57 | func (r *defaultPolicyReferenceResolver) isServiceReferredOnEgress(ctx context.Context, svc *corev1.Service, policy *networking.NetworkPolicy) bool { 58 | for _, egressRule := range policy.Spec.Egress { 59 | for _, peer := range egressRule.To { 60 | r.logger.V(1).Info("Checking peer for service reference on egress", "peer", peer) 61 | if peer.PodSelector != nil || peer.NamespaceSelector != nil { 62 | if r.isServiceMatchLabelSelector(ctx, svc, &peer, policy.Namespace) { 63 | return true 64 | } 65 | } 66 | } 67 | } 68 | return false 69 | } 70 | 71 | // isServiceMatchLabelSelector returns true if the service is referred in the list of peers 72 | func (r *defaultPolicyReferenceResolver) isServiceMatchLabelSelector(ctx context.Context, svc *corev1.Service, peer *networking.NetworkPolicyPeer, policyNamespace string) bool { 73 | if peer.NamespaceSelector != nil { 74 | ns := &corev1.Namespace{} 75 | if err := r.k8sClient.Get(ctx, types.NamespacedName{Name: svc.Namespace}, ns); err != nil { 76 | r.logger.Info("Failed to get namespace", "namespace", svc.Namespace, "err", err) 77 | return false 78 | } 79 | nsSelector, err := metav1.LabelSelectorAsSelector(peer.NamespaceSelector) 80 | if err != nil { 81 | r.logger.Info("Failed to convert namespace selector to selector", "namespace", peer.NamespaceSelector, "err", err) 82 | return false 83 | } 84 | if !nsSelector.Matches(labels.Set(ns.Labels)) { 85 | return false 86 | } 87 | if peer.PodSelector == nil { 88 | return true 89 | } 90 | } else if svc.Namespace != policyNamespace { 91 | r.logger.V(1).Info("Svc and policy namespace does not match", "namespace", svc.Namespace) 92 | return false 93 | } 94 | if svc.Spec.Selector == nil { 95 | r.logger.V(1).Info("Ignoring service without selector", "service", k8s.NamespacedName(svc)) 96 | return false 97 | } 98 | svcSelector, err := metav1.LabelSelectorAsSelector(peer.PodSelector) 99 | if err != nil { 100 | r.logger.Info("Failed to convert pod selector to selector", "podSelector", peer.PodSelector, "err", err) 101 | return false 102 | } 103 | if svcSelector.Matches(labels.Set(svc.Spec.Selector)) { 104 | return true 105 | } 106 | return false 107 | } 108 | -------------------------------------------------------------------------------- /pkg/resolvers/policy_tracker.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/aws/amazon-network-policy-controller-k8s/pkg/k8s" 7 | "github.com/go-logr/logr" 8 | networking "k8s.io/api/networking/v1" 9 | "k8s.io/apimachinery/pkg/types" 10 | "k8s.io/apimachinery/pkg/util/sets" 11 | ) 12 | 13 | type PolicyTracker interface { 14 | UpdatePolicy(policy *networking.NetworkPolicy) 15 | RemovePolicy(policy *networking.NetworkPolicy) 16 | GetPoliciesWithNamespaceReferences() sets.Set[types.NamespacedName] 17 | GetPoliciesWithEgressRules() sets.Set[types.NamespacedName] 18 | } 19 | 20 | func NewPolicyTracker(logger logr.Logger) PolicyTracker { 21 | return &defaultPolicyTracker{} 22 | } 23 | 24 | var _ PolicyTracker = (*defaultPolicyTracker)(nil) 25 | 26 | type defaultPolicyTracker struct { 27 | logger logr.Logger 28 | namespacedPolicies sync.Map 29 | egressRulesPolicies sync.Map 30 | } 31 | 32 | // UpdatePolicy updates the policy tracker with the given policy 33 | func (t *defaultPolicyTracker) UpdatePolicy(policy *networking.NetworkPolicy) { 34 | if t.containsNamespaceReference(policy) { 35 | t.logger.V(1).Info("policy contains ns references", "policy", k8s.NamespacedName(policy)) 36 | t.namespacedPolicies.Store(k8s.NamespacedName(policy), true) 37 | } else { 38 | t.logger.V(1).Info("no ns references, remove tracking", "policy", k8s.NamespacedName(policy)) 39 | t.namespacedPolicies.Delete(k8s.NamespacedName(policy)) 40 | } 41 | if t.containsEgressRules(policy) { 42 | t.logger.V(1).Info("policy contains egress rules", "policy", k8s.NamespacedName(policy)) 43 | t.egressRulesPolicies.Store(k8s.NamespacedName(policy), true) 44 | } else { 45 | t.logger.V(1).Info("no egress rules, remove tracking", "policy", k8s.NamespacedName(policy)) 46 | t.egressRulesPolicies.Delete(k8s.NamespacedName(policy)) 47 | } 48 | } 49 | 50 | // RemovePolicy removes the given policy from the policy tracker during deletion 51 | func (t *defaultPolicyTracker) RemovePolicy(policy *networking.NetworkPolicy) { 52 | t.logger.V(1).Info("remove from tracking", "policy", k8s.NamespacedName(policy)) 53 | t.namespacedPolicies.Delete(k8s.NamespacedName(policy)) 54 | t.egressRulesPolicies.Delete(k8s.NamespacedName(policy)) 55 | } 56 | 57 | // GetPoliciesWithNamespaceReferences returns the set of policies that have namespace references in the ingress/egress rules 58 | func (t *defaultPolicyTracker) GetPoliciesWithNamespaceReferences() sets.Set[types.NamespacedName] { 59 | policies := sets.Set[types.NamespacedName]{} 60 | t.namespacedPolicies.Range(func(k, _ interface{}) bool { 61 | policies.Insert(k.(types.NamespacedName)) 62 | return true 63 | }) 64 | return policies 65 | } 66 | 67 | // GetPoliciesWithEgressRules returns the set of policies that have egress rules 68 | func (t *defaultPolicyTracker) GetPoliciesWithEgressRules() sets.Set[types.NamespacedName] { 69 | policies := sets.Set[types.NamespacedName]{} 70 | t.egressRulesPolicies.Range(func(k, _ interface{}) bool { 71 | policies.Insert(k.(types.NamespacedName)) 72 | return true 73 | }) 74 | return policies 75 | } 76 | 77 | func (t *defaultPolicyTracker) containsNamespaceReference(policy *networking.NetworkPolicy) bool { 78 | for _, ingRule := range policy.Spec.Ingress { 79 | for _, peer := range ingRule.From { 80 | if peer.NamespaceSelector != nil { 81 | return true 82 | } 83 | } 84 | } 85 | for _, egrRule := range policy.Spec.Egress { 86 | for _, peer := range egrRule.To { 87 | if peer.NamespaceSelector != nil { 88 | return true 89 | } 90 | } 91 | } 92 | return false 93 | } 94 | 95 | func (t *defaultPolicyTracker) containsEgressRules(policy *networking.NetworkPolicy) bool { 96 | return len(policy.Spec.Egress) > 0 97 | } 98 | -------------------------------------------------------------------------------- /pkg/resolvers/policy_tracker_test.go: -------------------------------------------------------------------------------- 1 | package resolvers 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | 7 | "github.com/go-logr/logr" 8 | "github.com/stretchr/testify/assert" 9 | networking "k8s.io/api/networking/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/types" 12 | "sigs.k8s.io/controller-runtime/pkg/log" 13 | ) 14 | 15 | func TestDefaultPolicyTracker_UpdatePolicy(t *testing.T) { 16 | tests := []struct { 17 | name string 18 | policies []networking.NetworkPolicy 19 | wantNsList []types.NamespacedName 20 | wantEgressList []types.NamespacedName 21 | }{ 22 | { 23 | name: "no namespace selector", 24 | policies: []networking.NetworkPolicy{ 25 | { 26 | ObjectMeta: metav1.ObjectMeta{ 27 | Name: "no-ns-selector", 28 | }, 29 | }, 30 | }, 31 | wantEgressList: []types.NamespacedName{}, 32 | wantNsList: []types.NamespacedName{}, 33 | }, 34 | { 35 | name: "ns selector in ingress", 36 | policies: []networking.NetworkPolicy{ 37 | { 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: "ingress-selector", 40 | Namespace: "app", 41 | }, 42 | Spec: networking.NetworkPolicySpec{ 43 | Ingress: []networking.NetworkPolicyIngressRule{ 44 | { 45 | From: []networking.NetworkPolicyPeer{ 46 | { 47 | NamespaceSelector: &metav1.LabelSelector{}, 48 | }, 49 | }, 50 | }, 51 | }, 52 | }, 53 | }, 54 | }, 55 | wantNsList: []types.NamespacedName{ 56 | { 57 | Namespace: "app", 58 | Name: "ingress-selector", 59 | }, 60 | }, 61 | wantEgressList: []types.NamespacedName{}, 62 | }, 63 | { 64 | name: "ns selector in egress", 65 | policies: []networking.NetworkPolicy{ 66 | { 67 | ObjectMeta: metav1.ObjectMeta{ 68 | Name: "egress-selector", 69 | Namespace: "egress", 70 | }, 71 | Spec: networking.NetworkPolicySpec{ 72 | Egress: []networking.NetworkPolicyEgressRule{ 73 | { 74 | To: []networking.NetworkPolicyPeer{ 75 | { 76 | NamespaceSelector: &metav1.LabelSelector{}, 77 | }, 78 | }, 79 | }, 80 | }, 81 | }, 82 | }, 83 | }, 84 | wantNsList: []types.NamespacedName{ 85 | { 86 | Namespace: "egress", 87 | Name: "egress-selector", 88 | }, 89 | }, 90 | wantEgressList: []types.NamespacedName{ 91 | { 92 | Namespace: "egress", 93 | Name: "egress-selector", 94 | }, 95 | }, 96 | }, 97 | { 98 | name: "multiple entries", 99 | policies: []networking.NetworkPolicy{ 100 | { 101 | ObjectMeta: metav1.ObjectMeta{ 102 | Name: "no-ns-selector", 103 | }, 104 | }, 105 | { 106 | ObjectMeta: metav1.ObjectMeta{ 107 | Name: "ingress-selector", 108 | Namespace: "ing", 109 | }, 110 | Spec: networking.NetworkPolicySpec{ 111 | Ingress: []networking.NetworkPolicyIngressRule{ 112 | { 113 | From: []networking.NetworkPolicyPeer{ 114 | { 115 | NamespaceSelector: &metav1.LabelSelector{}, 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | { 123 | ObjectMeta: metav1.ObjectMeta{ 124 | Name: "egress-selector", 125 | }, 126 | Spec: networking.NetworkPolicySpec{ 127 | Egress: []networking.NetworkPolicyEgressRule{ 128 | { 129 | To: []networking.NetworkPolicyPeer{ 130 | { 131 | NamespaceSelector: &metav1.LabelSelector{}, 132 | }, 133 | }, 134 | }, 135 | }, 136 | }, 137 | }, 138 | }, 139 | wantNsList: []types.NamespacedName{ 140 | { 141 | Namespace: "ing", 142 | Name: "ingress-selector", 143 | }, 144 | { 145 | Name: "egress-selector", 146 | }, 147 | }, 148 | wantEgressList: []types.NamespacedName{ 149 | { 150 | Name: "egress-selector", 151 | }, 152 | }, 153 | }, 154 | } 155 | for _, tt := range tests { 156 | t.Run(tt.name, func(t *testing.T) { 157 | policyTracker := &defaultPolicyTracker{ 158 | logger: logr.New(&log.NullLogSink{}), 159 | } 160 | for _, policy := range tt.policies { 161 | policyTracker.UpdatePolicy(&policy) 162 | } 163 | gotNsList := policyTracker.GetPoliciesWithNamespaceReferences().UnsortedList() 164 | gotEgressList := policyTracker.GetPoliciesWithEgressRules().UnsortedList() 165 | for _, lst := range [][]types.NamespacedName{gotNsList, gotEgressList, tt.wantNsList, tt.wantEgressList} { 166 | sort.Slice(lst, func(i, j int) bool { 167 | return lst[i].String() < lst[j].String() 168 | }) 169 | } 170 | assert.Equal(t, tt.wantNsList, gotNsList) 171 | assert.Equal(t, tt.wantEgressList, gotEgressList) 172 | }) 173 | } 174 | } 175 | 176 | func TestDefaultPolicyTracker_RemovePolicy(t *testing.T) { 177 | tests := []struct { 178 | name string 179 | existingNsRefPolicies []types.NamespacedName 180 | existingEgressPolicies []types.NamespacedName 181 | policies []networking.NetworkPolicy 182 | wantNsList []types.NamespacedName 183 | wantEgressList []types.NamespacedName 184 | }{ 185 | { 186 | name: "existing empty", 187 | policies: []networking.NetworkPolicy{ 188 | { 189 | ObjectMeta: metav1.ObjectMeta{ 190 | Name: "egress-selector", 191 | }, 192 | }, 193 | }, 194 | wantNsList: []types.NamespacedName{}, 195 | wantEgressList: []types.NamespacedName{}, 196 | }, 197 | { 198 | name: "non tracked item delete", 199 | existingNsRefPolicies: []types.NamespacedName{ 200 | { 201 | Namespace: "awesome", 202 | Name: "app", 203 | }, 204 | }, 205 | existingEgressPolicies: []types.NamespacedName{ 206 | { 207 | Namespace: "awesome", 208 | Name: "egress", 209 | }, 210 | }, 211 | policies: []networking.NetworkPolicy{ 212 | { 213 | ObjectMeta: metav1.ObjectMeta{ 214 | Name: "egress-selector", 215 | }, 216 | }, 217 | }, 218 | wantNsList: []types.NamespacedName{ 219 | { 220 | Namespace: "awesome", 221 | Name: "app", 222 | }, 223 | }, 224 | wantEgressList: []types.NamespacedName{ 225 | { 226 | Namespace: "awesome", 227 | Name: "egress", 228 | }, 229 | }, 230 | }, 231 | { 232 | name: "tracked item delete", 233 | existingNsRefPolicies: []types.NamespacedName{ 234 | { 235 | Namespace: "awesome", 236 | Name: "app", 237 | }, 238 | { 239 | Namespace: "ing", 240 | Name: "policy", 241 | }, 242 | { 243 | Name: "egr", 244 | }, 245 | }, 246 | existingEgressPolicies: []types.NamespacedName{ 247 | { 248 | Name: "egr", 249 | }, 250 | { 251 | Name: "app", 252 | }, 253 | }, 254 | policies: []networking.NetworkPolicy{ 255 | { 256 | ObjectMeta: metav1.ObjectMeta{ 257 | Name: "app", 258 | }, 259 | }, 260 | { 261 | ObjectMeta: metav1.ObjectMeta{ 262 | Namespace: "ing", 263 | Name: "policy", 264 | }, 265 | }, 266 | }, 267 | wantNsList: []types.NamespacedName{ 268 | { 269 | Name: "egr", 270 | }, 271 | { 272 | Namespace: "awesome", 273 | Name: "app", 274 | }, 275 | }, 276 | wantEgressList: []types.NamespacedName{ 277 | { 278 | Name: "egr", 279 | }, 280 | }, 281 | }, 282 | } 283 | for _, tt := range tests { 284 | t.Run(tt.name, func(t *testing.T) { 285 | policyTracker := &defaultPolicyTracker{ 286 | logger: logr.New(&log.NullLogSink{}), 287 | } 288 | for _, entry := range tt.existingNsRefPolicies { 289 | policyTracker.namespacedPolicies.Store(entry, true) 290 | } 291 | for _, entry := range tt.existingEgressPolicies { 292 | policyTracker.egressRulesPolicies.Store(entry, true) 293 | } 294 | for _, policy := range tt.policies { 295 | policyTracker.RemovePolicy(&policy) 296 | } 297 | gotNsList := policyTracker.GetPoliciesWithNamespaceReferences().UnsortedList() 298 | gotEgressList := policyTracker.GetPoliciesWithEgressRules().UnsortedList() 299 | for _, lst := range [][]types.NamespacedName{gotNsList, gotEgressList, tt.wantNsList, tt.wantEgressList} { 300 | sort.Slice(lst, func(i, j int) bool { 301 | return lst[i].String() < lst[j].String() 302 | }) 303 | } 304 | assert.Equal(t, tt.wantNsList, gotNsList) 305 | assert.Equal(t, tt.wantEgressList, gotEgressList) 306 | }) 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /pkg/utils/configmap/configmap.go: -------------------------------------------------------------------------------- 1 | package configmap 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | "k8s.io/apimachinery/pkg/types" 6 | ) 7 | 8 | const ( 9 | controllerConfigMapKey = "enable-network-policy-controller" 10 | controllerConfigEnabledValue = "true" 11 | ) 12 | 13 | // GetControllerConfigMapId returns the id for the configmap resource containing the controller config 14 | func GetControllerConfigMapId() types.NamespacedName { 15 | return types.NamespacedName{ 16 | Namespace: "kube-system", 17 | Name: "amazon-vpc-cni", 18 | } 19 | } 20 | 21 | // GetConfigmapCheckFn returns a function that checks if controller is enabled in the configmap 22 | func GetConfigmapCheckFn() func(configMap *corev1.ConfigMap) bool { 23 | return func(configMap *corev1.ConfigMap) bool { 24 | return configMap.Data[controllerConfigMapKey] == controllerConfigEnabledValue 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/version/version_info.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | GitVersion string 5 | GitCommit string 6 | BuildDate string 7 | ) 8 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | ## Scripts 2 | 3 | This package contains shell scripts and libraries used for running e2e tests and some helper scripts. 4 | 5 | `run-cyclonus-tests.sh` Runs cyclonus tests against an existing cluster and validates the output 6 | `update-controller-image-dataplane.sh` Deploys the `amazon-network-policy-controller-k8s` controller on Dataplane 7 | 8 | ### Cyclonus tests 9 | `run-cyclonus-tests.sh` script runs the cyclonus suite against an existing cluster. It provides the option of disabling the control plane `amazon-network-policy-controller-k8s` controller if you are deploying a custom/dev version of the controller installed on dataplane. The script also skips CNI installation if `SKIP_CNI_INSTALLATION` environment variable is set. 10 | Use `make run-cyclonus-test` to run this script 11 | 12 | ### Deploy Controller on Dataplane 13 | `update-controller-image-dataplane.sh` script helps in installing the Dataplane manifests for `amazon-network-policy-controller-k8s` controller. It provides the option to run a custom/dev image of the controller on dataplane if you set `AMAZON_NP_CONTROLLER` to the image URI. 14 | Use `make deploy-controller-on-dataplane` action to run this script or `make deploy-and-test` to use run this script and cyclonus suite. 15 | -------------------------------------------------------------------------------- /scripts/deploy-controller-on-dataplane.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Use this script to deploy the amazon-np-controller deployment on Dataplane nodes 3 | 4 | # Parameters: 5 | # KUBECONFIG: path to the kubeconfig file, default ~/.kube/config 6 | # NP_CONTROLLER_IMAGE: Custom network policy controller image 7 | # NP_CONTROLLER_ENDPOINT_CHUNK_SIZE: The number of endpoints in policy endpoint resource 8 | 9 | set -e 10 | DIR=$(cd "$(dirname "$0")"; pwd) 11 | source ${DIR}/lib/network-policy.sh 12 | 13 | echo "Ensuring Control Plane Controller is disabled" 14 | disable_cp_network_policy_controller 15 | 16 | HELM_NPC_ARGS="" 17 | if [[ ! -z $NP_CONTROLLER_IMAGE ]]; then 18 | HELM_NPC_ARGS+=" --set image.repository=$NP_CONTROLLER_IMAGE" 19 | fi 20 | 21 | if [[ ! -z $NP_CONTROLLER_ENDPOINT_CHUNK_SIZE ]]; then 22 | HELM_NPC_ARGS+=" --set endpointChunkSize=$NP_CONTROLLER_ENDPOINT_CHUNK_SIZE" 23 | fi 24 | 25 | echo "Deploy the default Amazon Network Policy Controller on Dataplane" 26 | helm template amazon-network-policy-controller-k8s ${DIR}/../charts/amazon-network-policy-controller-k8s \ 27 | --set enableConfigMapCheck=false $HELM_NPC_ARGS | kubectl apply -f - 28 | 29 | echo "Restarting the Controller" 30 | kubectl rollout restart deployment.v1.apps/amazon-network-policy-controller-k8s -n kube-system 31 | 32 | echo "Ensuring Controller is Running on Dataplane" 33 | kubectl rollout status deployment.v1.apps/amazon-network-policy-controller-k8s -n kube-system --timeout=2m || (echo "Amazon Network Policy controller is unhealthy" && exit 1) 34 | -------------------------------------------------------------------------------- /scripts/gen_mocks.sh: -------------------------------------------------------------------------------- 1 | # Note: Put all external mocks in the ./mock directory 2 | # The mocks specific to this project go alongside the original package 3 | 4 | MOCKGEN=${MOCKGEN:-~/go/bin/mockgen} 5 | 6 | $MOCKGEN -package=mock_client -destination=./mocks/controller-runtime/client/client_mocks.go sigs.k8s.io/controller-runtime/pkg/client Client 7 | -------------------------------------------------------------------------------- /scripts/lib/network-policy.sh: -------------------------------------------------------------------------------- 1 | 2 | function load_addon_details() { 3 | 4 | ADDON_NAME="vpc-cni" 5 | echo "loading $ADDON_NAME addon details" 6 | LATEST_ADDON_VERSION=$(aws eks describe-addon-versions $ENDPOINT_FLAG --addon-name $ADDON_NAME --kubernetes-version $K8S_VERSION | jq '.addons[0].addonVersions[0].addonVersion' -r) 7 | EXISTING_SERVICE_ACCOUNT_ROLE_ARN=$(kubectl get serviceaccount -n kube-system aws-node -o json | jq '.metadata.annotations."eks.amazonaws.com/role-arn"' -r) 8 | } 9 | 10 | function wait_for_addon_status() { 11 | local expected_status=$1 12 | local retry_attempt=0 13 | if [ "$expected_status" = "DELETED" ]; then 14 | while $(aws eks describe-addon $ENDPOINT_FLAG --cluster-name $CLUSTER_NAME --addon-name $ADDON_NAME --region $REGION >> /dev/null); do 15 | if [ $retry_attempt -ge 30 ]; then 16 | echo "failed to delete addon, qutting after too many attempts" 17 | exit 1 18 | fi 19 | echo "addon is still not deleted" 20 | sleep 5 21 | ((retry_attempt=retry_attempt+1)) 22 | done 23 | echo "addon deleted" 24 | 25 | sleep 10 26 | return 27 | fi 28 | 29 | retry_attempt=0 30 | while true 31 | do 32 | STATUS=$(aws eks describe-addon $ENDPOINT_FLAG --cluster-name "$CLUSTER_NAME" --addon-name $ADDON_NAME --region "$REGION" | jq -r '.addon.status') 33 | if [ "$STATUS" = "$expected_status" ]; then 34 | echo "addon status matches expected status" 35 | return 36 | fi 37 | 38 | if [ $retry_attempt -ge 30 ]; then 39 | echo "failed to get desired add-on status: $STATUS, qutting after too many attempts" 40 | exit 1 41 | fi 42 | echo "addon status is not equal to $expected_status" 43 | sleep 10 44 | ((retry_attempt=retry_attempt+1)) 45 | done 46 | } 47 | 48 | function install_network_policy_mao() { 49 | 50 | local addon_version=$1 51 | if DESCRIBE_ADDON=$(aws eks describe-addon $ENDPOINT_FLAG --cluster-name $CLUSTER_NAME --addon-name $ADDON_NAME --region $REGION); then 52 | local current_addon_version=$(echo "$DESCRIBE_ADDON" | jq '.addon.addonVersion' -r) 53 | echo "deleting the $current_addon_version" 54 | aws eks delete-addon $ENDPOINT_FLAG --cluster-name $CLUSTER_NAME --addon-name $ADDON_NAME --region $REGION 55 | wait_for_addon_status "DELETED" 56 | fi 57 | 58 | echo "Installing addon $addon_version with network policy enabled" 59 | 60 | if [ "$EXISTING_SERVICE_ACCOUNT_ROLE_ARN" != "null" ]; then 61 | SA_ROLE_ARN_ARG="--service-account-role-arn $EXISTING_SERVICE_ACCOUNT_ROLE_ARN" 62 | fi 63 | 64 | aws eks create-addon \ 65 | --cluster-name $CLUSTER_NAME \ 66 | --addon-name $ADDON_NAME \ 67 | --configuration-value '{"enableNetworkPolicy": "true"}' \ 68 | --resolve-conflicts OVERWRITE \ 69 | --addon-version $addon_version \ 70 | --region $REGION $ENDPOINT_FLAG $SA_ROLE_ARN_ARG 71 | 72 | wait_for_addon_status "ACTIVE" 73 | } 74 | 75 | function install_network_policy_helm(){ 76 | 77 | helm repo add eks https://aws.github.io/eks-charts 78 | 79 | if [[ $IP_FAMILY == "IPv4" ]]; then 80 | ENABLE_IPv4=true 81 | ENABLE_IPv6=false 82 | ENABLE_PREFIX_DELEGATION=false 83 | else 84 | ENABLE_IPv4=false 85 | ENABLE_IPv6=true 86 | ENABLE_PREFIX_DELEGATION=true 87 | fi 88 | 89 | echo "Updating annotations and labels on existing resources" 90 | resources=("daemonSet/aws-node" "clusterRole/aws-node" "clusterRoleBinding/aws-node" "serviceAccount/aws-node" "configmap/amazon-vpc-cni") 91 | for kind in ${resources[@]}; do 92 | echo "setting annotations and labels on $kind" 93 | kubectl -n kube-system annotate --overwrite $kind meta.helm.sh/release-name=aws-vpc-cni meta.helm.sh/release-namespace=kube-system || echo "Unable to annotate $kind" 94 | kubectl -n kube-system label --overwrite $kind app.kubernetes.io/managed-by=Helm || echo "Unable to label $kind" 95 | done 96 | 97 | echo "Installing/Updating the aws-vpc-cni helm chart with enableNetworkPolicy=true" 98 | helm upgrade --install aws-vpc-cni eks/aws-vpc-cni --wait --timeout 300s \ 99 | --namespace kube-system \ 100 | --set enableNetworkPolicy=true \ 101 | --set originalMatchLabels=true \ 102 | --set init.env.ENABLE_IPv6=$ENABLE_IPv6 \ 103 | --set image.env.ENABLE_IPv6=$ENABLE_IPv6 \ 104 | --set nodeAgent.enableIpv6=$ENABLE_IPv6 \ 105 | --set image.env.ENABLE_PREFIX_DELEGATION=$ENABLE_PREFIX_DELEGATION \ 106 | --set image.env.ENABLE_IPv4=$ENABLE_IPv4 107 | } 108 | 109 | function disable_cp_network_policy_controller() { 110 | 111 | if kubectl get configmap amazon-vpc-cni -n kube-system > /dev/null; then 112 | echo "Disabling Network Policy Controller on Control Plane" 113 | kubectl patch configmap/amazon-vpc-cni -n kube-system --type merge -p '{"data":{"enable-network-policy-controller":"false"}}' 114 | fi 115 | 116 | } -------------------------------------------------------------------------------- /scripts/lib/tests.sh: -------------------------------------------------------------------------------- 1 | function run_cyclonus_tests(){ 2 | 3 | kubectl create ns netpol 4 | kubectl create clusterrolebinding cyclonus --clusterrole=cluster-admin --serviceaccount=netpol:cyclonus 5 | kubectl create sa cyclonus -n netpol 6 | kubectl apply -f ${DIR}/test/cyclonus-config.yaml -n netpol 7 | 8 | echo "Executing cyclonus suite" 9 | kubectl wait --for=condition=complete --timeout=240m -n netpol job.batch/cyclonus || TEST_FAILED=true 10 | kubectl logs -n netpol job/cyclonus > ${DIR}/results.log 11 | 12 | # Cleanup after test finishes 13 | kubectl delete clusterrolebinding cyclonus 14 | kubectl delete ns netpol x y z 15 | 16 | cat ${DIR}/results.log 17 | 18 | echo "Verify results against expected" 19 | python3 ${DIR}/lib/verify_test_results.py -f ${DIR}/results.log -ip $IP_FAMILY || TEST_FAILED=true 20 | } 21 | -------------------------------------------------------------------------------- /scripts/lib/verify_test_results.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("-f", "--file-name",default="", dest="file_name",help="Cyclonus results log file") 8 | parser.add_argument("-ip", "--ip-family",default="IPv4", dest="ip_family",help="IP Family of the cluster") 9 | args = parser.parse_args() 10 | 11 | # Cyclonus runs 112 test cases in total with each having some steps. Each step runs 81 probes in total across TCP, UDP and SCTP protocol 12 | # AWS Network Policy doesn't support all these combinations. We maintain a mapping of the test number and the number of 13 | # probes that are expected to pass on each testcase+step combination for IPv4 and IPv6 cluster. 14 | # For the test numbers not included in this map, it is expected that all the probes should be passing 15 | if args.ip_family == "IPv6": 16 | expected_results={ 2:{'Step 1': 80}, 3:{'Step 1': 80}, 8:{'Step 1': 80}, 12:{'Step 1': 64}, 23:{'Step 1': 80}, 25:{'Step 1': 80}, 26:{'Step 1': 80}, 28:{'Step 1': 80}, 29:{'Step 1': 80}, 31:{'Step 1': 50}, 32:{'Step 1': 64}, 98:{'Step 1': 79}, 102:{'Step 1': 71}, 104:{'Step 1': 71}, 106:{'Step 1': 71}, 108:{'Step 1': 71}, 111:{'Step 1': 79}, 112:{'Step 1': 80} } 17 | else: 18 | expected_results={ 2:{'Step 1': 80}, 3:{'Step 1': 80}, 8:{'Step 1': 80}, 12:{'Step 1': 80}, 23:{'Step 1': 80}, 25:{'Step 1': 80}, 26:{'Step 1': 80}, 28:{'Step 1': 80}, 29:{'Step 1': 80}, 31:{'Step 1': 50}, 32:{'Step 1': 64}, 98:{'Step 1': 80}, 111:{'Step 1': 80}, 112:{'Step 1': 80}} 19 | 20 | results = capture_results(args.file_name) 21 | verify_results(results,expected_results) 22 | 23 | def capture_results(file_name): 24 | results = {} 25 | rowbreak = False 26 | start_capture = False 27 | test_number = 0 28 | with open(file_name, 'r') as filedata: 29 | for data in filedata: 30 | if start_capture: 31 | if len(data.strip()) == 0: 32 | break 33 | elif data.startswith("+---"): 34 | rowbreak = True 35 | else: 36 | keys = [x.strip() for x in data.split('|')] 37 | print(keys) 38 | if keys[1] == "TEST": 39 | continue 40 | elif rowbreak: 41 | if keys[2] in ["passed", "failed"]: 42 | test_number = int(keys[1].split(":")[0]) 43 | results[test_number] = {} 44 | else: 45 | # Capture all retries for a testcase+step combination to verify 46 | step = keys[3].split(",")[0] 47 | if step not in results[test_number]: 48 | results[test_number][step] = [] 49 | results[test_number][step].append([int(keys[4]),int(keys[5]),int(keys[6])]) 50 | rowbreak = False 51 | else: 52 | continue 53 | elif "SummaryTable:" in data: 54 | start_capture = True 55 | else: 56 | continue 57 | return results 58 | 59 | 60 | def verify_results(results,expected_results): 61 | 62 | is_test_run_failed = False 63 | for test_number in results.keys(): 64 | for step in results[test_number].keys(): 65 | is_test_case_failed = True 66 | expected_correct = 0 67 | 68 | # Verifiying result from each retry for testcase+step 69 | for try_result in results[test_number][step]: 70 | count_failed, count_correct, count_ignored = try_result 71 | # Expected correct count by default for a testcase+step 72 | expected_correct = count_failed + count_correct + count_ignored 73 | 74 | if test_number in expected_results.keys(): 75 | if step in expected_results[test_number]: 76 | expected_correct = expected_results[test_number][step] 77 | 78 | # Check if the number of probes passed in testcase+step are as expected 79 | if count_correct >= expected_correct: 80 | print("Test Number:{test_number} | {step} | Passed -> Correct:{count_correct} Expected:{expected_correct}".format( 81 | test_number=test_number,step=step, 82 | count_correct=try_result[1],expected_correct=expected_correct 83 | )) 84 | is_test_case_failed = False 85 | break 86 | 87 | if is_test_case_failed: 88 | print("Test Number:{test_number} | {step} | Failed -> Try results: {probes} Expected:{expected_correct}".format( 89 | test_number=test_number,step=step, 90 | probes=results[test_number][step],expected_correct=expected_correct 91 | )) 92 | # Mark the entire test run as fail since atleast one test deviated from the expected results 93 | is_test_run_failed=True 94 | 95 | if is_test_run_failed or len(results) !=112: 96 | sys.exit(1) 97 | else: 98 | sys.exit(0) 99 | 100 | if __name__ == "__main__": 101 | main() 102 | -------------------------------------------------------------------------------- /scripts/run-cyclonus-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The script runs Network Policy Cyclonus tests on a existing cluster 4 | # Parameters: 5 | # CLUSTER_NAME: name of the cluster 6 | # KUBECONFIG: path to the kubeconfig file, default ~/.kube/config 7 | # REGION: defaults to us-west-2 8 | # IP_FAMILY: defaults to IPv4 9 | # ADDON_VERSION: Optional, defaults to the latest version 10 | # ENDPOINT: Optional 11 | 12 | set -euoE pipefail 13 | DIR=$(cd "$(dirname "$0")"; pwd) 14 | 15 | source ${DIR}/lib/network-policy.sh 16 | source ${DIR}/lib/tests.sh 17 | 18 | : "${ENDPOINT_FLAG:=""}" 19 | : "${ENDPOINT:=""}" 20 | : "${ADDON_VERSION:=""}" 21 | : "${IP_FAMILY:="IPv4"}" 22 | : "${REGION:="us-west-2"}" 23 | : "${SKIP_CNI_INSTALLATION:="false"}" 24 | : "${K8S_VERSION:=""}" 25 | : "${DISABLE_CP_NETWORK_POLICY_CONTROLLER="false"}" 26 | 27 | if [[ ! -z $ENDPOINT ]]; then 28 | ENDPOINT_FLAG="--endpoint-url $ENDPOINT" 29 | fi 30 | 31 | if [[ -z $K8S_VERSION ]]; then 32 | K8S_VERSION=$(aws eks describe-cluster $ENDPOINT_FLAG --name $CLUSTER_NAME --region $REGION | jq -r '.cluster.version') 33 | fi 34 | 35 | TEST_FAILED="false" 36 | 37 | echo "Running Cyclonus e2e tests with the following variables 38 | KUBECONFIG: $KUBECONFIG 39 | CLUSTER_NAME: $CLUSTER_NAME 40 | REGION: $REGION 41 | IP_FAMILY: $IP_FAMILY 42 | K8S_VERSION: $K8S_VERSION 43 | 44 | Optional args 45 | ENDPOINT: $ENDPOINT 46 | " 47 | 48 | if [[ $SKIP_CNI_INSTALLATION == "false" ]]; then 49 | install_network_policy_helm 50 | else 51 | echo "Skipping CNI installation. Make sure you have enabled network policy support in your cluster before executing the test" 52 | fi 53 | 54 | if [[ $DISABLE_CP_NETWORK_POLICY_CONTROLLER == "true" ]]; then 55 | echo "Disable CP Network Policy Controller on controller plane" 56 | disable_cp_network_policy_controller 57 | else 58 | echo "Skip disabling CP Network Policy controller. Tests will be evaulated against control plane NP controller" 59 | fi 60 | 61 | run_cyclonus_tests 62 | 63 | if [[ $TEST_FAILED == "true" ]]; then 64 | echo "Test run failed, check failures" 65 | exit 1 66 | fi 67 | -------------------------------------------------------------------------------- /scripts/test/cyclonus-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: cyclonus 5 | spec: 6 | backoffLimit: 0 7 | template: 8 | spec: 9 | restartPolicy: Never 10 | containers: 11 | - command: 12 | - ./cyclonus 13 | - generate 14 | - --retries=2 15 | name: cyclonus 16 | imagePullPolicy: Always 17 | image: mfenwick100/cyclonus:v0.5.4 18 | serviceAccount: cyclonus --------------------------------------------------------------------------------