├── .gitignore ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── OWNERS ├── README-QWIKLABS.md ├── README.md ├── common.sh ├── create.sh ├── enable-apis.sh ├── generate-tfvars.sh ├── img ├── architecture.png └── terraform_fingerprint_error.png ├── manifests ├── hello-app │ ├── hello-client.yaml │ └── hello-server.yaml ├── network-policy-namespaced.yaml └── network-policy.yaml ├── setup_manifests.sh ├── teardown.sh ├── terraform ├── bastion.tf ├── firewall.tf ├── main.tf ├── network.tf ├── provider.tf ├── variables.tf └── versions.tf ├── test ├── boilerplate │ ├── boilerplate.BUILD.txt │ ├── boilerplate.Dockerfile.txt │ ├── boilerplate.Makefile.txt │ ├── boilerplate.WORKSPACE.txt │ ├── boilerplate.bazel.txt │ ├── boilerplate.bzl.txt │ ├── boilerplate.css.txt │ ├── boilerplate.go.preamble │ ├── boilerplate.go.txt │ ├── boilerplate.html.preamble │ ├── boilerplate.html.txt │ ├── boilerplate.java.txt │ ├── boilerplate.js.txt │ ├── boilerplate.py.preamble │ ├── boilerplate.py.txt │ ├── boilerplate.scss.txt │ ├── boilerplate.sh.preamble │ ├── boilerplate.sh.txt │ ├── boilerplate.tf.txt │ ├── boilerplate.ts.txt │ ├── boilerplate.xml.preamble │ ├── boilerplate.xml.txt │ └── boilerplate.yaml.txt ├── make.sh └── verify_boilerplate.py └── validate.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Emacs save files 8 | *~ 9 | \#*\# 10 | .\#* 11 | 12 | # Vim-related files 13 | [._]*.s[a-w][a-z] 14 | [._]s[a-w][a-z] 15 | *.un~ 16 | Session.vim 17 | .netrwhist 18 | 19 | # Local .terraform directories 20 | **/.terraform/* 21 | 22 | # .tfstate files 23 | *.tfstate 24 | *.tfstate.* 25 | 26 | # Ignore any .tfvars files that are generated automatically for each Terraform 27 | # run. Most .tfvars files are not managed as part of configuration and so should 28 | # not be included in version control. 29 | terraform.tfvars 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | Contributions to this project must be accompanied by a Contributor License 8 | Agreement. You (or your employer) retain the copyright to your contribution; 9 | this simply gives us permission to use and redistribute your contributions as 10 | part of the project. Head over to https://cla.developers.google.com/ to see your 11 | current agreements on file or to sign a new one. 12 | 13 | You generally only need to submit a CLA once, so if you've already submitted one 14 | (even if it was for a different project), you probably don't need to do it again. 15 | 16 | ## Code reviews 17 | All submissions, including submissions by project members, require review. We 18 | use GitHub pull requests for this purpose. Consult GitHub Help for more 19 | information on using pull requests. 20 | 21 | ## Community Guidelines 22 | This project follows 23 | [Google's Open Source Community Guidelines](CODE-OF-CONDUCT.md). 24 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | /* 3 | Copyright 2018 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | // Reference: https://github.com/jenkinsci/kubernetes-plugin 19 | // set up pod label and GOOGLE_APPLICATION_CREDENTIALS (for Terraform) 20 | def containerName = "network-policy" 21 | def GOOGLE_APPLICATION_CREDENTIALS = '/home/jenkins/dev/jenkins-deploy-dev-infra.json' 22 | def jenkins_container_version = env.JENKINS_CONTAINER_VERSION 23 | 24 | podTemplate( 25 | containers: [ 26 | containerTemplate(name: "${containerName}", 27 | image: "gcr.io/pso-helmsman-cicd/jenkins-k8s-node:${jenkins_container_version}", 28 | command: 'tail -f /dev/null', 29 | resourceRequestCpu: '1000m', 30 | resourceLimitCpu: '2000m', 31 | resourceRequestMemory: '1Gi', 32 | resourceLimitMemory: '2Gi' 33 | ) 34 | ], 35 | volumes: [secretVolume(mountPath: '/home/jenkins/dev', 36 | secretName: 'jenkins-deploy-dev-infra' 37 | )] 38 | ) { 39 | node(POD_LABEL) { 40 | try { 41 | // Options covers all other job properties or wrapper functions that apply to entire Pipeline. 42 | properties([disableConcurrentBuilds()]) 43 | // set env variable GOOGLE_APPLICATION_CREDENTIALS for Terraform 44 | env.GOOGLE_APPLICATION_CREDENTIALS = GOOGLE_APPLICATION_CREDENTIALS 45 | 46 | stage('Setup') { 47 | container(containerName) { 48 | // checkout code from scm i.e. commits related to the PR 49 | checkout scm 50 | // Setup gcloud service account access 51 | sh "gcloud auth activate-service-account --key-file=${GOOGLE_APPLICATION_CREDENTIALS}" 52 | sh "gcloud config set compute/zone ${env.ZONE}" 53 | sh "gcloud config set core/project ${env.PROJECT_ID}" 54 | sh "gcloud config set compute/region ${env.REGION}" 55 | } 56 | } 57 | stage('Lint') { 58 | container(containerName) { 59 | sh "make lint" 60 | } 61 | } 62 | 63 | stage('Create') { 64 | container(containerName) { 65 | sh "make create" 66 | } 67 | } 68 | 69 | stage('Validate') { 70 | container(containerName) { 71 | sh "make validate" 72 | } 73 | } 74 | } catch (err) { 75 | // if any exception occurs, mark the build as failed 76 | // and display a detailed message on the Jenkins console output 77 | currentBuild.result = 'FAILURE' 78 | echo "FAILURE caught echo ${err}" 79 | throw err 80 | } finally { 81 | stage('Teardown') { 82 | container(containerName) { 83 | sh "make teardown" 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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 | # Copyright 2018 Google LLC 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 | # https://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 | # Make will use bash instead of sh 15 | SHELL := /usr/bin/env bash 16 | ROOT := ${CURDIR} 17 | 18 | # create/delete/validate is for CICD 19 | .PHONY: create 20 | create: 21 | $(ROOT)/create.sh 22 | .PHONY: delete 23 | teardown: 24 | $(ROOT)/teardown.sh 25 | 26 | .PHONY: validate 27 | validate: 28 | ${ROOT}/validate.sh 29 | 30 | # All is the first target in the file so it will get picked up when you just run 'make' on its own 31 | lint: check_shell check_shebangs check_python check_golang check_terraform check_docker check_base_files check_headers check_trailing_whitespace 32 | 33 | # The .PHONY directive tells make that this isn't a real target and so 34 | # the presence of a file named 'check_shell' won't cause this target to stop 35 | # working 36 | .PHONY: check_shell 37 | check_shell: 38 | @source test/make.sh && check_shell 39 | 40 | .PHONY: ci 41 | ci: verify-header 42 | 43 | .PHONY: verify-header 44 | verify-header: 45 | python test/verify_boilerplate.py 46 | @echo "\n Test passed - Verified all file Apache 2 headers" 47 | 48 | .PHONY: setup-project 49 | setup-project: 50 | # Enables the Google Cloud APIs needed 51 | ./enable-apis.sh 52 | # Runs the generate-tfvars.sh 53 | ./generate-tfvars.sh 54 | 55 | .PHONY: tf-apply 56 | tf-apply: 57 | # Downloads the terraform providers and applies the configuration 58 | cd terraform && terraform init && terraform apply 59 | 60 | .PHONY: tf-destroy 61 | tf-destroy: 62 | # Downloads the terraform providers and applies the configuration 63 | cd terraform && terraform destroy 64 | 65 | 66 | .PHONY: clean-up 67 | clean-up: 68 | ./remove_manifests.sh 69 | 70 | .PHONY: check_python 71 | check_python: 72 | @source test/make.sh && check_python 73 | 74 | .PHONY: check_golang 75 | check_golang: 76 | @source test/make.sh && golang 77 | 78 | .PHONY: check_terraform 79 | check_terraform: 80 | @source test/make.sh && check_terraform 81 | 82 | .PHONY: check_docker 83 | check_docker: 84 | @source test/make.sh && docker 85 | 86 | .PHONY: check_base_files 87 | check_base_files: 88 | @source test/make.sh && basefiles 89 | 90 | .PHONY: check_shebangs 91 | check_shebangs: 92 | @source test/make.sh && check_bash 93 | 94 | .PHONY: check_trailing_whitespace 95 | check_trailing_whitespace: 96 | @source test/make.sh && check_trailing_whitespace 97 | 98 | .PHONY: check_headers 99 | check_headers: 100 | @echo "Checking file headers" 101 | @python3.7 test/verify_boilerplate.py 102 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - chrislovecnm 3 | - robinpercy 4 | - geojaz 5 | - techgnosis 6 | - erkolson 7 | labels: 8 | - gke-helmsman 9 | -------------------------------------------------------------------------------- /README-QWIKLABS.md: -------------------------------------------------------------------------------- 1 | # How to use a Network Policy in Kubernetes Engine 2 | 3 | ## Table of Contents 4 | 5 | 6 | 7 | * [Introduction](#introduction) 8 | * [Architecture](#architecture) 9 | * [Initialize GCP Authorization](#initialize-gcp-authorization) 10 | * [Deplyment steps](#deplyment-steps) 11 | * [Setup this project](#setup-this-project) 12 | * [Provisioning the Kubernetes Engine Cluster](#provisioning-the-kubernetes-engine-cluster) 13 | * [Validation](#validation) 14 | * [Installing the hello server](#installing-the-hello-server) 15 | * [Confirming default access to the hello server](#confirming-default-access-to-the-hello-server) 16 | * [Restricting access with a Network Policy](#restricting-access-with-a-network-policy) 17 | * [Restricting namespaces with Network Policies](#restricting-namespaces-with-network-policies) 18 | * [Validation](#validation-1) 19 | * [Tear Down](#tear-down) 20 | * [Troubleshooting](#troubleshooting) 21 | * [The install script fails with a Permission denied when running Terraform](#the-install-script-fails-with-a-permission-denied-when-running-terraform) 22 | * [Invalid fingerprint error during Terraform operations](#invalid-fingerprint-error-during-terraform-operations) 23 | * [Relevant Material](#relevant-material) 24 | 25 | 26 | 27 | ## Introduction 28 | 29 | This guide demonstrates how to improve the security of your Kubernetes Engine by applying fine-grained restrictions to network communication. 30 | 31 | The Principle of Least Privilege is widely recognized as an important design consideration in enhancing the protection of critical systems from faults and malicious behavior (https://en.wikipedia.org/wiki/Principle_of_least_privilege). It suggests that every component must be able to access **only** the information and resources that are necessary for its legitimate purpose. This document demonstrates how the Principle of Least Privilege can be implemented within the Kubernetes Engine network layer. 32 | 33 | Network connections can be restricted at two tiers of your Kubernetes Engine infrastructure. The first, and coarser grained, mechanism is the application of Firewall Rules at the Network, Subnetwork, and Host levels. These rules are applied outside of the Kubernetes Engine at the VPC level. 34 | 35 | While Firewall Rules are a powerful security measure, Kubernetes enables us to define even finer grained rules via Network Policies. [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) are used to limit intra-cluster communication. Note that network policies do not apply to pods attached to the host's network namespace. 36 | 37 | The steps below will provision a private Kubernetes Engine cluster and a bastion host with which to access it. A bastion host provides a single host that has access to the cluster, which, when combined with a private kubernetes network ensures that the cluster isn't exposed to malicious behavior from the internet at large. Bastions are particularly useful when you do not have VPN access to the cloud network. 38 | 39 | Within the cluster, a simple HTTP server and two client pods will be provisioned. This tutorial will then demonstrate how to use a Network Policy and labeling to only allow connections from one of the client pods. 40 | 41 | ## Architecture 42 | 43 | For demonstration purposes, you will define a private Kubernetes cluster. Since the cluster is private, neither the API nor the worker nodes will be accessible from the internet. Instead, you will define a bastion host and use a firewall rule to enable access to it. The bastion's IP address is defined as an authorized network for the cluster, which grants it access to the API. 44 | 45 | Within the cluster, provision three workloads: 46 | 47 | 1. hello-server: this is a simple HTTP server with an internally-accessible endpoint 48 | 1. hello-client-allowed: this is a single pod that repeatedly attempts to access hello-server. The pod is labeled such that the Network Policy will allow it to connect to hello-server. 49 | 1. hello-client-blocked: this runs the same code as hello-client-allowed but the pod is labeled such that the Network Policy will __not__ allow it to connect to hello-server. 50 | 51 | ![architecture](./img/architecture.png) 52 | 53 | 54 | ## Initialize GCP Authorization 55 | 56 | When using Cloud Shell execute the following command in order to setup gcloud cli. 57 | 58 | ```console 59 | gcloud init 60 | ``` 61 | 62 | ## Deplyment steps 63 | 64 | The following steps will allow a user to run the demo. 65 | 66 | ### Setup this project 67 | 68 | This project requires the following Google Cloud Service APIs to be enabled: 69 | 70 | * `compute.googleapis.com` 71 | * `container.googleapis.com` 72 | * `cloudbuild.googleapis.com` 73 | 74 | In addition, the terraform configuration takes three parameters to determine where the Kubernetes Engine cluster should be created: 75 | 76 | * `project` 77 | * `region` 78 | * `zone` 79 | 80 | For simplicity, these parameters are to be specified in a file named `terraform.tfvars`, in the `terraform` directory. To ensure the appropriate APIs are enabled and to generate the `terraform/terraform.tfvars` file based on your gcloud defaults, run: 81 | 82 | ```console 83 | make setup-project 84 | ``` 85 | 86 | This will enable the necessary Service APIs, and it will also generate a `terraform/terraform.tfvars` file with the following keys. The values themselves will match the output of `gcloud config list`: 87 | 88 | ```console 89 | cat terraform/terraform.tfvars 90 | 91 | project="YOUR_PROJECT" 92 | region="YOUR_REGION" 93 | zone="YOUR_ZONE" 94 | ``` 95 | 96 | If you need to override any of the defaults, simply replace the desired value(s) to the right of the equals sign(s). Be sure your replacement values are still double-quoted. 97 | 98 | ### Provisioning the Kubernetes Engine Cluster 99 | 100 | Next, apply the terraform configuration with: 101 | 102 | ```console 103 | # From within the project root, use make to apply the terraform 104 | make tf-apply 105 | ``` 106 | 107 | When prompted if you want to deploy the plan, review the generated plan and enter `yes` to deploy the environment. 108 | 109 | ## Validation 110 | 111 | Once complete, terraform will output a message indicating successful creation of the cluster. 112 | 113 | ```console 114 | ...snip... 115 | google_container_cluster.primary: Still creating... (3m0s elapsed) 116 | google_container_cluster.primary: Still creating... (3m10s elapsed) 117 | google_container_cluster.primary: Still creating... (3m20s elapsed) 118 | google_container_cluster.primary: Still creating... (3m30s elapsed) 119 | google_container_cluster.primary: Creation complete after 3m34s (ID: gke-demo-cluster) 120 | 121 | Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 122 | ``` 123 | 124 | You can also confirm the cluster was created successfully by ensuring that Network Policies are enabled for the new cluster. Verify that `networkPolicyConfig.disabled` is `false` and `networkPolicy.provider` is `CALICO`. 125 | 126 | ```console 127 | gcloud container clusters describe gke-demo-cluster | grep -A1 networkPolicy 128 | networkPolicyConfig: 129 | disabled: true 130 | -- 131 | networkPolicy: 132 | provider: CALICO 133 | 134 | ``` 135 | 136 | Now ssh into the bastion for the remaining steps. 137 | 138 | ```console 139 | gcloud compute ssh gke-demo-bastion 140 | ``` 141 | 142 | The newly-created cluster will now be available for the standard `kubectl` commands on the bastion. 143 | 144 | ## Installing the hello server 145 | 146 | The test application consists of one simple HTTP server, deployed as hello-server and two clients, one of which will be labeled as app=hello and the other app=not-hello. 147 | 148 | All three services can be deployed by applying the hello-app manifests. On the bastion, run: 149 | 150 | ```console 151 | kubectl apply -f ./manifests/hello-app/ 152 | 153 | deployment.apps/hello-client-allowed created 154 | deployment.apps/hello-client-blocked created 155 | service/hello-server created 156 | deployment.apps/hello-server created 157 | ``` 158 | 159 | Use `kubectl get pods`` to verify all three pods have been successfully deployed. 160 | You will see one running pod for each of *hello-client-allowed*, *hello-client-blocked*, 161 | and *hello-server* deployments. 162 | 163 | ```console 164 | kubectl get pods 165 | NAME READY STATUS RESTARTS AGE 166 | hello-client-allowed-7d95fcd5d9-t8fsk | 1/1 Running 0 14m 167 | hello-client-blocked-6497db465d-ckbn8 | 1/1 Running 0 14m 168 | hello-server-7df58f7fb5-nvcvd | 1/1 Running 0 14m 169 | ``` 170 | 171 | ## Confirming default access to the hello server 172 | 173 | Open two new terminals and ssh into the bastion on both using the command: 174 | 175 | ```console 176 | gcloud compute ssh gke-demo-bastion 177 | ``` 178 | 179 | Tail the logs for the clients in each console: 180 | 181 | In the first of the two newly created terminals, tail the "allowed" client: 182 | 183 | ```console 184 | kubectl logs --tail 10 -f $(kubectl get pods -oname -l app=hello) 185 | ``` 186 | 187 | In the second newly created terminal, tail the logs of the "blocked" client: 188 | 189 | ```console 190 | kubectl logs --tail 10 -f $(kubectl get pods -oname -l app=not-hello) 191 | ``` 192 | 193 | You will notice that both pods are successfully able to connect to the `hello-server` service. This is because you have not yet defined a Network Policy to restrict access. In each of these windows you should see successful responses from the server. 194 | 195 | ```console 196 | Hostname: hello-server-7df58f7fb5-nvcvd 197 | Hello, world! 198 | Version: 1.0.0 199 | Hostname: hello-server-7df58f7fb5-nvcvd 200 | Hello, world! 201 | Version: 1.0.0 202 | Hostname: hello-server-7df58f7fb5-nvcvd 203 | ... 204 | ``` 205 | 206 | ## Restricting access with a Network Policy 207 | 208 | In this step you will block access to the `hello-server` pod from all pods that are not labeled with `app=hello`. 209 | 210 | The policy definition you'll use is contained in `manifests/network-policy.yaml` 211 | 212 | Apply the policy with the following command: 213 | 214 | ```console 215 | kubectl apply -f ./manifests/network-policy.yaml 216 | 217 | networkpolicy.networking.k8s.io/hello-server-allow-from-hello-client created 218 | ``` 219 | 220 | You'll now see that the output looks like this in the window tailing the "blocked" client: 221 | 222 | ```console 223 | wget: download timed out 224 | wget: download timed out 225 | wget: download timed out 226 | wget: download timed out 227 | wget: download timed out 228 | wget: download timed out 229 | wget: download timed out 230 | wget: download timed out 231 | wget: download timed out 232 | ... 233 | ``` 234 | 235 | Indicating that the network policy has now prevented communication to the `hello-server` from the unlabeled pod. 236 | 237 | ## Restricting namespaces with Network Policies 238 | 239 | In the previous example, you defined a network policy that restricts connections based on pod labels. It is often useful to instead label entire namespaces, particularly when teams or applications are granted their own namespaces. 240 | 241 | You'll now modify the network policy to only allow traffic from a designated namespace, then you'll move the `hello-allowed` pod into that new namespace. 242 | 243 | First, delete the existing network policy and create the namespaced version 244 | 245 | ```console 246 | kubectl delete -f ./manifests/network-policy.yaml 247 | 248 | networkpolicy.networking.k8s.io "hello-server-allow-from-hello-client" deleted 249 | ``` 250 | 251 | ```console 252 | kubectl create -f ./manifests/network-policy-namespaced.yaml 253 | 254 | networkpolicy.networking.k8s.io/hello-server-allow-from-hello-client created 255 | ``` 256 | 257 | Now observe the logs of the `hello-allowed-client` pod in the default namespace. You will notice it is no longer able to connect to the `hello-server`. 258 | 259 | Finally, deploy a second copy of the hello-clients app into the new namespace. 260 | 261 | ```console 262 | kubectl -n hello-apps apply -f ./manifests/hello-app/hello-client.yaml 263 | 264 | deployment.apps/hello-client-allowed created 265 | deployment.apps/hello-client-blocked created 266 | ``` 267 | 268 | ## Validation 269 | 270 | Check the logs for the two new `hello-app` clients and you will see that both clients are able to connect successfully. This is because, *as of Kubernetes 1.10.x NetworkPolicies do not support restricting access to pods within a given namespace*. You can whitelist by pod label, namespace label, or whitelist the union (ie OR) of both. But you cannot yet whitelist the intersection (ie AND) of pod labels and namespace labels. 271 | 272 | View the logs for the "hello"-labeled app in the app in the `hello-apps` namespace: 273 | 274 | ```console 275 | kubectl logs --tail 10 -f -n hello-apps $(kubectl get pods -oname -l app=hello -n hello-apps) 276 | 277 | Hostname: hello-server-6c6fd59cc9-7fvgp 278 | Hello, world! 279 | Version: 1.0.0 280 | Hostname: hello-server-6c6fd59cc9-7fvgp 281 | Hello, world! 282 | Version: 1.0.0 283 | Hostname: hello-server-6c6fd59cc9-7fvgp 284 | Hello, world! 285 | Version: 1.0.0 286 | Hostname: hello-server-6c6fd59cc9-7fvgp 287 | Hello, world! 288 | Version: 1.0.0 289 | Hostname: hello-server-6c6fd59cc9-7fvgp 290 | ``` 291 | 292 | ## Tear Down 293 | 294 | Log out of the bastion host and run the following to destroy the environment: 295 | 296 | ```console 297 | make teardown 298 | 299 | ...snip... 300 | google_compute_subnetwork.cluster-subnet: Still destroying... (ID: us-east1/kube-net-subnet, 20s elapsed) 301 | google_compute_subnetwork.cluster-subnet: Destruction complete after 25s 302 | google_compute_network.gke-network: Destroying... (ID: kube-net) 303 | google_compute_network.gke-network: Still destroying... (ID: kube-net, 10s elapsed) 304 | google_compute_network.gke-network: Still destroying... (ID: kube-net, 20s elapsed) 305 | google_compute_network.gke-network: Destruction complete after 26s 306 | 307 | Destroy complete! Resources: 5 destroyed. 308 | ``` 309 | 310 | ## Troubleshooting 311 | 312 | ### The install script fails with a `Permission denied` when running Terraform 313 | 314 | The credentials that Terraform is using do not provide the necessary permissions to create resources in the selected projects. Ensure that the account listed in `gcloud config list` has necessary permissions to create resources. If it does, regenerate the application default credentials using `gcloud auth application-default login`. 315 | 316 | ### Invalid fingerprint error during Terraform operations 317 | 318 | Terraform occasionally complains about an invalid fingerprint, when updating certain resources. If you see the error below, simply re-run the command. ![terraform fingerprint error](./img/terraform_fingerprint_error.png) 319 | 320 | ## Relevant Material 321 | 322 | * [Terraform Google Provider](https://www.terraform.io/docs/providers/google/) 323 | * [Kubernetes Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) 324 | * [Kubernetes Engine - Creating a Cluster Network Policy](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy) 325 | * [Kubernetes Engine - Network Policy Tutorial](https://cloud.google.com/kubernetes-engine/docs/tutorials/network-policy) 326 | * [Kubernetes Engine - Hardening your cluster's security](https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster) 327 | 328 | **This is not an officially supported Google product** 329 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to use a Network Policy in Kubernetes Engine 2 | 3 | ## Table of Contents 4 | 5 | 6 | 7 | * [Introduction](#introduction) 8 | * [Architecture](#architecture) 9 | * [Prerequisites](#prerequisites) 10 | * [Tools](#tools) 11 | * [Versions](#versions) 12 | * [Deployment](#deployment) 13 | * [Authenticate gcloud](#authenticate-gcloud) 14 | * [Configure gcloud settings](#configuring-gcloud-settings) 15 | * [Setup this project](#setup-this-project) 16 | * [One time initialization.](#one-time-initialization) 17 | * [Provisioning the Kubernetes Engine Cluster](#provisioning-the-kubernetes-engine-cluster) 18 | * [Validation](#validation) 19 | * [Installing the hello server](#installing-the-hello-server) 20 | * [Restricting access with a Network Policy](#restricting-access-with-a-network-policy) 21 | * [Restricting namespaces with Network Policies](#restricting-namespaces-with-network-policies) 22 | * [Tear Down](#tear-down) 23 | * [Troubleshooting](#troubleshooting) 24 | * [Relevant Material](#relevant-material) 25 | 26 | 27 | 28 | ## Introduction 29 | 30 | This guide demonstrates how to improve the security of your Kubernetes Engine by applying fine-grained restrictions to network communication. 31 | 32 | The Principle of Least Privilege is widely recognized as an important design consideration in enhancing the protection of critical systems from faults and malicious behavior (https://en.wikipedia.org/wiki/Principle_of_least_privilege). It suggests that every component must be able to access **only** the information and resources that are necessary for its legitimate purpose. This document demonstrates how the Principle of Least Privilege can be implemented within the Kubernetes Engine network layer. 33 | 34 | Network connections can be restricted at two tiers of your Kubernetes Engine infrastructure. The first, and coarser grained, mechanism is the application of Firewall Rules at the Network, Subnetwork, and Host levels. These rules are applied outside of the Kubernetes Engine at the VPC level. 35 | 36 | While Firewall Rules are a powerful security measure, Kubernetes enables us to define even finer grained rules via Network Policies. [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) are used to limit intra-cluster communication. Note that network policies do not apply to pods attached to the host's network namespace. 37 | 38 | The steps below will provision a private Kubernetes Engine cluster and a bastion host with which to access it. A bastion host provides a single host that has access to the cluster, which, when combined with a private kubernetes network ensures that the cluster isn't exposed to malicious behavior from the internet at large. Bastions are particularly useful when you do not have VPN access to the cloud network. 39 | 40 | Within the cluster, a simple HTTP server and two client pods will be provisioned. This tutorial will then demonstrate how to use a Network Policy and labeling to only allow connections from one of the client pods. 41 | 42 | ## Architecture 43 | 44 | For demonstration purposes, you will define a private Kubernetes cluster. Since the cluster is private, neither the API nor the worker nodes will be accessible from the internet. Instead, you will define a bastion host and use a firewall rule to enable access to it. The bastion's IP address is defined as an authorized network for the cluster, which grants it access to the API. 45 | 46 | Within the cluster, provision three workloads: 47 | 48 | 1. hello-server: this is a simple HTTP server with an internally-accessible endpoint 49 | 1. hello-client-allowed: this is a single pod that repeatedly attempts to access hello-server. The pod is labeled such that the Network Policy will allow it to connect to hello-server. 50 | 1. hello-client-blocked: this runs the same code as hello-client-allowed but the pod is labeled such that the Network Policy will __not__ allow it to connect to hello-server. 51 | 52 | ![architecture](./img/architecture.png) 53 | 54 | ## Prerequisites 55 | 56 | ### Run Demo in a Google Cloud Shell 57 | 58 | Click the button below to run the demo in a [Google Cloud Shell](https://cloud.google.com/shell/docs/). 59 | 60 | [![Open in Cloud Shell](http://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https://github.com/GoogleCloudPlatform/gke-network-policy-demo.git&cloudshell_image=gcr.io/graphite-cloud-shell-images/terraform:latest&cloudshell_tutorial=README.md) 61 | 62 | All the tools for the demo are installed. When using Cloud Shell execute the following 63 | command in order to setup gcloud cli. When executing this command please setup your region 64 | and zone. 65 | 66 | ```console 67 | gcloud init 68 | ``` 69 | 70 | 71 | ### Tools 72 | 1. [Terraform >= 0.12.3](https://www.terraform.io/downloads.html) 73 | 2. [Google Cloud SDK version >= 253.0.0](https://cloud.google.com/sdk/docs/downloads-versioned-archives) 74 | 3. [kubectl matching the latest GKE version](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 75 | 4. bash or bash compatible shell 76 | 5. [GNU Make 3.x or later](https://www.gnu.org/software/make/) 77 | 6. A Google Cloud Platform project where you have permission to create networks 78 | 79 | #### Install Cloud SDK 80 | The Google Cloud SDK is used to interact with your GCP resources. 81 | [Installation instructions](https://cloud.google.com/sdk/downloads) for multiple platforms are available online. 82 | 83 | #### Install kubectl CLI 84 | 85 | The kubectl CLI is used to interteract with both Kubernetes Engine and kubernetes in general. 86 | [Installation instructions](https://cloud.google.com/kubernetes-engine/docs/quickstart) 87 | for multiple platforms are available online. 88 | 89 | #### Install Terraform 90 | 91 | Terraform is used to automate the manipulation of cloud infrastructure. Its 92 | [installation instructions](https://www.terraform.io/intro/getting-started/install.html) are also available online. 93 | 94 | ## Deployment 95 | 96 | The steps below will walk you through using terraform to deploy a Kubernetes Engine cluster that you will then use for working with Kubernetes network policies. 97 | 98 | ### Authenticate gcloud 99 | 100 | Prior to running this demo, ensure you have authenticated your gcloud client by running the following command: 101 | 102 | ```console 103 | gcloud auth application-default login 104 | ``` 105 | 106 | ### Configure gcloud settings 107 | 108 | Run `gcloud config list` and make sure that `compute/zone`, `compute/region` and `core/project` are populated with values that work for you. You can set their values with the following commands: 109 | 110 | ```console 111 | # Where the region is us-east1 112 | gcloud config set compute/region us-east1 113 | 114 | Updated property [compute/region]. 115 | ``` 116 | 117 | ```console 118 | # Where the zone inside the region is us-east1-c 119 | gcloud config set compute/zone us-east1-c 120 | 121 | Updated property [compute/zone]. 122 | ``` 123 | 124 | ```console 125 | # Where the project name is my-project-name 126 | gcloud config set project my-project-name 127 | 128 | Updated property [core/project]. 129 | ``` 130 | 131 | ### Setup this project 132 | 133 | This project requires the following Google Cloud Service APIs to be enabled: 134 | 135 | * `compute.googleapis.com` 136 | * `container.googleapis.com` 137 | * `cloudbuild.googleapis.com` 138 | 139 | In addition, the terraform configuration takes three parameters to determine where the Kubernetes Engine cluster should be created: 140 | 141 | * `project` 142 | * `region` 143 | * `zone` 144 | 145 | For simplicity, these parameters are to be specified in a file named `terraform.tfvars`, in the `terraform` directory. To ensure the appropriate APIs are enabled and to generate the `terraform/terraform.tfvars` file based on your gcloud defaults, run: 146 | 147 | ```console 148 | make setup-project 149 | ``` 150 | 151 | This will enable the necessary Service APIs, and it will also generate a `terraform/terraform.tfvars` file with the following keys. The values themselves will match the output of `gcloud config list`: 152 | 153 | ```console 154 | cat terraform/terraform.tfvars 155 | 156 | project="YOUR_PROJECT" 157 | region="YOUR_REGION" 158 | zone="YOUR_ZONE" 159 | ``` 160 | 161 | If you need to override any of the defaults, simply replace the desired value(s) to the right of the equals sign(s). Be sure your replacement values are still double-quoted. 162 | 163 | ### Provisioning the Kubernetes Engine Cluster 164 | 165 | Next, apply the terraform configuration with: 166 | 167 | ```console 168 | # From within the project root, use make to apply the terraform 169 | make tf-apply 170 | ``` 171 | 172 | When prompted if you want to deploy the plan, review the generated plan and enter `yes` to deploy the environment. 173 | 174 | ## Validation 175 | 176 | Once complete, terraform will output a message indicating successful creation of the cluster. 177 | 178 | ```console 179 | ...snip... 180 | google_container_cluster.primary: Still creating... (3m0s elapsed) 181 | google_container_cluster.primary: Still creating... (3m10s elapsed) 182 | google_container_cluster.primary: Still creating... (3m20s elapsed) 183 | google_container_cluster.primary: Still creating... (3m30s elapsed) 184 | google_container_cluster.primary: Creation complete after 3m34s (ID: gke-demo-cluster) 185 | 186 | Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 187 | ``` 188 | 189 | You can also confirm the cluster was created successfully by ensuring that Network Policies are enabled for the new cluster. Verify that `networkPolicyConfig.disabled` is `false` and `networkPolicy.provider` is `CALICO`. 190 | 191 | ```console 192 | gcloud container clusters describe gke-demo-cluster | grep -A1 networkPolicy 193 | networkPolicyConfig: 194 | disabled: true 195 | -- 196 | networkPolicy: 197 | provider: CALICO 198 | 199 | ``` 200 | 201 | Now ssh into the bastion for the remaining steps. 202 | 203 | ```console 204 | gcloud compute ssh gke-demo-bastion 205 | ``` 206 | 207 | The newly-created cluster will now be available for the standard `kubectl` commands on the bastion. 208 | 209 | ## Installing the hello server 210 | 211 | The test application consists of one simple HTTP server, deployed as hello-server and two clients, one of which will be labeled as app=hello and the other app=not-hello. 212 | 213 | All three services can be deployed by applying the hello-app manifests. On the bastion, run: 214 | 215 | ```console 216 | kubectl apply -f ./manifests/hello-app/ 217 | 218 | deployment.apps/hello-client-allowed created 219 | deployment.apps/hello-client-blocked created 220 | service/hello-server created 221 | deployment.apps/hello-server created 222 | ``` 223 | 224 | Use `kubectl get pods`` to verify all three pods have been successfully deployed. 225 | You will see one running pod for each of *hello-client-allowed*, *hello-client-blocked*, 226 | and *hello-server* deployments. 227 | 228 | ```console 229 | kubectl get pods 230 | NAME READY STATUS RESTARTS AGE 231 | hello-client-allowed-7d95fcd5d9-t8fsk | 1/1 Running 0 14m 232 | hello-client-blocked-6497db465d-ckbn8 | 1/1 Running 0 14m 233 | hello-server-7df58f7fb5-nvcvd | 1/1 Running 0 14m 234 | ``` 235 | 236 | ## Confirming default access to the hello server 237 | 238 | Open two new terminals and ssh into the bastion on both using the command: 239 | 240 | ```console 241 | gcloud compute ssh gke-demo-bastion 242 | ``` 243 | 244 | Tail the logs for the clients in each console: 245 | 246 | In the first of the two newly created terminals, tail the "allowed" client: 247 | 248 | ```console 249 | kubectl logs --tail 10 -f $(kubectl get pods -oname -l app=hello) 250 | ``` 251 | 252 | In the second newly created terminal, tail the logs of the "blocked" client: 253 | 254 | ```console 255 | kubectl logs --tail 10 -f $(kubectl get pods -oname -l app=not-hello) 256 | ``` 257 | 258 | You will notice that both pods are successfully able to connect to the `hello-server` service. This is because you have not yet defined a Network Policy to restrict access. In each of these windows you should see successful responses from the server. 259 | 260 | ```console 261 | Hostname: hello-server-7df58f7fb5-nvcvd 262 | Hello, world! 263 | Version: 1.0.0 264 | Hostname: hello-server-7df58f7fb5-nvcvd 265 | Hello, world! 266 | Version: 1.0.0 267 | Hostname: hello-server-7df58f7fb5-nvcvd 268 | ... 269 | ``` 270 | 271 | ## Restricting access with a Network Policy 272 | 273 | In this step you will block access to the `hello-server` pod from all pods that are not labeled with `app=hello`. 274 | 275 | The policy definition you'll use is contained in `manifests/network-policy.yaml` 276 | 277 | Apply the policy with the following command: 278 | 279 | ```console 280 | kubectl apply -f ./manifests/network-policy.yaml 281 | 282 | networkpolicy.networking.k8s.io/hello-server-allow-from-hello-client created 283 | ``` 284 | 285 | You'll now see that the output looks like this in the window tailing the "blocked" client: 286 | 287 | ```console 288 | wget: download timed out 289 | wget: download timed out 290 | wget: download timed out 291 | wget: download timed out 292 | wget: download timed out 293 | wget: download timed out 294 | wget: download timed out 295 | wget: download timed out 296 | wget: download timed out 297 | ... 298 | ``` 299 | 300 | Indicating that the network policy has now prevented communication to the `hello-server` from the unlabeled pod. 301 | 302 | ## Restricting namespaces with Network Policies 303 | 304 | In the previous example, you defined a network policy that restricts connections based on pod labels. It is often useful to instead label entire namespaces, particularly when teams or applications are granted their own namespaces. 305 | 306 | You'll now modify the network policy to only allow traffic from a designated namespace, then you'll move the `hello-allowed` pod into that new namespace. 307 | 308 | First, delete the existing network policy and create the namespaced version 309 | 310 | ```console 311 | kubectl delete -f ./manifests/network-policy.yaml 312 | 313 | networkpolicy.networking.k8s.io "hello-server-allow-from-hello-client" deleted 314 | ``` 315 | 316 | ```console 317 | kubectl create -f ./manifests/network-policy-namespaced.yaml 318 | 319 | networkpolicy.networking.k8s.io/hello-server-allow-from-hello-client created 320 | ``` 321 | 322 | Now observe the logs of the `hello-allowed-client` pod in the default namespace. You will notice it is no longer able to connect to the `hello-server`. 323 | 324 | Finally, deploy a second copy of the hello-clients app into the new namespace. 325 | 326 | ```console 327 | kubectl -n hello-apps apply -f ./manifests/hello-app/hello-client.yaml 328 | 329 | deployment.apps/hello-client-allowed created 330 | deployment.apps/hello-client-blocked created 331 | ``` 332 | 333 | ### Validation 334 | 335 | Check the logs for the two new `hello-app` clients and you will see that both clients are able to connect successfully. This is because, *as of Kubernetes 1.10.x NetworkPolicies do not support restricting access to pods within a given namespace*. You can whitelist by pod label, namespace label, or whitelist the union (ie OR) of both. But you cannot yet whitelist the intersection (ie AND) of pod labels and namespace labels. 336 | 337 | View the logs for the "hello"-labeled app in the app in the `hello-apps` namespace: 338 | 339 | ```console 340 | kubectl logs --tail 10 -f -n hello-apps $(kubectl get pods -oname -l app=hello -n hello-apps) 341 | 342 | Hostname: hello-server-6c6fd59cc9-7fvgp 343 | Hello, world! 344 | Version: 1.0.0 345 | Hostname: hello-server-6c6fd59cc9-7fvgp 346 | Hello, world! 347 | Version: 1.0.0 348 | Hostname: hello-server-6c6fd59cc9-7fvgp 349 | Hello, world! 350 | Version: 1.0.0 351 | Hostname: hello-server-6c6fd59cc9-7fvgp 352 | Hello, world! 353 | Version: 1.0.0 354 | Hostname: hello-server-6c6fd59cc9-7fvgp 355 | ``` 356 | 357 | ## Tear Down 358 | 359 | Log out of the bastion host and run the following to destroy the environment: 360 | 361 | ```console 362 | make teardown 363 | 364 | ...snip... 365 | google_compute_subnetwork.cluster-subnet: Still destroying... (ID: us-east1/kube-net-subnet, 20s elapsed) 366 | google_compute_subnetwork.cluster-subnet: Destruction complete after 25s 367 | google_compute_network.gke-network: Destroying... (ID: kube-net) 368 | google_compute_network.gke-network: Still destroying... (ID: kube-net, 10s elapsed) 369 | google_compute_network.gke-network: Still destroying... (ID: kube-net, 20s elapsed) 370 | google_compute_network.gke-network: Destruction complete after 26s 371 | 372 | Destroy complete! Resources: 5 destroyed. 373 | ``` 374 | 375 | ## Troubleshooting 376 | 377 | ### The install script fails with a `Permission denied` when running Terraform 378 | 379 | The credentials that Terraform is using do not provide the necessary permissions to create resources in the selected projects. Ensure that the account listed in `gcloud config list` has necessary permissions to create resources. If it does, regenerate the application default credentials using `gcloud auth application-default login`. 380 | 381 | ### Invalid fingerprint error during Terraform operations 382 | 383 | Terraform occasionally complains about an invalid fingerprint, when updating certain resources. If you see the error below, simply re-run the command. ![terraform fingerprint error](./img/terraform_fingerprint_error.png) 384 | 385 | ## Relevant Material 386 | 387 | * [Terraform Google Provider](https://www.terraform.io/docs/providers/google/) 388 | * [Kubernetes Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) 389 | * [Kubernetes Engine - Creating a Cluster Network Policy](https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy) 390 | * [Kubernetes Engine - Network Policy Tutorial](https://cloud.google.com/kubernetes-engine/docs/tutorials/network-policy) 391 | * [Kubernetes Engine - Hardening your cluster's security](https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster) 392 | 393 | **This is not an officially supported Google product** 394 | -------------------------------------------------------------------------------- /common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # "---------------------------------------------------------" 18 | # "- -" 19 | # "- Common commands for all scripts -" 20 | # "- -" 21 | # "---------------------------------------------------------" 22 | 23 | # git is required for this tutorial 24 | command -v git >/dev/null 2>&1 || { \ 25 | echo >&2 "I require git but it's not installed. Aborting."; exit 1; } 26 | 27 | # glcoud is required for this tutorial 28 | command -v gcloud >/dev/null 2>&1 || { \ 29 | echo >&2 "I require gcloud but it's not installed. Aborting."; exit 1; } 30 | 31 | # bastion set up 32 | BASTION_INSTANCE_NAME=gke-demo-bastion 33 | # set to jenkins if there is no $USER 34 | # USER=$(whoami) 35 | # [[ "${USER}" == "root" ]] && export USER=jenkins 36 | # echo "user is: $USER" 37 | 38 | 39 | # validate deployment status via bastion server 40 | function validate_deployment_bastion() { 41 | deployment=$1 42 | ROLLOUT=$(gcloud compute ssh "${USER}"@"${BASTION_INSTANCE_NAME}" --command "kubectl rollout status deploy/${deployment}") 43 | MESSAGE="deployment \"${deployment}\" successfully rolled out" 44 | # Test the ROLLOUT variable to see if the grep has returned the expected value. 45 | # Depending on the test print success or failure messages. 46 | if [[ $ROLLOUT = *"$MESSAGE"* ]]; then 47 | echo "Validation Passed: the deployment ${deployment} has been deployed" 48 | else 49 | echo "Validation Failed: the deployment ${deployment} has not been deployed" 50 | exit 1 51 | fi 52 | } 53 | 54 | 55 | # gcloud config holds values related to your environment. If you already 56 | # defined a default region we will retrieve it and use it 57 | REGION="$(gcloud config get-value compute/region)" 58 | if [[ -z "${REGION}" ]]; then 59 | echo "https://cloud.google.com/compute/docs/regions-zones/changing-default-zone-region" 1>&2 60 | echo "gcloud cli must be configured with a default region." 1>&2 61 | echo "run 'gcloud config set compute/region REGION'." 1>&2 62 | echo "replace 'REGION' with the region name like us-west1." 1>&2 63 | exit 1; 64 | fi 65 | 66 | # gcloud config holds values related to your environment. If you already 67 | # defined a default zone we will retrieve it and use it 68 | ZONE="$(gcloud config get-value compute/zone)" 69 | if [[ -z "${ZONE}" ]]; then 70 | echo "https://cloud.google.com/compute/docs/regions-zones/changing-default-zone-region" 1>&2 71 | echo "gcloud cli must be configured with a default zone." 1>&2 72 | echo "run 'gcloud config set compute/zone ZONE'." 1>&2 73 | echo "replace 'ZONE' with the zone name like us-west1-a." 1>&2 74 | exit 1; 75 | fi 76 | 77 | # gcloud config holds values related to your environment. If you already 78 | # defined a default project we will retrieve it and use it 79 | PROJECT="$(gcloud config get-value core/project)" 80 | if [[ -z "${PROJECT}" ]]; then 81 | echo "gcloud cli must be configured with a default project." 1>&2 82 | echo "run 'gcloud config set core/project PROJECT'." 1>&2 83 | echo "replace 'PROJECT' with the project name." 1>&2 84 | exit 1; 85 | fi 86 | 87 | -------------------------------------------------------------------------------- /create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # "---------------------------------------------------------" 18 | # "- -" 19 | # "- Create starts a GKE Cluster and installs -" 20 | # "- a Cassandra StatefulSet -" 21 | # "- -" 22 | # "---------------------------------------------------------" 23 | 24 | set -o errexit 25 | set -o nounset 26 | set -o pipefail 27 | 28 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 29 | # shellcheck disable=SC1090 30 | source "$ROOT"/common.sh 31 | 32 | # Enable Compute Engine, Kubernetes Engine, and Container Builder 33 | echo "Enabling the Compute API, the Container API, the Cloud Build API" 34 | gcloud services enable \ 35 | compute.googleapis.com \ 36 | container.googleapis.com \ 37 | cloudbuild.googleapis.com 38 | 39 | # Runs the generate-tfvars.sh 40 | "$ROOT"/generate-tfvars.sh 41 | 42 | cd "$ROOT"/terraform && \ 43 | terraform init -input=false && \ 44 | terraform apply -input=false -auto-approve 45 | 46 | # Roll out hello-app 47 | "$ROOT"/setup_manifests.sh 48 | -------------------------------------------------------------------------------- /enable-apis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | # Stop immediately if something goes wrong 20 | set -euo pipefail 21 | 22 | # Validate the user would like to proceed 23 | echo 24 | echo "The following APIs will be enabled in your Google Cloud account:" 25 | echo "- compute.googleapis.com" 26 | echo "- container.googleapis.com" 27 | echo "- cloudbuild.googleapis.com" 28 | echo 29 | read -p "Would you like to proceed? [y/n]: " -n 1 -r 30 | echo 31 | # Require a "Y" or "y" to proceed. Otherwise abort. 32 | if [[ ! "$REPLY" =~ ^[Yy]$ ]] 33 | then 34 | # Do not continue. Do not enable the APIs. 35 | echo "Exiting without making changes." 36 | exit 1 37 | fi 38 | 39 | # Enable Compute Engine, Kubernetes Engine, and Container Builder 40 | echo "Enabling the Compute API, the Container API, the Cloud Build API" 41 | gcloud services enable \ 42 | compute.googleapis.com \ 43 | container.googleapis.com \ 44 | cloudbuild.googleapis.com -------------------------------------------------------------------------------- /generate-tfvars.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # "---------------------------------------------------------" 18 | # "- -" 19 | # "- Helper script to generate terraform variables -" 20 | # "- file based on glcoud defaults. -" 21 | # "- -" 22 | # "---------------------------------------------------------" 23 | 24 | # Stop immediately if something goes wrong 25 | set -euo pipefail 26 | 27 | # The purpose is to populate defaults for subsequent terraform commands. 28 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 29 | # shellcheck disable=SC1090 30 | source "$ROOT"/common.sh 31 | 32 | # This script should be run from directory that contains the terraform directory. 33 | 34 | # Use git to find the top-level directory and confirm 35 | # by looking for the 'terraform' directory 36 | PROJECT_DIR="$(git rev-parse --show-toplevel)" 37 | if [[ -d "./terraform" ]]; then 38 | PROJECT_DIR="$(pwd)" 39 | fi 40 | if [[ -z "${PROJECT_DIR}" ]]; then 41 | echo "Could not identify project base directory." 1>&2 42 | echo "Please re-run from a project directory and ensure" 1>&2 43 | echo "the .git directory exists." 1>&2 44 | exit 1; 45 | fi 46 | 47 | 48 | ( 49 | cd "${PROJECT_DIR}" 50 | 51 | TFVARS_FILE="./terraform/terraform.tfvars" 52 | 53 | # We don't want to overwrite a pre-existing tfvars file 54 | if [[ -f "${TFVARS_FILE}" ]] 55 | then 56 | echo "${TFVARS_FILE} already exists." 1>&2 57 | echo "Please remove or rename before regenerating." 1>&2 58 | exit 1; 59 | else 60 | # Write out all the values we gathered into a tfvars file so you don't 61 | # have to enter the values manually 62 | cat < "${TFVARS_FILE}" 63 | project="${PROJECT}" 64 | region="${REGION}" 65 | zone="${ZONE}" 66 | ssh_user_bastion="${USER}" 67 | EOF 68 | fi 69 | ) 70 | 71 | -------------------------------------------------------------------------------- /img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/gke-network-policy-demo/c1b96ac992fb260642d898e771e0182e8fd1bc04/img/architecture.png -------------------------------------------------------------------------------- /img/terraform_fingerprint_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/gke-network-policy-demo/c1b96ac992fb260642d898e771e0182e8fd1bc04/img/terraform_fingerprint_error.png -------------------------------------------------------------------------------- /manifests/hello-app/hello-client.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | # The deployments below both run a simple loop that attempts to access the hello-server 16 | # endpoint: 17 | # 18 | # hello-client-allowed: 19 | # Runs a single pod labeled with app=hello, which matches the ingress rule for 20 | # the hello-server service defined in the network policy and will therefore still be 21 | # able to access the service when the network policy is enabled. 22 | # 23 | # hello-client-blocked: 24 | # Runs a single pod lacking the app=hello label, which does not match the ingress rule 25 | # for the hello-server service defined in the network policy. Once the network policy 26 | # is enabled, the pod will lose access to the service. 27 | 28 | # hello-client-allowed deployment 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: hello-client-allowed 33 | spec: 34 | # Only run a single pod 35 | replicas: 1 36 | 37 | # Control any pod labeled with app=hello 38 | selector: 39 | matchLabels: 40 | app: hello 41 | 42 | # Define pod properties 43 | template: 44 | 45 | # Ensure created pods are labeled with app=hello to match the deployment selector 46 | metadata: 47 | labels: 48 | app: hello 49 | spec: 50 | # This pod does not require access to the Kubernetes API server, so we prevent 51 | # even the default token from being mounted 52 | automountServiceAccountToken: false 53 | 54 | # Pod-level security context to define the default UID and GIDs under which to 55 | # run all container processes. We use 9999 for all IDs since it is unprivileged 56 | # and known to be unallocated on the node instances. 57 | securityContext: 58 | runAsUser: 9999 59 | runAsGroup: 9999 60 | fsGroup: 9999 61 | 62 | # Define container properties 63 | containers: 64 | - image: alpine:3.7 65 | name: hello-client-allowed 66 | 67 | # A simple while loop that attempts to access the hello-server service every 68 | # two seconds. 69 | command: ["sh", "-c"] 70 | args: ["while true; do wget -qO- --timeout=2 http://hello-server.default.svc:8080; sleep 2; done"] 71 | 72 | # Container-level security settings 73 | # Note, containers are unprivileged by default 74 | securityContext: 75 | # Prevents the container from writing to its filesystem 76 | readOnlyRootFilesystem: true 77 | 78 | --- 79 | # hello-client-blocked deployment 80 | apiVersion: apps/v1 81 | kind: Deployment 82 | metadata: 83 | name: hello-client-blocked 84 | spec: 85 | 86 | # Only run a single pod 87 | replicas: 1 88 | 89 | # Control any pod labeled with app=hello 90 | selector: 91 | matchLabels: 92 | app: not-hello 93 | 94 | # Define pod properties 95 | template: 96 | 97 | # Ensure created pods are labeled with app=hello to match the deployment selector 98 | metadata: 99 | labels: 100 | app: not-hello 101 | spec: 102 | # This pod does not require access to the Kubernetes API server, so we prevent 103 | # even the default token from being mounted 104 | automountServiceAccountToken: false 105 | 106 | # Pod-level security context to define the default UID and GIDs under which to 107 | # run all container processes. We use 9999 for all IDs since it is unprivileged 108 | # and known to be unallocated on the node instances. 109 | securityContext: 110 | runAsUser: 9999 111 | runAsGroup: 9999 112 | fsGroup: 9999 113 | 114 | # Define container properties 115 | containers: 116 | - image: alpine:3.7 117 | name: hello-client-blocked 118 | 119 | # A simple while loop that attempts to access the hello-server service every 120 | # two seconds. 121 | command: ["sh", "-c"] 122 | args: ["while true; do wget -qO- --timeout=2 http://hello-server.default.svc:8080; sleep 2; done"] 123 | 124 | # Container-level security settings 125 | # Note, containers are unprivileged by default 126 | securityContext: 127 | # Prevents the container from writing to its filesystem 128 | readOnlyRootFilesystem: true 129 | 130 | -------------------------------------------------------------------------------- /manifests/hello-app/hello-server.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | # The manifest exposes a simple hello-server service and deployment with a single pod 16 | # The pod is labeled with app=hello server, which will be matched and filtered by 17 | # our network policy. 18 | 19 | # Makes the hello-server pod addressable within the cluster 20 | kind: Service 21 | apiVersion: v1 22 | metadata: 23 | # Label and name the service 24 | labels: 25 | app: hello-server 26 | name: hello-server 27 | spec: 28 | ports: 29 | # Listens on port 8080 and routes to targetPort 8080 on backend pods 30 | - port: 8080 31 | protocol: TCP 32 | targetPort: 8080 33 | 34 | # Load balance requests across all pods labeled with app=hello-server 35 | selector: 36 | app: hello-server 37 | 38 | # Disable session affinity, each request may be routed to a new pod 39 | sessionAffinity: None 40 | 41 | # Expose the service internally only 42 | type: ClusterIP 43 | 44 | --- 45 | 46 | # Deploys a pod to service hello-server requests 47 | apiVersion: apps/v1 48 | kind: Deployment 49 | metadata: 50 | # Label and name the deployment 51 | labels: 52 | app: hello-server 53 | name: hello-server 54 | spec: 55 | 56 | # Only run a single pod 57 | replicas: 1 58 | 59 | # Control any pod labeled with app=hello 60 | selector: 61 | matchLabels: 62 | app: hello-server 63 | 64 | # Define pod properties 65 | template: 66 | # Ensure created pods are labeld with hello-server to match the deployment selector 67 | metadata: 68 | labels: 69 | app: hello-server 70 | spec: 71 | # This pod does not require access to the Kubernetes API server, so we prevent 72 | # even the default token from being mounted 73 | automountServiceAccountToken: false 74 | 75 | # Pod-level security context to define the default UID and GIDs under which to 76 | # run all container processes. We use 9999 for all IDs since it is unprivileged 77 | # and known to be unallocated on the node instances. 78 | securityContext: 79 | runAsUser: 9999 80 | runAsGroup: 9999 81 | fsGroup: 9999 82 | 83 | # Define container properties 84 | containers: 85 | - image: gcr.io/google-samples/hello-app:1.0 86 | name: hello-server 87 | 88 | # Describes the ports exposed on the service 89 | ports: 90 | - containerPort: 8080 91 | protocol: TCP 92 | 93 | # Container-level security settings 94 | # Note, containers are unprivileged by default 95 | securityContext: 96 | # Prevents the container from writing to its filesystem 97 | readOnlyRootFilesystem: true 98 | -------------------------------------------------------------------------------- /manifests/network-policy-namespaced.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | # Defines a new namespace used to demonstrate whitelisting an entire namespace 16 | # See https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ 17 | kind: Namespace 18 | apiVersion: v1 19 | metadata: 20 | name: hello-apps 21 | labels: 22 | team: "hello" 23 | 24 | --- 25 | 26 | # This network policy places restrictions on which pods can communicate to the 27 | # hello-server service. Network policies can be thought of as in-cluster firewall 28 | # rules, where the source and destination are specified as selectors. In this case 29 | # we use labels as the selection criteria for namespaces. 30 | # See https://kubernetes.io/docs/concepts/services-networking/network-policies/ 31 | kind: NetworkPolicy 32 | apiVersion: networking.k8s.io/v1 33 | metadata: 34 | # Name the network policy 35 | name: hello-server-allow-from-hello-client 36 | spec: 37 | 38 | # Define this as an ingress rule which allows us to restrict access to a set of pods. 39 | policyTypes: 40 | - Ingress 41 | 42 | # Defines the set of pods to which this policy applies 43 | # In this case, we apply the policy to pods labeled as app=hello-server 44 | podSelector: 45 | matchLabels: 46 | app: hello-server 47 | 48 | # Define the sources allowed by this policy 49 | # In this case, we allow ingress from all pods the namespace labeled as team=hello 50 | # Note: as of Kubernetes 1.10. It is not possible to restrict connections 51 | # by both namespace and pod labels simultaneously. However support is 52 | # expected to be added in the future. 53 | ingress: 54 | - from: 55 | - namespaceSelector: 56 | matchLabels: 57 | team: hello 58 | -------------------------------------------------------------------------------- /manifests/network-policy.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | 16 | # This network policy places restrictions on which pods can communicate to the 17 | # hello-server service. Network policies can be thought of as in-cluster firewall 18 | # rules, where the source and destination are specified as selectors. In this case 19 | # we use labels as the selection criteria. 20 | # See https://kubernetes.io/docs/concepts/services-networking/network-policies/ 21 | kind: NetworkPolicy 22 | apiVersion: networking.k8s.io/v1 23 | metadata: 24 | # Name the network policy 25 | name: hello-server-allow-from-hello-client 26 | spec: 27 | 28 | # Define this as an ingress rule which allows us to restrict access to a set of pods. 29 | policyTypes: 30 | - Ingress 31 | 32 | # Defines the set of pods to which this policy applies 33 | # In this case, we apply the policy to pods labeled as app=hello-server 34 | podSelector: 35 | matchLabels: 36 | app: hello-server 37 | 38 | # Define the sources allowed by this policy 39 | # In this case, we allow ingress from all pods labeled as app=hello 40 | ingress: 41 | - from: 42 | - podSelector: 43 | matchLabels: 44 | app: hello 45 | -------------------------------------------------------------------------------- /setup_manifests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2018 Google LLC 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 | # https://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 | # bash "strict-mode", fail immediately if there is a problem 17 | set -euo pipefail 18 | 19 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 20 | # shellcheck disable=SC1090 21 | source "$ROOT"/common.sh 22 | 23 | # A helper method to make calls to the gke cluster through the bastion. 24 | call_bastion() { 25 | local command=$1; shift; 26 | # shellcheck disable=SC2005 27 | echo "$(gcloud compute ssh "${USER}"@gke-demo-bastion --command "${command}")" 28 | } 29 | MESSAGE="successfully rolled out" 30 | 31 | # Make a call to the bastion to ensure that credentials are populated. 32 | call_bastion "source /etc/profile && exit" 33 | 34 | # Create the initial manifests necessary for the validation to pass. 35 | call_bastion "kubectl apply -f ./manifests/hello-app/" 36 | 37 | 38 | # For each manifest, ensure that it is rolled out before continuing. 39 | while true 40 | do 41 | ROLLOUT=$(call_bastion "kubectl rollout status --watch=false \ 42 | deployment/hello-server") &> /dev/null 43 | if [[ $ROLLOUT = *"$MESSAGE"* ]]; then 44 | break 45 | fi 46 | sleep 2 47 | done 48 | while true 49 | do 50 | ROLLOUT=$(call_bastion "kubectl rollout status --watch=false \ 51 | deployment/hello-client-allowed") &> /dev/null 52 | if [[ $ROLLOUT = *"$MESSAGE"* ]]; then 53 | break 54 | fi 55 | sleep 2 56 | done 57 | while true 58 | do 59 | ROLLOUT=$(call_bastion "kubectl rollout status --watch=false \ 60 | deployment/hello-client-blocked") &> /dev/null 61 | if [[ $ROLLOUT = *"$MESSAGE"* ]]; then 62 | break 63 | fi 64 | sleep 2 65 | done 66 | 67 | # Credentials are deployed, manifests are rolled out. 68 | exit 0 69 | -------------------------------------------------------------------------------- /teardown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # "---------------------------------------------------------" 18 | # "- -" 19 | # "- Delete uninstalls Cassandra and deletes -" 20 | # "- the GKE cluster -" 21 | # "- -" 22 | # "---------------------------------------------------------" 23 | 24 | # Do not set errexit as it makes partial deletes impossible 25 | set -o nounset 26 | set -o pipefail 27 | 28 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 29 | # shellcheck disable=SC1090 30 | source "$ROOT"/common.sh 31 | 32 | # Tear down hello-app 33 | gcloud compute ssh "${USER}"@"${BASTION_INSTANCE_NAME}" --command "kubectl delete -f manifests/hello-app/" 34 | 35 | # remove metadata for bastion (SSH keys) 36 | gcloud compute instances remove-metadata --all "${BASTION_INSTANCE_NAME}" --project "${PROJECT}" --zone "${ZONE}" 37 | 38 | # Terraform destroy 39 | cd terraform && terraform destroy -auto-approve 40 | 41 | -------------------------------------------------------------------------------- /terraform/bastion.tf: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Google LLC 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 | https://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 | /* 18 | This file contains the configuration of a GCP instance to use as a bastion node. 19 | A bastion node is an accessible instance within an otherwise unaccessible network that is 20 | most often used when a VPN is not available 21 | */ 22 | 23 | // https://www.terraform.io/docs/providers/template/index.html 24 | // This template will be rendered and used as the startup script for the bastion. 25 | // It installs kubectl and configures it to access the GKE cluster. 26 | data "template_file" "startup_script" { 27 | template = <> /etc/profile 31 | EOF 32 | 33 | 34 | vars = { 35 | cluster_name = var.cluster_name 36 | cluster_zone = var.zone 37 | project = var.project 38 | } 39 | } 40 | 41 | // https://www.terraform.io/docs/providers/google/r/compute_instance.html 42 | // bastion host for access and administration of a private cluster. 43 | 44 | resource "google_compute_instance" "gke-bastion" { 45 | name = var.bastion_hostname 46 | machine_type = var.bastion_machine_type 47 | zone = var.zone 48 | project = var.project 49 | tags = var.bastion_tags 50 | allow_stopping_for_update = true 51 | 52 | // Specify the Operating System Family and version. 53 | boot_disk { 54 | initialize_params { 55 | image = "debian-cloud/debian-11" 56 | } 57 | } 58 | 59 | // Define a network interface in the correct subnet. 60 | network_interface { 61 | subnetwork = google_compute_subnetwork.cluster-subnet.self_link 62 | 63 | // Add an ephemeral external IP. 64 | access_config { 65 | // Implicit ephemeral IP 66 | } 67 | } 68 | 69 | // Ensure that when the bastion host is booted, it will have kubectl. 70 | # metadata_startup_script = "sudo apt-get install -y kubectl" 71 | metadata_startup_script = data.template_file.startup_script.rendered 72 | 73 | // Necessary scopes for administering kubernetes. 74 | service_account { 75 | scopes = ["userinfo-email", "compute-ro", "storage-ro", "cloud-platform"] 76 | } 77 | 78 | // Copy the manifests to the bastion 79 | // Copy the manifests to the bastion 80 | provisioner "local-exec" { 81 | interpreter = ["/bin/bash", "-c"] 82 | command = < 16 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.java.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 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 | // https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.js.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 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 | // https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.py.preamble: -------------------------------------------------------------------------------- 1 | #! 2 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.py.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.scss.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 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 | // https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.sh.preamble: -------------------------------------------------------------------------------- 1 | #! 2 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.sh.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.tf.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2018 Google LLC 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 | https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.ts.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 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 | // https://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 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.xml.preamble: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.yaml.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 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 | # https://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 | -------------------------------------------------------------------------------- /test/make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This function checks to make sure that every 18 | # shebang has a '- e' flag, which causes it 19 | # to exit on error 20 | function check_bash() { 21 | find . -name "*.sh" | while IFS= read -d '' -r file; 22 | do 23 | if [[ "$file" != *"bash -e"* ]]; 24 | then 25 | echo "$file is missing shebang with -e"; 26 | exit 1; 27 | fi; 28 | done; 29 | } 30 | 31 | # This function makes sure that the required files for 32 | # releasing to OSS are present 33 | function basefiles() { 34 | echo "Checking for required files" 35 | test -f CONTRIBUTING.md || echo "Missing CONTRIBUTING.md" 36 | test -f LICENSE || echo "Missing LICENSE" 37 | test -f README.md || echo "Missing README.md" 38 | } 39 | 40 | # This function runs the hadolint linter on 41 | # every file named 'Dockerfile' 42 | function docker() { 43 | echo "Running hadolint on Dockerfiles" 44 | find . -name "Dockerfile" -exec hadolint {} \; 45 | } 46 | 47 | # This function runs 'terraform validate' against all 48 | # files ending in '.tf' 49 | function check_terraform() { 50 | echo "Running terraform validate" 51 | #shellcheck disable=SC2156 52 | find . -name "*.tf" -exec bash -c 'terraform init $(dirname "{}") && terraform validate $(dirname "{}")' \; 53 | } 54 | 55 | # This function runs 'go fmt' and 'go vet' on eery file 56 | # that ends in '.go' 57 | function golang() { 58 | echo "Running go fmt and go vet" 59 | find . -name "*.go" -exec go fmt {} \; 60 | find . -name "*.go" -exec go vet {} \; 61 | } 62 | 63 | # This function runs the flake8 linter on every file 64 | # ending in '.py' 65 | function check_python() { 66 | echo "Running flake8" 67 | find . -name "*.py" -exec flake8 {} \; 68 | } 69 | 70 | # This function runs the shellcheck linter on every 71 | # file ending in '.sh' 72 | function check_shell() { 73 | echo "Running shellcheck" 74 | find . -name "*.sh" -exec shellcheck -x {} \; 75 | } 76 | 77 | # This function makes sure that there is no trailing whitespace 78 | # in any files in the project. 79 | # There are some exclusions 80 | function check_trailing_whitespace() { 81 | echo "The following lines have trailing whitespace" 82 | grep -r '[[:blank:]]$' --exclude-dir=".terraform" --exclude="*.png" --exclude-dir=".git" --exclude="*.pyc" . 83 | rc=$? 84 | if [ $rc = 0 ]; then 85 | exit 1 86 | fi 87 | } 88 | -------------------------------------------------------------------------------- /test/verify_boilerplate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.7 2 | # Copyright 2018 Google LLC 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 | # https://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 | # Verifies that all source files contain the necessary copyright boilerplate 16 | # snippet. 17 | 18 | # This code is based on existing work 19 | # https://partner-code.googlesource.com/helmsman-cardinal/+/refs/heads/master/helmsman-template-project/test/verify_boilerplate.py 20 | 21 | 22 | """ 23 | A runnable module to test the presence of boilerplate 24 | text in files within a repo. 25 | """ 26 | 27 | from __future__ import print_function 28 | from subprocess import run, CalledProcessError 29 | import argparse 30 | import glob 31 | import os 32 | import re 33 | import sys 34 | 35 | 36 | # These directories will be omitted from header checks 37 | SKIPPED_PATHS = [ 38 | 'Godeps', 'third_party', '_gopath', '_output', 39 | '.git', 'vendor', '__init__.py', 'node_modules', 40 | 'bazel-out', 'external', '3rdparty' 41 | ] 42 | 43 | # A map of regular expressions used in boilerplate validation. 44 | # The date regex is used in validating the date referenced 45 | # is the boilerplate, by ensuring it is an acceptable year. 46 | REGEXES = { 47 | # beware the Y2100 problem 48 | "date": re.compile(r'(20\d\d)') 49 | } 50 | 51 | 52 | def get_args(): 53 | """Parses command line arguments. 54 | Configures and runs argparse.ArgumentParser to extract command line 55 | arguments. 56 | Returns: 57 | An argparse.Namespace containing the arguments parsed from the 58 | command line 59 | """ 60 | parser = argparse.ArgumentParser() 61 | 62 | parser.add_argument("filenames", 63 | help="""A list of files to check, all in repo are 64 | checked if this is unspecified.""", 65 | nargs='*') 66 | 67 | parser.add_argument("-f", "--force-extension", 68 | default="", 69 | help="""Force an extension to compare against. Useful 70 | for files without extensions, such as runnable shell 71 | scripts .""") 72 | 73 | parser.add_argument( 74 | "-r", "--rootdir", 75 | default=None, 76 | help="""Root directory of repository. If not specified, the script will 77 | attempt to draw this value from git.""") 78 | 79 | parser.add_argument("-b", "--boilerplate-dir", 80 | default=None, 81 | help="""Directory with boilerplate files. Defaults to 82 | [root]/test/boilerplate.""") 83 | 84 | args = parser.parse_args() 85 | 86 | if not args.rootdir: 87 | ask_git = run( 88 | ["git", "rev-parse", "--show-toplevel"], 89 | capture_output=True, text=True) 90 | try: 91 | ask_git.check_returncode() 92 | except CalledProcessError: 93 | print("""No root specfied and directory does not seem to be a git 94 | repository, or git is not installed.""", file=sys.stderr) 95 | sys.exit(1) 96 | args.rootdir = ask_git.stdout.strip() 97 | 98 | if not args.boilerplate_dir: 99 | args.boilerplate_dir = os.path.join(args.rootdir, "test/boilerplate") 100 | 101 | return args 102 | 103 | 104 | def get_references(args): 105 | """Reads each reference boilerplate file's contents into an array, then 106 | adds that array to a dictionary keyed by the file extension. 107 | 108 | Returns: 109 | A dictionary of boilerplate lines, keyed by file extension. 110 | For example, boilerplate.py.txt would result in the 111 | k,v pair {".py": py_lines} where py_lines is an array 112 | containing each line of the file. 113 | """ 114 | references = {} 115 | 116 | # Find all paths for boilerplate references 117 | boilerplate_paths = glob.glob( 118 | os.path.join(args.boilerplate_dir, "boilerplate.*.txt")) 119 | 120 | # Read all boilerplate references into dictionary 121 | for path in boilerplate_paths: 122 | with open(path, 'r') as ref_file: 123 | extension = os.path.basename(path).split(".")[1] 124 | ref = ref_file.read().splitlines() 125 | references[extension] = ref 126 | 127 | return references 128 | 129 | 130 | # Improvement: combine this function with `get_references` 131 | def get_preambles(args): 132 | """Reads each preamble boilerplate file's contents into an array, then 133 | adds that array to a dictionary keyed by the file extension. 134 | 135 | Returns: 136 | A dictionary of boilerplate lines, keyed by file extension. 137 | For example, boilerplate.py.preamble would result 138 | in the k,v pair {".py": py_lines} where py_lines is 139 | an array containing each line of the file 140 | (ex: "#!/usr/bin/env python3.7") 141 | """ 142 | preambles = {} 143 | 144 | # Find all paths for boilerplate preambles 145 | boilerplate_paths = glob.glob( 146 | os.path.join(args.boilerplate_dir, "boilerplate.*.preamble")) 147 | 148 | # Read all boilerplate preambles into dictionary 149 | for path in boilerplate_paths: 150 | with open(path, 'r') as ref_file: 151 | extension = os.path.basename(path).split(".")[1] 152 | ref = ref_file.read().splitlines() 153 | preambles[extension] = ref 154 | 155 | return preambles 156 | 157 | 158 | def has_valid_header(filename, references, preambles, regexs, args): 159 | """Test whether a file has the correct boilerplate header. 160 | Tests each file against the boilerplate stored in refs for that file type 161 | (based on extension), or by the entire filename (eg Dockerfile, Makefile). 162 | Some heuristics are applied to remove build tags and shebangs, but little 163 | variance in header formatting is tolerated. 164 | Args: 165 | filename: A string containing the name of the file to test 166 | references: A map of reference boilerplate text, 167 | keyed by file extension 168 | preambles: A map of preamble boilerplate text, keyed by file extension 169 | regexs: a map of compiled regex objects used in verifying boilerplate 170 | Returns: 171 | True if the file has the correct boilerplate header, otherwise returns 172 | False. 173 | """ 174 | # Read the entire file. 175 | with open(filename, 'r') as test_file: 176 | data = test_file.read() 177 | 178 | # Select the appropriate reference based on the extension, 179 | # or if none, the file name. 180 | basename, extension = get_file_parts(filename) 181 | if args.force_extension: 182 | extension = args.force_extension 183 | elif extension: 184 | extension = extension 185 | else: 186 | extension = basename 187 | ref = references[extension] 188 | print("Verifying boilerplate in file: %s as %s" % ( 189 | os.path.relpath(filename, args.rootdir), 190 | extension)) 191 | 192 | preamble = preambles.get(extension) 193 | if preamble: 194 | preamble = re.escape("\n".join(preamble)) 195 | regflags = re.MULTILINE | re.IGNORECASE 196 | regex = re.compile(r"^(%s.*\n)\n*" % preamble, regflags) 197 | (data, _) = regex.subn("", data, 1) 198 | 199 | data = data.splitlines() 200 | 201 | # if our test file is smaller than the reference it surely fails! 202 | if len(ref) > len(data): 203 | return False 204 | # truncate our file to the same number of lines as the reference file 205 | data = data[:len(ref)] 206 | 207 | # if we don't match the reference at this point, fail 208 | if ref != data: 209 | return False 210 | 211 | return True 212 | 213 | 214 | def get_file_parts(filename): 215 | """Extracts the basename and extension parts of a filename. 216 | Identifies the extension as everything after the last period in filename. 217 | Args: 218 | filename: string containing the filename 219 | Returns: 220 | A tuple of: 221 | A string containing the basename 222 | A string containing the extension in lowercase 223 | """ 224 | extension = os.path.splitext(filename)[1].split(".")[-1].lower() 225 | basename = os.path.basename(filename) 226 | return basename, extension 227 | 228 | 229 | def normalize_files(files, args): 230 | """Extracts the files that require boilerplate checking from the files 231 | argument. 232 | A new list will be built. Each path from the original files argument will 233 | be added unless it is within one of SKIPPED_DIRS. All relative paths will 234 | be converted to absolute paths by prepending the root_dir path parsed from 235 | the command line, or its default value. 236 | Args: 237 | files: a list of file path strings 238 | Returns: 239 | A modified copy of the files list where any any path in a skipped 240 | directory is removed, and all paths have been made absolute. 241 | """ 242 | newfiles = [f for f in files if not any(s in f for s in SKIPPED_PATHS)] 243 | 244 | for idx, pathname in enumerate(newfiles): 245 | if not os.path.isabs(pathname): 246 | newfiles[idx] = os.path.join(args.rootdir, pathname) 247 | return newfiles 248 | 249 | 250 | def get_files(extensions, args): 251 | """Generates a list of paths whose boilerplate should be verified. 252 | If a list of file names has been provided on the command line, it will be 253 | treated as the initial set to search. Otherwise, all paths within rootdir 254 | will be discovered and used as the initial set. 255 | Once the initial set of files is identified, it is normalized via 256 | normalize_files() and further stripped of any file name whose extension is 257 | not in extensions. 258 | Args: 259 | extensions: a list of file extensions indicating which file types 260 | should have their boilerplate verified 261 | Returns: 262 | A list of absolute file paths 263 | """ 264 | files = [] 265 | if args.filenames: 266 | files = args.filenames 267 | else: 268 | for root, dirs, walkfiles in os.walk(args.rootdir): 269 | # don't visit certain dirs. This is just a performance improvement 270 | # as we would prune these later in normalize_files(). But doing it 271 | # cuts down the amount of filesystem walking we do and cuts down 272 | # the size of the file list 273 | for dpath in SKIPPED_PATHS: 274 | if dpath in dirs: 275 | dirs.remove(dpath) 276 | for name in walkfiles: 277 | pathname = os.path.join(root, name) 278 | files.append(pathname) 279 | files = normalize_files(files, args) 280 | outfiles = [] 281 | for pathname in files: 282 | basename, extension = get_file_parts(pathname) 283 | extension_present = extension in extensions or basename in extensions 284 | if args.force_extension or extension_present: 285 | outfiles.append(pathname) 286 | return outfiles 287 | 288 | 289 | def main(args): 290 | """Identifies and verifies files that should have the desired boilerplate. 291 | Retrieves the lists of files to be validated and tests each one in turn. 292 | If all files contain correct boilerplate, this function terminates 293 | normally. Otherwise it prints the name of each non-conforming file and 294 | exists with a non-zero status code. 295 | """ 296 | refs = get_references(args) 297 | preambles = get_preambles(args) 298 | filenames = get_files(refs.keys(), args) 299 | nonconforming_files = [] 300 | for filename in filenames: 301 | if not has_valid_header(filename, refs, preambles, REGEXES, args): 302 | nonconforming_files.append(filename) 303 | if nonconforming_files: 304 | print('%d files have incorrect boilerplate headers:' % len( 305 | nonconforming_files)) 306 | for filename in sorted(nonconforming_files): 307 | print(os.path.relpath(filename, args.rootdir)) 308 | sys.exit(1) 309 | else: 310 | print('All files examined have correct boilerplate.') 311 | 312 | 313 | if __name__ == "__main__": 314 | ARGS = get_args() 315 | main(ARGS) 316 | -------------------------------------------------------------------------------- /validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2018 Google LLC 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 | # https://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 | # bash "strict-mode", fail immediately if there is a problem 17 | set -euo pipefail 18 | 19 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 20 | # shellcheck disable=SC1090 21 | source "$ROOT"/common.sh 22 | 23 | HELLO_WORLD='Hello, world!' 24 | TIMED_OUT='wget: download timed out' 25 | 26 | # Kubectl logs of the hello app pod will return $TIMED_OUT for the first 5 second. 27 | # So let's wait for a while before we start pulling logs 28 | sleep 20 29 | 30 | # A helper method to make calls to the gke cluster through the bastion. 31 | call_bastion() { 32 | local command=$1; shift; 33 | # shellcheck disable=SC2005 34 | echo "$(gcloud compute ssh "$USER"@gke-demo-bastion --command "${command}")" 35 | } 36 | 37 | # We expect to see "Hello, world!" in the logs with the app=hello label. 38 | call_bastion "kubectl logs --tail 10 \$(kubectl get pods -oname -l app=hello)" \ 39 | | grep "$HELLO_WORLD" &> /dev/null || exit 1 40 | echo "step 1 of the validation passed." 41 | 42 | # We expect to see the same behavior in the logs with the app=not-hello label 43 | # until the network-policy is applied. 44 | call_bastion "kubectl logs --tail 10 \ 45 | \$(kubectl get pods -oname -l app=not-hello)" | grep "$HELLO_WORLD" \ 46 | &> /dev/null || exit 1 47 | echo "step 2 of the validation passed." 48 | 49 | # Apply the network policy. 50 | call_bastion "kubectl apply -f ./manifests/network-policy.yaml" &> /dev/null 51 | 52 | # Sleep for 10s while more logs come in. 53 | sleep 10 54 | 55 | # Now we expect to see a 'timed out' message because the network policy 56 | # prevents the communication. 57 | call_bastion "kubectl logs --tail 10 \ 58 | \$(kubectl get pods -oname -l app=not-hello)" | grep "$TIMED_OUT" \ 59 | &> /dev/null || exit 1 60 | echo "step 3 of the validation passed." 61 | 62 | # If the network policy is working correctly, we still see the original behavior 63 | # in the logs with the app=hello label. 64 | call_bastion "kubectl logs --tail 10 \$(kubectl get pods -oname -l app=hello)" \ 65 | | grep "$HELLO_WORLD" &> /dev/null || exit 1 66 | echo "step 4 of the validation passed." 67 | 68 | call_bastion "kubectl delete -f ./manifests/network-policy.yaml" &> /dev/null 69 | --------------------------------------------------------------------------------