[
  {
    "path": ".github/lock.yml",
    "content": "daysUntilLock: 90\nlockLabel: false\nlockComment: false\n"
  },
  {
    "path": ".github/reaction.yml",
    "content": "reactionComment: false\n"
  },
  {
    "path": ".gitignore",
    "content": "tls/\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Vault Kubernetes Workshop on Google Cloud Platform\n\n## Prerequisites &amp; Caveats\n\n- This workshop is designed to be run on [Google Cloud Shell][cs]. It may work\n  in other environments without modification, but the materials are only tested\n  and guaranteed against Cloud Shell.\n\n- You must have a Google Cloud Platform account and be authenticated as a\n  project owner. Again, if you are using Cloud Shell, this happens\n  automatically. If you are running locally, you will need to download and\n  install the [Google Cloud SDK][sdk], and then authenticate to Google Cloud\n  appropriately.\n\n- There are places where this workshop sacrifices \"best practices\" for \"things\n  feasible to complete in the duration of a workshop\". In particular, this\n  workshop generates self-signed SSL certificates and does not encrypt the\n  resulting keys. For more details on a production-hardened setup, please see\n  the [Vault production hardening docs][vault-prod-hardening].\n\n- You must clone this repo:\n\n    ```text\n    git clone https://github.com/sethvargo/vault-kubernetes-workshop\n    cd vault-kubernetes-workshop\n    ```\n\n## 00 Install Vault\n\nThe first step is to install Vault. To install the Vault binary in the current\nworking directory, run the install script.\n\n```text\n./scripts/00-install-vault.sh\n```\n\nThis will install Vault using the `sethvargo/hashicorp-installer` Docker\ncontainer which verifies GPG signatures and checksums from the download.\n\nThis represents a \"best practices\" installation for installing secure software\nlike Vault. By verifying the signature of the SHASUMs and then verifying the\nSHASUMs themselves, we guarantee both the integrity of the download and ensure\nthe binary has not been tampered with beyond it's original publishing. For added\nsecurity, you can download and compile Vault yourself from source, but that is\nout of scope for this workshop.\n\nCloud Shell does not persist things in `/usr/local/bin` or `/usr/bin` between\nsessions. As such, Vault will be installed in `~/bin`. We recommend installing\nother binaries or software you need between sessions in this folder.\n\n## 01 Enable Services\n\nBy default, a new Google Cloud project does not have many services enabled.\nEnable the required services (this only needs to be done once per project):\n\n```text\n./scripts/01-enable-services.sh\n```\n\nThis will make the necessary calls to enable the enable the right APIs on your\nproject. This process can take some time, but it is idempotent (you can run it\nmultiple times to achieve the same result).\n\n## 02 Setup Storage\n\nVault requires a storage backend to persist its data. This workshop leverages\n[Google Cloud Storage][gcs]. Vault does not automatically create the storage\nbucket, so we need to create:\n\n```text\n./scripts/02-setup-storage.sh\n```\n\nCloud Storage bucket names must be globally unique across all of Google Cloud.\nThe bucket will be named \"${PROJECT}-vault-storage\".\n\nFor security purposes, it is not recommended that other applications or services\nhave access to this bucket. Even though the data is encrypted at rest, it is\nbest to limit the scope of access as much as possible.\n\n## 03 Setup KMS\n\nVault will leverage [Google Cloud KMS][kms] to encrypt its unseal keys for\nauto-unsealing and auto-scaling purposes. We must create the KMS key in advance:\n\n```text\n./scripts/03-setup-kms.sh\n```\n\n## 04 Create IAM Service Account\n\nIt is a best practice to create a limited, dedicated service account that has\nonly the required permissions. Create a dedicated service account in the project\nand grant it the most minimal set of permissions, in particular:\n\n- The ability to read/write to the Cloud Storage bucket created above\n- The ability to encrypt/decrypt data with the KMS key created above\n- The ability to generate new service accounts (not required to use Vault, but\n  helpful if you plan to use the Vault GCP secrets engine)\n\n```text\n./scripts/04-create-iam-service-account.sh\n```\n\n## 05 Create Kubernetes Cluster\n\nNext we need to create the [Google Kubernetes Engine][gke] (GKE) cluster which\nwill run Vault. It is recommended that you run Vault in a dedicated namespace or\n(even better) a dedicated cluster and a dedicated project. Vault will then act\nas a service with an IP/DNS entry that other projects and services query.\n\n```text\n./scripts/05-create-k8s-cluster.sh\n```\n\nThis will create the cluster and attach the service account created in the\nprevious step to the cluster. It also ensures the cluster has the correct oauth\nscopes.\n\n## 06 Create Public IP\n\nThis step creates and reserves a regional public IP address. In a future step,\nwe will attach this reserved IP address to a Kubernetes load balancer. For now,\nwe will just reserve the dedicated IP address.\n\n```text\n./scripts/06-create-public-ip.sh\n```\n\nWe use a regional IP address instead of a global IP address because global IPs\nperform load balancing at L7 whereas regional IP addresses perform load\nbalancing at L4. Ideally we do not want the load balancer to perform TLS\ntermination, and let Vault manage TLS, etc. While recent versions of Vault do\nsupport advanced routing like `X-Forwarded-For` headers, it is still a better\npractice to let Vault fully manage the TLS and thus use an L4 load balancer.\n\nThis step is not required if you are comfortable assigning your Vault Kubernetes\nservice an ephemeral IP or will manage it via an external DNS service.\n\n## 07 Create Certificates\n\nThis is arguably the most complex and nuanced piece of the workshop - generating\nVault's certificate authority and server certificates for TLS. Vault can run\nwithout TLS, but this is _highly_ discouraged. This step could be replaced with\na trusted CA like Let's Encrypt, but that is out of scope for this workshop.\n\n```text\n./scripts/07-create-certs.sh\n```\n\nThis will create the Certificate Authority (`ca.key`, `ca.crt`) and Vault\ncertificate (`vault.key`, `vault.crt`). In a future step, we will put these\nvalues in a Kubernetes secret so our pods can access them.\n\n## 08 Create Config\n\nNext we create the config map and secrets to store our data for our pods. The\ninsecure data such as the storage bucket name and IP address are placed in a\nconfigmap. The secure data like the TLS certificates are put in a Kubernetes\nsecret.\n\n```text\n./scripts/08-setup-config.sh\n```\n\n## 09 Deploy Vault\n\nThe next step is to actually deploy Vault as a StatefulSet on Kubernetes. The\nreason we use a StatefulSet is two-fold:\n\n1. It guarantees exactly one service starts at a time. This is required by the\n[vault-init][vault-init] sidecar service.\n\n1. It gives us consistent naming for referencing the Vault servers (which is\nnice for a workshop).\n\n```text\n./scripts/09-deploy-vault.sh\n```\n\nVault will automatically be initialized and unsealed via the vault-init service.\n\n## 10 Deploy Load Balancer\n\nEven though Vault is deployed, it is not publicly accessible. We need to create\na Kubernetes Service Load Balancer to forward from the IP address reserved in\nthe previous steps to the pods we just created.\n\n```text\n./scripts/10-deploy-lb.sh\n```\n\nThe load balancer listens on port 443 and forwards to port 8200 on the\ncontainers.\n\nFor production-hardened scenarios, you may want to include firewall rules to\nlimit access to Vault at the network layer.\n\n## 11 Setup Comms\n\nLastly, we need to configure our local Vault CLI to communicate with these\nnewly-created Vault servers through the Load Balancer. Since we used custom TLS\ncertificates, we'll need to trust the appropriate CA, etc. This will:\n\n- Set `VAULT_ADDR` to the Load Balancer address\n\n- Set `VAULT_CAPATH` to the path of the CA cert created in previous steps for\n  properly verifying the TLS connection\n\n- Set `VAULT_TOKEN` to the decrypted root token by decrypting it from KMS\n\n```text\n./scripts/11-setup-comms.sh\n```\n\nAt this point, the local Vault CLI is configured to communicate with our Vault\ncluster. Verify by running `vault status`:\n\n```text\nvault status\n```\n\n## 12 Setup Static KV\n\nNext we will explore techniques for retrieving static (i.e. non-expiring)\ncredentials from Vault. The kv secrets engine is commonly used with legacy\napplications which cannot handle graceful restarts or when secrets cannot be\ndynamically generated by Vault.\n\n```text\n./scripts/12-setup-static-kv.sh\n```\n\nThis will:\n\n1. Enable the KV secrets engine\n1. Create a policy to read data from a subpath\n1. Store some static username/password data in the secrets engine\n\nTry reading back the secret by running:\n\n```text\nvault kv get kv/myapp/config\n```\n\nYou can also read the data via a request tool like curl.\n\n```text\ncurl -k -H \"x-vault-token:${VAULT_TOKEN}\" \"${VAULT_ADDR}/v1/kv/myapp/config\"\n```\n\n## 13 Another Cluster\n\nNext we are going to create another Kubernetes cluster. There is no requirement\nthat our Vault servers run under Kubernetes (they could be running on dedicated\nVMs or as a managed service). It is a best practice to treat the Vault server\ncluster as a \"service\" through which other applications and services request\ncredentials. As such, moving forward, the Vault cluster will be treated simply\nas an IP address. We will not leverage K8S for \"discovering\" the Vault cluster,\netc.\n\n**To put it another way, completely forget that Vault is running in Kubernetes.\nIf it helps, think that Vault is running in a PaaS like Heroku instead.**\n\nNext create the Kubernetes cluster where our services will actually run. This is\ncompletely separate from the Vault K8S cluster. In fact, on GCP, is it\nrecommended that you run these in completely separate projects. For the purpose\nof this workshop, we will run them in the same project.\n\n```text\n./scripts/13-create-another-cluster.sh\n```\n\nThis will provision a new Kubernetes cluster named \"my-apps\". We will deploy\nall future apps and services in this cluster.\n\nUnlike the previous cluster, this cluster does not attach a service account.\n\n## 14 Service Account\n\nIn our cluster, services will authenticate to Vault using the [Kubernetes auth\nmethod][vault-k8s-auth-method]. In this model, services present their JWT token\nto Vault as part of an authentication request. Vault takes that signed JWT token\nand, using the token reviewer API, verifies the token is authenticated. If the\nauthentication is successful, Vault generates a token and maps a series of\nconfigured policies onto the token which is returned to the caller.\n\n```text\n./scripts/14-create-service-account.sh\n```\n\nThis will create a dedicated service account named \"vault-auth\" and grant that\nservice account the ability to communicate with the token reviewer API.\n\n## 15 Configure Vault to talk to Kubernetes\n\nNext we need to configure the Vault cluster to talk to our new Kubernetes\ncluster (\"my-apps\"). We will need to give Vault the IP address of the\ncluster, the CA information, and the service account to use for accessing the\ntoken reviewer API.\n\n```text\n./scripts/15-setup-vault-comms-k8s.sh\n```\n\nThis process will:\n\n1. Look up the service account JWT token (this is how Vault will talk to the\nKubernetes API)\n\n1. Extract the Kubernetes host from the local kube configuration (this where\nVault will make API requests)\n\n1. Extract the Kubernetes CA from the local kube configuration (this is how\nVault will authenticate requests)\n\n1. Enable the Kubernetes auth method in Vault\n\n1. Give Vault the service account JWT, host, and CA so that Vault can\ncommunicate with Kubernetes\n\nThere are other techniques for retrieving some of these values, but leveraging\n`kubectl` makes it easy to script.\n\n## 16 Create KV Role\n\nTypically this process is done by a security team or operations team. We need to\nconfigure Vault to map an application or service in Kubernetes to a series of\npolicies in Vault. That way, when an application successfully authenticates to\nVault via its JWT token, Vault knows which policies to assign to the response.\n\n```text\n./scripts/16-create-kv-role.sh\n```\n\nThis will create a role named \"myapp-role\" that permits pods in the \"default\"\nnamespace with the \"default\" service account to receive a Vault token that has\nthe \"myapp-kv-rw\" policy attached.\n\n## 17 Sidecar Static App\n\nThis is one of the most common techniques for injecting Vault secrets into an\napplication.\n\n1. An init container pulls the service account JWT token and performs the auth\nmechanism for that service account. If successful, it stores the resulting\n_Vault_ token in somewhere on a shared volume mount.\n\n1. A tool like [Consul Template][consul-template] runs as the first container.\nThis tool uses the Vault token acquired by the init container and makes the\nappropriate API calls to Vault based off of a template file. The template file\ncan reference one or more Vault credentials. Consul Template writes the rendered\nfile with the secrets from Vault to a shared volume mount which the app reads.\n\n1. The app reads credentials. In this example, our application is a dummy\napplication that just reads the contents of `/etc/secrets/config` repeatedly.\n\n```text\n./scripts/17-run-kv-sidecar.sh\n```\n\nVerify the app is authenticating and retrieving secrets from Vault:\n\n```text\n./scripts/kubectl-logs.sh kv-sidecar\n```\n\n## 18 Setup Dynamic Credentials\n\nNext we configure Vault to generate dynamic credentials. Vault can generate many\ntypes of dynamic credentials like database credentials, certificates, etc. For\nthis example, we will leverage the GCP secrets engine to dynamically generate\nGoogle Cloud Platform CloudSQL MySQL users.\n\n```text\n./scripts/18-setup-dynamic-creds.sh\n```\n\nThis will:\n\n1. Create a CloudSQL database\n\n1. Enable the `database` secrets engine\n\n1. Configure the `database` secrets engine\n\n1. Create a \"role\" which configures the permissions the SQL user has\n\n1. Create a new policy which allows generating these dynamic credentials\n\n1. Update the Vault Kubernetes auth mapping to include this new policy when\nauthenticating\n\nThis process can take up to 10 minutes to complete.\n\n## 19 Sidecar Dynamic App\n\nIn this example, we follow the same pattern as the static KV secrets, but our\nsidecar application will pull dynamic credentials from Vault. In this case, we\nwill be creating a database password.\n\n```text\n./scripts/19-run-db-sidecar.sh\n```\n\nThis also configures a command to run which will signal the application when the\nunderlying service account changes. This is important as we need to notify the\napplication (which is not aware of Vault's existence) that it should reload its\nconfiguration.\n\nVerify the app is authenticating and retrieving secrets from Vault:\n\n```text\n./scripts/kubectl-logs.sh db-sidecar\n```\n\n[cs]: https://cloud.google.com/shell\n[gcs]: https://cloud.google.com/storage\n[gke]: https://cloud.google.com/gke\n[kms]: https://cloud.google.com/kms\n[sdk]: https://cloud.google.com/sdk\n[consul-template]: https://github.com/hashicorp/consul-template\n[vault-init]: https://github.com/sethvargo/vault-init\n[vault-k8s-auth-method]: https://www.vaultproject.io/docs/auth/kubernetes.html\n[vault-prod-hardening]: https://www.vaultproject.io/guides/operations/production.html\n"
  },
  {
    "path": "k8s/db-sidecar.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: db-sidecar\n  labels:\n    app: db-sidecar\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: db-sidecar\n  template:\n    metadata:\n      labels:\n        app: db-sidecar\n    spec:\n      shareProcessNamespace: true\n      volumes:\n      - name: secrets\n        emptyDir: {}\n      - name: vault-tls\n        secret:\n          secretName: vault-tls\n      - name: vault-token\n        emptyDir:\n          medium: Memory\n\n      securityContext:\n        runAsUser: 1000\n        runAsGroup: 1000\n\n      initContainers:\n      # The vault-authenticator container authenticates the container using the\n      # kubernetes auth method and puts the resulting token on the filesystem.\n      - name: vault-authenticator\n        image: sethvargo/vault-kubernetes-authenticator:0.2.0\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - name: vault-token\n          mountPath: /var/run/secrets/vaultproject.io\n        - name: vault-tls\n          mountPath: /etc/vault/tls\n        env:\n        - name: VAULT_ADDR\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: vault_addr\n        - name: VAULT_CACERT\n          value: /etc/vault/tls/ca.crt\n        - name: VAULT_ROLE\n          value: myapp-role\n\n      containers:\n        # The consul-template container will pull secrets from Vault and expose\n        # them as files on disk.\n      - name: consul-template\n        image: hashicorp/consul-template:0.20.0-light\n        imagePullPolicy: IfNotPresent\n        securityContext:\n          capabilities:\n            add: ['SYS_PTRACE']\n        volumeMounts:\n        - name: secrets\n          mountPath: /etc/secrets\n        - name: vault-tls\n          mountPath: /etc/vault/tls\n        - name: vault-token\n          mountPath: /var/run/secrets/vaultproject.io\n        env:\n        - name: VAULT_ADDR\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: vault_addr\n        - name: VAULT_CACERT\n          value: /etc/vault/tls/ca.crt\n        - name: CT_LOCAL_CONFIG\n          value: |\n            vault {\n              vault_agent_token_file = \"/var/run/secrets/vaultproject.io/.vault-token\"\n\n              ssl {\n                ca_cert = \"/etc/vault/tls/ca.crt\"\n              }\n\n              retry {\n                backoff = \"1s\"\n              }\n            }\n\n            template {\n              contents = <<EOH\n                {{- with secret \"database/creds/readonly\" }}\n                username: {{ .Data.username }}\n                password: {{ .Data.password }}\n                {{ end }}\n              EOH\n              destination = \"/etc/secrets/config\"\n              command     = \"/bin/sh -c \\\"kill -HUP $(pidof vault-demo-app) || true\\\"\"\n            }\n\n      - name: app\n        image: sethvargo/vault-demo-app:0.1.0\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - name: secrets\n          mountPath: /etc/secrets\n"
  },
  {
    "path": "k8s/kv-sidecar.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: kv-sidecar\n  labels:\n    app: kv-sidecar\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: kv-sidecar\n  template:\n    metadata:\n      labels:\n        app: kv-sidecar\n    spec:\n      volumes:\n      - name: secrets\n        emptyDir: {}\n      - name: vault-tls\n        secret:\n          secretName: vault-tls\n      - name: vault-token\n        emptyDir:\n          medium: Memory\n\n      securityContext:\n        runAsUser: 1000\n        runAsGroup: 1000\n\n      initContainers:\n      # The vault-authenticator container authenticates the container using the\n      # kubernetes auth method and puts the resulting token on the filesystem.\n      - name: vault-authenticator\n        image: sethvargo/vault-kubernetes-authenticator:0.2.0\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - name: vault-token\n          mountPath: /var/run/secrets/vaultproject.io\n        - name: vault-tls\n          mountPath: /etc/vault/tls\n        env:\n        - name: VAULT_ADDR\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: vault_addr\n        - name: VAULT_CACERT\n          value: /etc/vault/tls/ca.crt\n        - name: VAULT_ROLE\n          value: myapp-role\n\n      containers:\n        # The consul-template container will pull secrets from Vault and expose\n        # them as files on disk.\n      - name: consul-template\n        image: hashicorp/consul-template:0.20.0-light\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - name: secrets\n          mountPath: /etc/secrets\n        - name: vault-tls\n          mountPath: /etc/vault/tls\n        - name: vault-token\n          mountPath: /var/run/secrets/vaultproject.io\n        env:\n        - name: VAULT_ADDR\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: vault_addr\n        - name: VAULT_CACERT\n          value: /etc/vault/tls/ca.crt\n        - name: CT_LOCAL_CONFIG\n          value: |\n            vault {\n              vault_agent_token_file = \"/var/run/secrets/vaultproject.io/.vault-token\"\n\n              ssl {\n                ca_cert = \"/etc/vault/tls/ca.crt\"\n              }\n\n              retry {\n                backoff = \"1s\"\n              }\n            }\n\n            template {\n              contents = <<EOH\n                ---\n                {{- with secret \"kv/myapp/config\" }}\n                username: {{ .Data.username }}\n                password: {{ .Data.password }}\n                {{ end }}\n              EOH\n              destination = \"/etc/secrets/config\"\n            }\n\n      - name: app\n        image: sethvargo/vault-demo-app:0.1.0\n        imagePullPolicy: IfNotPresent\n        volumeMounts:\n        - name: secrets\n          mountPath: /etc/secrets\n"
  },
  {
    "path": "k8s/vault.yaml",
    "content": "---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: vault\n  labels:\n    app: vault\nspec:\n  serviceName: vault\n  replicas: 3\n  selector:\n    matchLabels:\n      app: vault\n  template:\n    metadata:\n      labels:\n        app: vault\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - weight: 60\n            podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values: [\"vault\"]\n              topologyKey: kubernetes.io/hostname\n      terminationGracePeriodSeconds: 10\n      containers:\n      - name: vault-init\n        image: registry.hub.docker.com/sethvargo/vault-init:1.0.0\n        imagePullPolicy: IfNotPresent\n        resources:\n          requests:\n            cpu: \"100m\"\n            memory: \"64Mi\"\n        env:\n        - name: CHECK_INTERVAL\n          value: \"5\"\n        - name: GCS_BUCKET_NAME\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: gcs_bucket_name\n        - name: KMS_KEY_ID\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: kms_key_id\n        - name: VAULT_ADDR\n          value: \"http://127.0.0.1:8200\"\n        - name: VAULT_SECRET_SHARES\n          value: \"1\"\n        - name: VAULT_SECRET_THRESHOLD\n          value: \"1\"\n      - name: vault\n        image: registry.hub.docker.com/library/vault:1.2.2\n        imagePullPolicy: IfNotPresent\n        args: [\"server\"]\n        securityContext:\n          capabilities:\n            add: [\"IPC_LOCK\"]\n        ports:\n        - containerPort: 8200\n          name: vault-port\n          protocol: TCP\n        - containerPort: 8201\n          name: cluster-port\n          protocol: TCP\n        resources:\n          requests:\n            cpu: \"500m\"\n            memory: \"256Mi\"\n        volumeMounts:\n        - name: vault-tls\n          mountPath: /etc/vault/tls\n        env:\n        - name: GCS_BUCKET_NAME\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: gcs_bucket_name\n        - name: KMS_PROJECT\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: kms_project\n        - name: KMS_REGION\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: kms_region\n        - name: KMS_KEY_RING\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: kms_key_ring\n        - name: KMS_CRYPTO_KEY\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: kms_crypto_key\n        - name: LOAD_BALANCER_ADDR\n          valueFrom:\n            configMapKeyRef:\n              name: vault\n              key: load_balancer_address\n        - name: POD_IP_ADDR\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: VAULT_ADDR\n          value: \"http://127.0.0.1:8200\"\n        - name: VAULT_LOCAL_CONFIG\n          value: |\n            api_addr     = \"https://$(LOAD_BALANCER_ADDR)\"\n            cluster_addr = \"https://$(POD_IP_ADDR):8201\"\n\n            log_level = \"warn\"\n\n            ui = true\n\n            seal \"gcpckms\" {\n              project    = \"$(KMS_PROJECT)\"\n              region     = \"$(KMS_REGION)\"\n              key_ring   = \"$(KMS_KEY_RING)\"\n              crypto_key = \"$(KMS_CRYPTO_KEY)\"\n            }\n\n            storage \"gcs\" {\n              bucket     = \"$(GCS_BUCKET_NAME)\"\n              ha_enabled = \"true\"\n            }\n\n            listener \"tcp\" {\n              address     = \"127.0.0.1:8200\"\n              tls_disable = \"true\"\n            }\n\n            listener \"tcp\" {\n              address       = \"$(POD_IP_ADDR):8200\"\n              tls_cert_file = \"/etc/vault/tls/vault.crt\"\n              tls_key_file  = \"/etc/vault/tls/vault.key\"\n\n              tls_disable_client_certs = true\n            }\n        readinessProbe:\n          httpGet:\n            path: /v1/sys/health?standbyok=true\n            port: 8200\n            scheme: HTTPS\n          initialDelaySeconds: 5\n          periodSeconds: 5\n      volumes:\n      - name: vault-tls\n        secret:\n          secretName: vault-tls\n"
  },
  {
    "path": "scripts/00-install-vault.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\necho \"--> Unpacking and installing\"\ndocker run -v $HOME/bin:/software sethvargo/hashicorp-installer vault 1.1.2\nsudo chown $(whoami):$(whoami) $HOME/bin/vault\nsudo chmod +x $HOME/bin/vault\n\necho \"--> Setting PATH\"\nexport PATH=\"${PATH}:${HOME}/bin\"\n\necho \"--> Installing completions\"\nvault -autocomplete-install || true\n\necho \"--> Done!\"\nexec $SHELL\n"
  },
  {
    "path": "scripts/01-enable-services.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\ngcloud services enable \\\n  --async \\\n  --project=\"$(google-project)\" \\\n  cloudapis.googleapis.com \\\n  cloudkms.googleapis.com \\\n  cloudresourcemanager.googleapis.com \\\n  cloudshell.googleapis.com \\\n  container.googleapis.com \\\n  containerregistry.googleapis.com \\\n  iam.googleapis.com\n"
  },
  {
    "path": "scripts/02-setup-storage.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\ngsutil mb -p \"$(google-project)\" \"gs://$(google-project)-vault-storage\"\n"
  },
  {
    "path": "scripts/03-setup-kms.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\ngcloud kms keyrings create vault \\\n  --project=\"$(google-project)\" \\\n  --location=\"$(google-region)\"\n\ngcloud kms keys create vault-init \\\n  --project=\"$(google-project)\" \\\n  --location=\"$(google-region)\" \\\n  --keyring=\"vault\" \\\n  --purpose=\"encryption\"\n"
  },
  {
    "path": "scripts/04-create-iam-service-account.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nSERVICE_ACCOUNT=\"vault-server@$(google-project).iam.gserviceaccount.com\"\n\n# Create the service account\ngcloud iam service-accounts create vault-server \\\n  --project=\"$(google-project)\" \\\n  --display-name=\"vault server\"\n\n# (Optional) grant the service account the ability to generate new service\n# accounts. This is required to use the Vault GCP secrets engine, otherwise it\n# can be omitted.\nROLES=(\n  \"roles/resourcemanager.projectIamAdmin\"\n  \"roles/iam.serviceAccountAdmin\"\n  \"roles/iam.serviceAccountKeyAdmin\"\n  \"roles/iam.serviceAccountTokenCreator\"\n  \"roles/iam.serviceAccountUser\"\n  \"roles/viewer\"\n)\nfor role in \"${ROLES[@]}\"; do\n  gcloud projects add-iam-policy-binding \"$(google-project)\" \\\n    --member \"serviceAccount:${SERVICE_ACCOUNT}\" \\\n    --role \"${role}\"\ndone\n\n# Grant the service account the ability to read and write objects in our storage\n# bucket\ngsutil iam ch \\\n  \"serviceAccount:${SERVICE_ACCOUNT}:objectAdmin\" \\\n  \"serviceAccount:${SERVICE_ACCOUNT}:legacyBucketReader\" \\\n  \"gs://$(google-project)-vault-storage\"\n\n# Grant the service account the ability to access the Cloud KMS crypto key\ngcloud kms keys add-iam-policy-binding vault-init \\\n  --project=\"$(google-project)\" \\\n  --location=\"$(google-region)\" \\\n  --keyring=\"vault\" \\\n  --member=\"serviceAccount:${SERVICE_ACCOUNT}\" \\\n  --role=\"roles/cloudkms.cryptoKeyEncrypterDecrypter\"\n"
  },
  {
    "path": "scripts/05-create-k8s-cluster.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nSERVICE_ACCOUNT=\"vault-server@$(google-project).iam.gserviceaccount.com\"\n\ngcloud container clusters create vault \\\n  --project=\"$(google-project)\" \\\n  --cluster-version=\"$(gke-latest-master-version)\" \\\n  --enable-autorepair \\\n  --enable-autoupgrade \\\n  --enable-ip-alias \\\n  --machine-type=\"n1-standard-2\" \\\n  --node-version=\"$(gke-latest-node-version)\" \\\n  --num-nodes=\"1\" \\\n  --region=\"$(google-region)\" \\\n  --scopes=\"cloud-platform\" \\\n  --service-account=\"${SERVICE_ACCOUNT}\"\n"
  },
  {
    "path": "scripts/06-create-public-ip.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\ngcloud compute addresses create vault \\\n  --project=\"$(google-project)\" \\\n  --region=\"$(google-region)\"\n"
  },
  {
    "path": "scripts/07-create-certs.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nLB_IP=\"$(vault-lb-ip)\"\n\nDIR=\"$(pwd)/tls\"\n\nrm -rf \"${DIR}\"\nmkdir -p \"${DIR}\"\n\n# Create the conf file\ncat > \"${DIR}/openssl.cnf\" << EOF\n[req]\ndefault_bits = 2048\nencrypt_key  = no\ndefault_md   = sha256\nprompt       = no\nutf8         = yes\n\ndistinguished_name = req_distinguished_name\nreq_extensions     = v3_req\n\n[req_distinguished_name]\nC  = US\nST = California\nL  = The Cloud\nO  = Demo\nCN = vault\n\n[v3_req]\nbasicConstraints     = CA:FALSE\nsubjectKeyIdentifier = hash\nkeyUsage             = digitalSignature, keyEncipherment\nextendedKeyUsage     = clientAuth, serverAuth\nsubjectAltName       = @alt_names\n\n[alt_names]\nIP.1  = ${LB_IP}\nDNS.1 = vault.default.svc.cluster.local\nEOF\n\n# Generate Vault's certificates and a CSR\nopenssl genrsa -out \"${DIR}/vault.key\" 2048\n\nopenssl req \\\n  -new -key \"${DIR}/vault.key\" \\\n  -out \"${DIR}/vault.csr\" \\\n  -config \"${DIR}/openssl.cnf\"\n\n# Create our CA\nopenssl req \\\n  -new \\\n  -newkey rsa:2048 \\\n  -days 120 \\\n  -nodes \\\n  -x509 \\\n  -subj \"/C=US/ST=California/L=The Cloud/O=Vault CA\" \\\n  -keyout \"${DIR}/ca.key\" \\\n  -out \"${DIR}/ca.crt\"\n\n# Sign CSR with our CA\nopenssl x509 \\\n  -req \\\n  -days 120 \\\n  -in \"${DIR}/vault.csr\" \\\n  -CA \"${DIR}/ca.crt\" \\\n  -CAkey \"${DIR}/ca.key\" \\\n  -CAcreateserial \\\n  -extensions v3_req \\\n  -extfile \"${DIR}/openssl.cnf\" \\\n  -out \"${DIR}/vault.crt\"\n\n# Export combined certs for vault\ncat \"${DIR}/vault.crt\" \"${DIR}/ca.crt\" > \"${DIR}/vault-combined.crt\"\n"
  },
  {
    "path": "scripts/08-setup-config.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nDIR=\"$(pwd)/tls\"\n\nkubectl create configmap vault \\\n  --cluster=\"$(gke-cluster-name \"vault\")\" \\\n  --from-literal=\"load_balancer_address=$(vault-lb-ip)\" \\\n  --from-literal=\"gcs_bucket_name=$(google-project)-vault-storage\" \\\n  --from-literal=\"kms_project=$(google-project)\" \\\n  --from-literal=\"kms_region=$(google-region)\" \\\n  --from-literal=\"kms_key_ring=vault\" \\\n  --from-literal=\"kms_crypto_key=vault-init\" \\\n  --from-literal=\"kms_key_id=projects/$(google-project)/locations/$(google-region)/keyRings/vault/cryptoKeys/vault-init\"\n\nkubectl create secret generic vault-tls \\\n  --cluster=\"$(gke-cluster-name \"vault\")\" \\\n  --from-file=\"${DIR}/ca.crt\" \\\n  --from-file=\"vault.crt=${DIR}/vault-combined.crt\" \\\n  --from-file=\"vault.key=${DIR}/vault.key\"\n"
  },
  {
    "path": "scripts/09-deploy-vault.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nkubectl apply \\\n  --cluster=\"$(gke-cluster-name \"vault\")\" \\\n  --filename=\"k8s/vault.yaml\"\n"
  },
  {
    "path": "scripts/10-deploy-lb.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nkubectl apply \\\n  --cluster=\"$(gke-cluster-name \"vault\")\" \\\n  --filename=-<<EOF\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: vault\n  labels:\n    app: vault\nspec:\n  type: LoadBalancer\n  loadBalancerIP: $(vault-lb-ip)\n  externalTrafficPolicy: Local\n  selector:\n    app: vault\n  ports:\n  - name: vault-port\n    port: 443\n    targetPort: 8200\n    protocol: TCP\nEOF\n"
  },
  {
    "path": "scripts/11-setup-comms.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nexport VAULT_CACERT=\"$(pwd)/tls/ca.crt\"\nexport VAULT_ADDR=\"https://$(vault-lb-ip):443\"\nexport VAULT_TOKEN=\"$(gsutil cat \"gs://$(google-project)-vault-storage/root-token.enc\" | \\\n  base64 --decode | \\\n  gcloud kms decrypt \\\n    --project=\"$(google-project)\" \\\n    --location=\"$(google-region)\" \\\n    --keyring=\"vault\" \\\n    --key=\"vault-init\" \\\n    --ciphertext-file - \\\n    --plaintext-file -)\"\n\nexport PATH=\"${PATH}:${HOME}/bin\"\n\nalias vualt=vault\n\nexec $SHELL\n"
  },
  {
    "path": "scripts/12-setup-static-kv.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\n# Enable kv secrets engine - this used to be enabled by default at secret/, but\n# that's not the case anymore.\nvault secrets enable kv\n\n# This creates a policy that allows reading, writing, and deleting from anything\n# under \"myapp\" in the kv secrets engine just created. This policy still must\n# be attached to tokens in order to receive the permission(s).\nvault policy write myapp-kv-rw - <<EOH\npath \"kv/myapp/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\"]\n}\nEOH\n\n# This stores a static credential at secrets/myapp/config with a username and\n# password for connecting to our myapp application.\nvault kv put kv/myapp/config \\\n  ttl=\"30s\" \\\n  username=\"appuser\" \\\n  password=\"suP3rsec(et!\"\n"
  },
  {
    "path": "scripts/13-create-another-cluster.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\n# Create a cluster to do process namespace sharing\ngcloud container clusters create my-apps \\\n  --project=\"$(google-project)\" \\\n  --cluster-version=\"$(gke-latest-master-version)\" \\\n  --enable-autorepair \\\n  --enable-autoupgrade \\\n  --enable-ip-alias \\\n  --machine-type=n1-standard-2 \\\n  --node-version=\"$(gke-latest-node-version)\" \\\n  --num-nodes=1 \\\n  --region=\"$(google-region)\" \\\n  --scopes=\"cloud-platform\"\n"
  },
  {
    "path": "scripts/14-create-service-account.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nkubectl create serviceaccount vault-auth \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\"\n\nkubectl apply \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --filename=-<<EOH\n---\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRoleBinding\nmetadata:\n  name: role-tokenreview-binding\n  namespace: default\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: vault-auth\n  namespace: default\nEOH\n"
  },
  {
    "path": "scripts/15-setup-vault-comms-k8s.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nDIR=\"$(pwd)/tls\"\n\n# Get the name of the secret corresponding to the service account\nSECRET_NAME=\"$(kubectl get serviceaccount vault-auth \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  -o go-template='{{ (index .secrets 0).name }}')\"\n\n# Get the actual token reviewer account\nTR_ACCOUNT_TOKEN=\"$(kubectl get secret ${SECRET_NAME} \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  -o go-template='{{ .data.token }}' | base64 --decode)\"\n\n# Get the host for the cluster (IP address)\nK8S_HOST=\"$(kubectl config view --raw \\\n  -o go-template=\"{{ range .clusters }}{{ if eq .name \\\"$(gke-cluster-name \"my-apps\")\\\" }}{{ index .cluster \\\"server\\\" }}{{ end }}{{ end }}\")\"\n\n# Get the CA for the cluster\nK8S_CACERT=\"$(kubectl config view --raw \\\n  -o go-template=\"{{ range .clusters }}{{ if eq .name \\\"$(gke-cluster-name \"my-apps\")\\\" }}{{ index .cluster \\\"certificate-authority-data\\\" }}{{ end }}{{ end }}\" | base64 --decode)\"\n\n# Enable the Kubernetes auth method\nvault auth enable kubernetes\n\n# Configure Vault to talk to our Kubernetes host with the cluster's CA and the\n# correct token reviewer JWT token\nvault write auth/kubernetes/config \\\n  kubernetes_host=\"${K8S_HOST}\" \\\n  kubernetes_ca_cert=\"${K8S_CACERT}\" \\\n  token_reviewer_jwt=\"${TR_ACCOUNT_TOKEN}\"\n\n# Create a config map to store the vault address\nkubectl create configmap vault \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --from-literal \"vault_addr=https://$(vault-lb-ip)\"\n\n# Create a secret for our CA\nkubectl create secret generic vault-tls \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --from-file \"${DIR}/ca.crt\"\n"
  },
  {
    "path": "scripts/16-create-kv-role.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nvault write auth/kubernetes/role/myapp-role \\\n  bound_service_account_names=\"default\" \\\n  bound_service_account_namespaces=\"default\" \\\n  policies=\"default,myapp-kv-rw\" \\\n  ttl=\"15m\"\n"
  },
  {
    "path": "scripts/17-run-kv-sidecar.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nkubectl apply \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --filename=\"k8s/kv-sidecar.yaml\"\n"
  },
  {
    "path": "scripts/18-setup-dynamic-creds.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\n# Create CloudSQL instance\ngcloud sql instances create my-instance \\\n  --project=\"$(google-project)\" \\\n  --activation-policy=\"always\" \\\n  --authorized-networks=\"0.0.0.0/0\" \\\n  --database-version=\"MYSQL_5_7\" \\\n  --no-backup \\\n  --region=\"$(google-region)\" \\\n  --tier=\"db-n1-standard-1\"\n\nINSTANCE_IP=\"$(gcloud sql instances describe my-instance --project=\"$(google-project)\" --format='value(ipAddresses[0].ipAddress)')\"\n\n# Change password\ngcloud sql users set-password root \\\n  --project=\"$(google-project)\" \\\n  --host=\"%\" \\\n  --instance=\"my-instance\" \\\n  --password=\"my-password\"\n\n# Enable the gcp secrets engine\nvault secrets enable database\n\n# Configure the database secrets engine TTLs\nvault write database/config/my-cloudsql-db \\\n  plugin_name=\"mysql-database-plugin\" \\\n  connection_url=\"{{username}}:{{password}}@tcp(${INSTANCE_IP}:3306)/\" \\\n  allowed_roles=\"readonly\" \\\n  username=\"root\" \\\n  password=\"my-password\"\n\n# Rotate the root cred\nvault write -f database/rotate-root/my-cloudsql-db\n\n# Create a role which will create a readonly user\nvault write database/roles/readonly \\\n  db_name=\"my-cloudsql-db\" \\\n  creation_statements=\"CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'%';\" \\\n  default_ttl=\"1h\" \\\n  max_ttl=\"24h\"\n\n# Create a new policy which allows generating these dynamic credentials\nvault policy write myapp-db-r -<<EOF\npath \"database/creds/readonly\" {\n  capabilities = [\"read\"]\n}\nEOF\n\n# Update the Vault kubernetes auth mapping to include this new policy\nvault write auth/kubernetes/role/myapp-role \\\n  bound_service_account_names=\"default\" \\\n  bound_service_account_namespaces=\"default\" \\\n  policies=\"default,myapp-kv-rw,myapp-db-r\" \\\n  ttl=\"15m\"\n"
  },
  {
    "path": "scripts/19-run-db-sidecar.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nkubectl apply \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --filename=\"k8s/db-sidecar.yaml\"\n"
  },
  {
    "path": "scripts/20-cleanup.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nSERVICE_ACCOUNT=\"vault-server@$(google-project).iam.gserviceaccount.com\"\n\nkubectl delete deployment kv-sidecar \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --force \\\n  --grace-period=0\n\nkubectl delete deployment sa-sidecar \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --force \\\n  --grace-period=0\n\ngcloud container clusters delete my-apps \\\n  --async \\\n  --quiet \\\n  --project=\"$(google-project)\" \\\n  --region=\"$(google-region)\"\n\nvault lease revoke -prefix gcp/\n\nkubectl delete service vault \\\n  --cluster=\"$(gke-cluster-name \"vault\")\"\n\nkubectl delete statefulsets vault \\\n  --cluster=\"$(gke-cluster-name \"vault\")\" \\\n  --grace-period=0 \\\n  --force\n\ngcloud container clusters delete vault \\\n  --async \\\n  --quiet \\\n  --project=\"$(google-project)\" \\\n  --region=\"$(google-region)\"\n\ngcloud compute addresses delete vault \\\n  --quiet \\\n  --project=\"$(google-project)\" \\\n  --region=\"$(google-region)\"\n\ngcloud iam service-accounts delete \"${SERVICE_ACCOUNT}\" \\\n  --quiet \\\n  --project=\"$(google-project)\"\n\ngsutil -m rm -rf \"gs://$(google-project)-vault-storage\"\ngsutil rb -f \"gs://$(google-project)-vault-storage\"\n"
  },
  {
    "path": "scripts/__helpers.sh",
    "content": "# google-project returns the name of the current project, accounting for a\n# variety of common environments. If no project is found in any of the common\n# places, an error is returned.\ngoogle-project() {\n  (\n    set -Eeuo pipefail\n\n    local project=\"${PROJECT:-${GOOGLE_PROJECT:-${GOOGLE_CLOUD_PROJECT:-${DEVSHELL_PROJECT_ID:-}}}}\"\n    if [ -z \"${project:-}\" ]; then\n      echo \"Missing project ID. Please set PROJECT, GOOGLE_PROJECT, or\"\n      echo \"GOOGLE_CLOUD_PROJECT to the ID of your project to continue:\"\n      echo \"\"\n      echo \"    export GOOGLE_CLOUD_PROJECT=$(whoami)-foobar123\"\n      echo \"\"\n      return 127\n    fi\n    echo \"${project}\"\n  )\n}\n\n# google-region returns the region in which resources should be created. This\n# variable must be changed before running any commands.\ngoogle-region() {\n  (\n    echo \"us-east4\"\n  )\n}\n\n# gke-cluster-name is the name of the cluster for the given suffix.\ngke-cluster-name() {\n  (\n    set -Eeuo pipefail\n\n    echo \"gke_$(google-project)_$(google-region)_${1}\"\n  )\n}\n\n# gke-latest-master-version returns the latest GKE master version.\ngke-latest-master-version() {\n  (\n    set -Eeuo pipefail\n\n    gcloud container get-server-config \\\n      --project=\"$(google-project)\" \\\n      --region=\"$(google-region)\" \\\n      --format='value(validMasterVersions[0])' \\\n      2>/dev/null\n  )\n}\n\n# gke-latest-node-version returns the latest GKE node version.\ngke-latest-node-version() {\n  (\n    set -Eeuo pipefail\n\n    gcloud container get-server-config \\\n      --project=\"$(google-project)\" \\\n      --region=\"$(google-region)\" \\\n      --format='value(validNodeVersions[0])' \\\n      2>/dev/null\n  )\n}\n\nvault-lb-ip() {\n  (\n    set -Eeuo pipefail\n\n    gcloud compute addresses describe vault \\\n      --project=\"$(google-project)\" \\\n      --region=\"$(google-region)\" \\\n      --format='value(address)'\n  )\n}\n"
  },
  {
    "path": "scripts/kubectl-logs.sh",
    "content": "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nsource \"$(cd \"$(dirname \"${0}\")\" &>/dev/null && pwd)/__helpers.sh\"\n\nif [ -z \"${1:-}\" ]; then\n  echo \"Missing pod name!\"\n  exit 1\nfi\n\nPOD=\"$(kubectl get pods \\\n  --cluster=\"$(gke-cluster-name \"my-apps\")\" \\\n  --selector=\"app=${1}\" \\\n  -o=jsonpath='{.items[0].metadata.name}')\"\n\nkubectl logs \"${POD}\" -c \"app\" \\\n  --context=\"$(gke-cluster-name \"my-apps\")\" \\\n"
  }
]