├── .circleci └── config.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── manifests ├── decoratorcontroller.yaml └── deployment.yaml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | jobs: 4 | test: 5 | working_directory: ~/repo 6 | docker: 7 | - image: circleci/golang:1.11 8 | 9 | steps: 10 | - checkout 11 | 12 | - run: 13 | name: Run unit tests 14 | command: |- 15 | make install 16 | make test 17 | 18 | build: 19 | working_directory: ~/repo 20 | docker: 21 | - image: docker:17.05.0-ce-git 22 | 23 | steps: 24 | - checkout 25 | - setup_remote_docker: 26 | version: 17.05.0-ce 27 | 28 | - run: 29 | name: Build vigilant container image 30 | command: | 31 | apk update 32 | apk add --no-cache make 33 | make build-container 34 | 35 | workflows: 36 | version: 2 37 | test_and_build: 38 | jobs: 39 | - test 40 | - build: 41 | requires: 42 | - test 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11 as build 2 | ADD . /src 3 | WORKDIR /src 4 | RUN go get 5 | RUN go build -o vigilant 6 | 7 | # ------------------------------------------- 8 | 9 | FROM gcr.io/distroless/base 10 | COPY --from=build /src/vigilant /vigilant 11 | USER 10000:10000 12 | EXPOSE 8000 13 | ENTRYPOINT [ "/vigilant" ] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CONTAINER=vigilant 2 | VERSION=v0.1.0 3 | 4 | install: 5 | go get -v 6 | 7 | test: 8 | go test -cover 9 | 10 | build: 11 | go build -o vigilant 12 | 13 | build-container: 14 | docker build -t $(CONTAINER):$(VERSION) . 15 | 16 | run-container: 17 | docker run -d -p 8000:8000 --name $(CONTAINER) $(CONTAINER):$(VERSION) 18 | 19 | stop-container: 20 | docker rm -f $(CONTAINER) 21 | 22 | tag-container: 23 | docker tag $(CONTAINER):$(VERSION) bincyber/$(CONTAINER):$(VERSION) 24 | 25 | upload-container: 26 | docker push bincyber/$(CONTAINER):$(VERSION) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vigilant 2 | 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![Go](https://img.shields.io/badge/Go-1.11-blue.svg)](#) 5 | [![Version](https://images.microbadger.com/badges/version/bincyber/vigilant.svg)](https://microbadger.com/images/bincyber/vigilant) 6 | [![Layers](https://images.microbadger.com/badges/image/bincyber/vigilant.svg)](https://microbadger.com/images/bincyber/vigilant) 7 | [![CircleCI](https://circleci.com/gh/bincyber/vigilant.svg?style=svg)](https://circleci.com/gh/bincyber/vigilant) 8 | 9 | 10 | _vigilant_ is a Kubernetes security controller. 11 | 12 | ## What It Does 13 | 14 | _vigilant_ ensures the following for every Namespace in your Kubernetes cluster: 15 | 16 | 1. the Namespace has the label `name` 17 | 18 | ``` 19 | $ kubectl get namespaces --show-labels 20 | 21 | NAME STATUS AGE LABELS 22 | default Active 2m42s name=default 23 | kube-public Active 2m42s name=kube-public 24 | kube-system Active 2m42s name=kube-system 25 | ``` 26 | 27 | This faciliates the use of the `namespaceSelector` in [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) objects. 28 | 29 | For example, this NetworkPolicy can be applied without having to manually add the label `name=web-app` to the `web-app` Namespace: 30 | 31 | ``` 32 | --- 33 | apiVersion: networking.k8s.io/v1 34 | kind: NetworkPolicy 35 | metadata: 36 | name: allow-web-app 37 | namespace: pgsql 38 | spec: 39 | policyTypes: 40 | - Ingress 41 | podSelector: 42 | matchLabels: 43 | app: postgres-10 44 | ingress: 45 | - from: 46 | - namespaceSelector: 47 | matchLabels: 48 | name: web-app 49 | ``` 50 | 51 | 52 | 2. the Namespace has a default NetworkPolicy that denies all ingress and egress traffic: 53 | 54 | ``` 55 | --- 56 | apiVersion: networking.k8s.io/v1 57 | kind: NetworkPolicy 58 | metadata: 59 | name: default-deny-all 60 | namespace: example 61 | spec: 62 | policyTypes: 63 | - Ingress 64 | - Egress 65 | podSelector: {} 66 | ingress: [] 67 | egress: 68 | - ports: 69 | - port: 53 70 | protocol: TCP 71 | - port: 53 72 | protocol: UDP 73 | ``` 74 | 75 | This policy will apply to all Pods in the Namespace and only permit outbound DNS traffic. 76 | 77 | 78 | ## How It Works 79 | 80 | _vigilant_ is a [DecoratorController](https://metacontroller.app/api/decoratorcontroller/). 81 | 82 | It is registered with the [metacontroller](https://github.com/GoogleCloudPlatform/metacontroller) and watches for the creation of Namespace objects. When a new namespace is created, the metacontroller sends a POST request to _vigilant_ at its `/sync` endpoint. _vigilant_ returns the `name` label and NetworkPolicy to add to the namespace which is done by the metacontroller. 83 | 84 | ``` 85 | $ kubectl -n metacontroller logs metacontroller-0 --tail=12 86 | 87 | I0124 21:47:31.633272 1 controller.go:423] DecoratorController knsc: sync Namespace /kube-system 88 | I0124 21:47:31.636532 1 controller.go:423] DecoratorController knsc: sync Namespace /default 89 | I0124 21:47:31.638269 1 controller.go:423] DecoratorController knsc: sync Namespace /kube-public 90 | I0124 21:47:31.638274 1 controller.go:508] DecoratorController knsc: updating Namespace /kube-system 91 | I0124 21:47:31.642925 1 controller.go:508] DecoratorController knsc: updating Namespace /default 92 | I0124 21:47:31.644319 1 controller.go:508] DecoratorController knsc: updating Namespace /kube-public 93 | I0124 21:47:31.646605 1 manage_children.go:246] Namespace kube-system: creating NetworkPolicy kube-system/default-deny-all 94 | I0124 21:47:31.646648 1 manage_children.go:246] Namespace default: creating NetworkPolicy default/default-deny-all 95 | I0124 21:47:31.647014 1 manage_children.go:246] Namespace kube-public: creating NetworkPolicy kube-public/default-deny-all 96 | I0124 21:47:31.653321 1 controller.go:423] DecoratorController knsc: sync Namespace /metacontroller 97 | I0124 21:47:31.655149 1 controller.go:508] DecoratorController knsc: updating Namespace /metacontroller 98 | I0124 21:47:31.658274 1 manage_children.go:246] Namespace metacontroller: creating NetworkPolicy metacontroller/default-deny-all 99 | ``` 100 | 101 | ## Prerequisites 102 | 103 | _vigilant_ requires the [metacontroller](https://github.com/GoogleCloudPlatform/metacontroller) add-on running in your Kubernetes cluster. 104 | 105 | 106 | ## Usage 107 | 108 | Deploy _vigilant_: 109 | ``` 110 | $ kubectl apply -f https://raw.githubusercontent.com/bincyber/vigilant/master/manifests/deployment.yaml 111 | ``` 112 | 113 | Register the DecoratorController with the metacontroller: 114 | ``` 115 | $ kubectl apply -f https://github.com/bincyber/vigilant/blob/master/manifests/decoratorcontroller.yaml 116 | ``` 117 | 118 | Verify that namespaces have had a `name` label added to them: 119 | ``` 120 | $ kubectl get namespaces --show-labels 121 | ``` 122 | 123 | Verify that a NetworkPolicy has been added to each namespace: 124 | ``` 125 | $ kubectl get networkpolicy --all-namespaces 126 | ``` 127 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module mod 2 | 3 | require ( 4 | github.com/gogo/protobuf v1.2.0 // indirect 5 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect 6 | github.com/prometheus/client_golang v0.9.2 7 | github.com/stretchr/testify v1.3.0 8 | golang.org/x/text v0.3.0 // indirect 9 | gopkg.in/inf.v0 v0.9.1 // indirect 10 | k8s.io/api v0.0.0-20190206011303-7d75eb91fcfa 11 | k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467 12 | k8s.io/klog v0.1.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 2 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= 6 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 7 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 8 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 9 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 10 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 11 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 12 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= 16 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 17 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 18 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 19 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= 20 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 21 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= 22 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 25 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 26 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= 27 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 28 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 32 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 33 | k8s.io/api v0.0.0-20190206011303-7d75eb91fcfa h1:noSo1r58tAHCpFwEpq77cbPTs10F4onQH5mwH2xIeoM= 34 | k8s.io/api v0.0.0-20190206011303-7d75eb91fcfa/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 35 | k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467 h1:zmz9UYvvXrK/B8EDqFuqreJEaXbIWdzEkNgWrN/Cd3o= 36 | k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 37 | k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= 38 | k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 39 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | api "k8s.io/api/core/v1" 15 | networkingv1 "k8s.io/api/networking/v1" 16 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 | "k8s.io/apimachinery/pkg/util/intstr" 18 | ) 19 | 20 | type WebHookRequest struct { 21 | Namespace api.Namespace `json:"object"` 22 | } 23 | 24 | type WebHookResponse struct { 25 | Labels map[string]string `json:"labels"` 26 | Attachments []networkingv1.NetworkPolicy `json:"attachments"` 27 | } 28 | 29 | func healthEndpoint(w http.ResponseWriter, r *http.Request) { 30 | w.Write([]byte("ok")) 31 | } 32 | 33 | func syncEndpoint(w http.ResponseWriter, r *http.Request) { 34 | if r.Method == http.MethodPost { 35 | var webHookRequest WebHookRequest 36 | 37 | if err := json.NewDecoder(r.Body).Decode(&webHookRequest); err != nil { 38 | log.Println(err) 39 | w.WriteHeader(http.StatusBadRequest) 40 | return 41 | } 42 | 43 | namespace := webHookRequest.Namespace.ObjectMeta.Name 44 | 45 | if namespace == "" { 46 | w.WriteHeader(http.StatusBadRequest) 47 | return 48 | } 49 | 50 | protocolTCP := api.ProtocolTCP 51 | protocolUDP := api.ProtocolUDP 52 | 53 | webHookResponse := WebHookResponse{ 54 | Labels: map[string]string{ 55 | "name": namespace, 56 | }, 57 | Attachments: []networkingv1.NetworkPolicy{networkingv1.NetworkPolicy{ 58 | TypeMeta: metav1.TypeMeta{ 59 | APIVersion: "networking.k8s.io/v1", 60 | Kind: "NetworkPolicy", 61 | }, 62 | ObjectMeta: metav1.ObjectMeta{ 63 | Name: "default-deny-all", 64 | Namespace: namespace, 65 | }, 66 | Spec: networkingv1.NetworkPolicySpec{ 67 | PolicyTypes: []networkingv1.PolicyType{ 68 | networkingv1.PolicyTypeIngress, 69 | networkingv1.PolicyTypeEgress, 70 | }, 71 | Egress: []networkingv1.NetworkPolicyEgressRule{ 72 | networkingv1.NetworkPolicyEgressRule{ 73 | Ports: []networkingv1.NetworkPolicyPort{ 74 | { 75 | Protocol: &protocolTCP, 76 | Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 53}, 77 | }, 78 | { 79 | Protocol: &protocolUDP, 80 | Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 53}, 81 | }, 82 | }, 83 | }, 84 | }, 85 | }, 86 | }, 87 | }, 88 | } 89 | 90 | log.Printf("Added name label to namespace: %s\n", namespace) 91 | log.Printf("Added NetworkPolicy default-deny-all to namespace: %s\n", namespace) 92 | 93 | w.Header().Set("content-type", "application/json") 94 | w.WriteHeader(http.StatusOK) 95 | json.NewEncoder(w).Encode(webHookResponse) 96 | } else { 97 | w.WriteHeader(http.StatusMethodNotAllowed) 98 | } 99 | } 100 | 101 | func main() { 102 | address := "0.0.0.0:8000" 103 | 104 | http.Handle("/metrics", promhttp.Handler()) 105 | http.Handle("/health", http.HandlerFunc(healthEndpoint)) 106 | http.Handle("/sync", http.HandlerFunc(syncEndpoint)) 107 | 108 | log.Printf("Starting vigilant on %s", address) 109 | 110 | stop := make(chan os.Signal) 111 | signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) 112 | 113 | server := &http.Server{ 114 | Addr: address, 115 | } 116 | 117 | e := make(chan error) 118 | 119 | go func() { 120 | e <- server.ListenAndServe() 121 | }() 122 | 123 | select { 124 | case err := <-e: 125 | log.Fatalf("%v\n", err) 126 | case <-stop: 127 | } 128 | 129 | log.Printf("Received signal, gracefully shutting down") 130 | 131 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 132 | defer cancel() 133 | 134 | if err := server.Shutdown(ctx); err != nil { 135 | log.Fatalf("Failed to shutdown: %v\n", err) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | api "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | ) 15 | 16 | func TestHealthEndpoint(t *testing.T) { 17 | req, _ := http.NewRequest(http.MethodGet, "/health", nil) 18 | resp := httptest.NewRecorder() 19 | 20 | server := http.NewServeMux() 21 | server.Handle("/health", http.HandlerFunc(healthEndpoint)) 22 | 23 | server.ServeHTTP(resp, req) 24 | 25 | t.Run("/health returns HTTP 200", func(t *testing.T) { 26 | assert.Equal(t, resp.Code, http.StatusOK) 27 | }) 28 | 29 | t.Run("/health returns correct response body", func(t *testing.T) { 30 | assert.Equal(t, resp.Body.String(), "ok") 31 | }) 32 | } 33 | 34 | func TestSyncEndpoint(t *testing.T) { 35 | 36 | var testWebHookResponse WebHookResponse 37 | 38 | namespace := "test" 39 | 40 | testWebHookRequest := WebHookRequest{api.Namespace{ 41 | TypeMeta: metav1.TypeMeta{ 42 | APIVersion: "v1", 43 | Kind: "Namespace", 44 | }, 45 | ObjectMeta: metav1.ObjectMeta{ 46 | Name: namespace, 47 | }, 48 | }} 49 | 50 | body, _ := json.Marshal(testWebHookRequest) 51 | 52 | req, _ := http.NewRequest(http.MethodPost, "/sync", bytes.NewBuffer(body)) 53 | 54 | resp := httptest.NewRecorder() 55 | 56 | server := http.NewServeMux() 57 | server.Handle("/sync", http.HandlerFunc(syncEndpoint)) 58 | 59 | server.ServeHTTP(resp, req) 60 | 61 | t.Run("/sync returns HTTP 200", func(t *testing.T) { 62 | assert.Equal(t, resp.Code, http.StatusOK) 63 | }) 64 | 65 | t.Run("/sync returns JSON", func(t *testing.T) { 66 | err := json.NewDecoder(resp.Body).Decode(&testWebHookResponse) 67 | assert.Nil(t, err) 68 | }) 69 | 70 | t.Run("/sync returns correct label in response body", func(t *testing.T) { 71 | labels := map[string]string{ 72 | "name": namespace, 73 | } 74 | 75 | assert.Equal(t, labels, testWebHookResponse.Labels) 76 | }) 77 | 78 | t.Run("/sync returns NetworkPolicy attachment in response body", func(t *testing.T) { 79 | assert.Equal(t, len(testWebHookResponse.Attachments), 1) 80 | assert.Equal(t, testWebHookResponse.Attachments[0].APIVersion, "networking.k8s.io/v1") 81 | assert.Equal(t, testWebHookResponse.Attachments[0].Kind, "NetworkPolicy") 82 | assert.Equal(t, testWebHookResponse.Attachments[0].Name, "default-deny-all") 83 | assert.Equal(t, testWebHookResponse.Attachments[0].Namespace, namespace) 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /manifests/decoratorcontroller.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: metacontroller.k8s.io/v1alpha1 3 | kind: DecoratorController 4 | metadata: 5 | name: vigilant 6 | spec: 7 | resources: 8 | - apiVersion: v1 9 | resource: namespaces 10 | attachments: 11 | - apiVersion: networking.k8s.io/v1 12 | resource: networkpolicies 13 | updateStrategy: 14 | method: InPlace 15 | resyncPeriodSeconds: 900 16 | hooks: 17 | sync: 18 | webhook: 19 | url: http://vigilant.metacontroller/sync 20 | timeout: 3s 21 | -------------------------------------------------------------------------------- /manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: metacontroller 6 | labels: 7 | name: metacontroller 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: vigilant 13 | namespace: metacontroller 14 | spec: 15 | replicas: 1 16 | strategy: 17 | type: RollingUpdate 18 | rollingUpdate: 19 | maxSurge: 1 20 | maxUnavailable: 0 21 | selector: 22 | matchLabels: 23 | app: vigilant 24 | template: 25 | metadata: 26 | labels: 27 | app: vigilant 28 | spec: 29 | automountServiceAccountToken: false 30 | containers: 31 | - name: vigilant 32 | image: bincyber/vigilant:v0.1.0 33 | ports: 34 | - containerPort: 8000 35 | protocol: TCP 36 | livenessProbe: 37 | httpGet: 38 | path: /health 39 | port: 8000 40 | scheme: HTTP 41 | initialDelaySeconds: 10 42 | periodSeconds: 5 43 | successThreshold: 1 44 | failureThreshold: 1 45 | timeoutSeconds: 1 46 | resources: 47 | requests: 48 | cpu: 100m 49 | memory: 32Mi 50 | securityContext: 51 | readOnlyRootFilesystem: true 52 | runAsNonRoot: true 53 | capabilities: 54 | drop: 55 | - ALL 56 | --- 57 | apiVersion: v1 58 | kind: Service 59 | metadata: 60 | name: vigilant 61 | namespace: metacontroller 62 | annotations: 63 | prometheus.io/scrape: "true" 64 | prometheus.io/port: "8000" 65 | prometheus.io/path: "/metrics" 66 | spec: 67 | selector: 68 | app: vigilant 69 | ports: 70 | - port: 80 71 | protocol: TCP 72 | targetPort: 8000 73 | --- 74 | kind: NetworkPolicy 75 | apiVersion: networking.k8s.io/v1 76 | metadata: 77 | name: vigilant 78 | namespace: metacontroller 79 | spec: 80 | podSelector: 81 | matchLabels: 82 | app: vigilant 83 | policyTypes: 84 | - Ingress 85 | ingress: 86 | - from: [] 87 | ports: 88 | - protocol: TCP 89 | port: 8000 90 | --------------------------------------------------------------------------------