diff --git a/USAGE.md b/USAGE.md index fb4b3e61..e1363bf6 100644 --- a/USAGE.md +++ b/USAGE.md @@ -55,8 +55,6 @@ The above command is an example of how to run the frontend. This creates a UI av ## Tornjak -WARNING: This is currently an experimental feature. - This container may be used as an alternative to having a frontend and backend container separately. The backend is configured exactly as the [Tornjak backend] with container arguments, and the frontend is configured exactly as the [Tornjak frontend] with container environment variables. An example command: diff --git a/docs/tornjak-quickstart.md b/docs/quickstart/README.md similarity index 52% rename from docs/tornjak-quickstart.md rename to docs/quickstart/README.md index 06fc1c6b..3da154d3 100644 --- a/docs/tornjak-quickstart.md +++ b/docs/quickstart/README.md @@ -1,20 +1,33 @@ # Tornjak simple deployment with SPIRE k8s quickstart -In this doc, we will show how to configure Tornjak with a SPIRE deployment using the SPIRE k8s quickstart tutorial. As we will see, this is as simple as three steps: -1. setting up -2. creating a k8s ConfigMap for storing the Tornjak configuration -3. editing the SPIRE server statefulset to use the Tornjak compatible image and pass the ConfigMap as an argument. +In this tutorial, we will show how to configure Tornjak with a SPIRE deployment using the SPIRE k8s quickstart tutorial. This is heavily inspired by the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/). -## Step 1: Setup SPIRE k8s quickstart tutorial (optional) +This tutorial will get you up and running with a local deployment of SPIRE and Tornjak in three simple steps: setting up the deployment files, deployment, and connecting to Tornjak. -For this tutorial we will utilize the SPIRE k8s quickstart deployment for a SPIRE deployment. If you have a SPIRE deployment set up already, you may skip this step and go ahead to the [section: configuring Tornjak](#step-2-configuring-tornjak). If not we will use the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/). +Contents +1. [Requirements](#step-0-requirements) +2. [Setup of Deployment files](#step-1-setup-quickstart-tutorial) +3. [Deployment of SPIRE and co-located Tornjak](#step-2-deployment) +4. [Connecting to Tornjak](#step-3-configuring-access-to-tornjak) +5. [Cleanup](#cleanup) + +## Step 0: Requirements + +We tested this quickstart with the following requirements: + - [Minikube](https://minikube.sigs.k8s.io/docs/start/) version 1.12.0 + - [Docker](https://docs.docker.com/get-docker/) version 20.10.23 + +## Step 1: Setup quickstart tutorial ### Setting up k8s -For this tutorial, we will use minikube, if you have an existing kubernetes cluster, feel free to use that. +For this tutorial, we will use minikube. If you have an existing kubernetes cluster, feel free to use that. + +```console +minikube start +``` ``` -➜ ~ minikube start 😄 minikube v1.12.0 on Darwin 11.2 🎉 minikube 1.18.1 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.18.1 💡 To disable this notice, run: 'minikube config set WantUpdateNotification false' @@ -25,131 +38,35 @@ For this tutorial, we will use minikube, if you have an existing kubernetes clus 🔎 Verifying Kubernetes components... 🌟 Enabled addons: default-storageclass, storage-provisioner 🏄 Done! kubectl is now configured to use "minikube" - -➜ ~ kubectl get nodes -NAME STATUS ROLES AGE VERSION -minikube Ready master 79s v1.18.3 ``` - -### Setting up the SPIRE deployment - -Next, we will follow the steps from the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/), for the most accurate information, follow the instructions from the page to get your SPIRE deployment set up. Follow through with the tutorial till you get to the end, but do not tear down the components! The output would look like the following: - +```console +kubectl get nodes ``` -➜ ~ git clone https://github.com/spiffe/spire-tutorials.git -Cloning into 'spire-tutorials'... -remote: Enumerating objects: 65, done. -remote: Counting objects: 100% (65/65), done. -remote: Compressing objects: 100% (49/49), done. -remote: Total 725 (delta 20), reused 52 (delta 16), pack-reused 660 -Receiving objects: 100% (725/725), 1008.46 KiB | 9.08 MiB/s, done. -Resolving deltas: 100% (327/327), done. - -➜ ~ cd spire-tutorials/k8s/quickstart -➜ quickstart git:(master) kubectl apply -f spire-namespace.yaml -namespace/spire created - -➜ quickstart git:(master) kubectl apply \ - -f server-account.yaml \ - -f spire-bundle-configmap.yaml \ - -f server-cluster-role.yaml -serviceaccount/spire-server created -configmap/spire-bundle created -clusterrole.rbac.authorization.k8s.io/spire-server-trust-role created -clusterrolebinding.rbac.authorization.k8s.io/spire-server-trust-role-binding created - -➜ quickstart git:(master) kubectl apply \ - -f server-configmap.yaml \ - -f server-statefulset.yaml \ - -f server-service.yaml -configmap/spire-server created -statefulset.apps/spire-server created -service/spire-server created - -➜ quickstart git:(master) kubectl get statefulset --namespace spire -NAME READY AGE -spire-server 1/1 26s ``` - -### Deploying the agent and creating test entries - +NAME STATUS ROLES AGE VERSION +minikube Ready master 79s v1.18.3 ``` -➜ quickstart git:(master) kubectl apply \ - -f agent-account.yaml \ - -f agent-cluster-role.yaml -serviceaccount/spire-agent created -clusterrole.rbac.authorization.k8s.io/spire-agent-cluster-role created -clusterrolebinding.rbac.authorization.k8s.io/spire-agent-cluster-role-binding created - -➜ quickstart git:(master) kubectl apply \ - -f agent-configmap.yaml \ - -f agent-daemonset.yaml -configmap/spire-agent created -daemonset.apps/spire-agent created - -➜ quickstart git:(master) kubectl get daemonset --namespace spire -NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE -spire-agent 1 1 1 1 1 19s -➜ quickstart git:(master) kubectl exec -n spire spire-server-0 -- \ - /opt/spire/bin/spire-server entry create \ - -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \ - -selector k8s_sat:cluster:demo-cluster \ - -selector k8s_sat:agent_ns:spire \ - -selector k8s_sat:agent_sa:spire-agent \ - -node -Entry ID : 03d0ec2b-54b7-4340-a0b9-d3b2cf1b041a -SPIFFE ID : spiffe://example.org/ns/spire/sa/spire-agent -Parent ID : spiffe://example.org/spire/server -Revision : 0 -TTL : default -Selector : k8s_sat:agent_ns:spire -Selector : k8s_sat:agent_sa:spire-agent -Selector : k8s_sat:cluster:demo-cluster +### Obtaining the Deployment Files -➜ quickstart git:(master) kubectl exec -n spire spire-server-0 -- \ - /opt/spire/bin/spire-server entry create \ - -spiffeID spiffe://example.org/ns/default/sa/default \ - -parentID spiffe://example.org/ns/spire/sa/spire-agent \ - -selector k8s:ns:default \ - -selector k8s:sa:default -Entry ID : 11a367ab-7095-4390-ab89-34dea5fddd61 -SPIFFE ID : spiffe://example.org/ns/default/sa/default -Parent ID : spiffe://example.org/ns/spire/sa/spire-agent -Revision : 0 -TTL : default -Selector : k8s:ns:default -Selector : k8s:sa:default - -➜ quickstart git:(master) kubectl apply -f client-deployment.yaml -deployment.apps/client created - -➜ quickstart git:(master) kubectl exec -it $(kubectl get pods -o=jsonpath='{.items[0].metadata.name}' \ - -l app=client) -- /opt/spire/bin/spire-agent api fetch -socketPath /run/spire/sockets/agent.sock -Received 1 svid after 8.8537ms - -SPIFFE ID: spiffe://example.org/ns/default/sa/default -SVID Valid After: 2021-04-06 20:13:02 +0000 UTC -SVID Valid Until: 2021-04-06 21:13:12 +0000 UTC -CA #1 Valid After: 2021-04-06 20:12:20 +0000 UTC -CA #1 Valid Until: 2021-04-07 20:12:30 +0000 UTC +To obtain the relevant files, clone our git repository and cd into the correct directory: -/opt/spire # +```console +git clone https://github.com/spiffe/tornjak.git +cd docs/quickstart ``` +Notice, the files in this directory are largely the same files as provided by the [SPIRE quickstart for Kubernetes](https://spiffe.io/docs/latest/try/getting-started-k8s/). However, there are some minor key differences. Notice the new ConfigMap file: -## Step 2: Configuring Tornjak - -Now that we have the SPIRE deployment set up, it should be fairly simple to use Tornjak. - -### Creating the Tornjak ConfigMap +```console +cat tornjak-configmap.yaml +``` -We first need to create the ConfigMap. We can create a new file: +This configmap has contents to configure the Tornjak backend: ``` -➜ quickstart git:(master) cat tornjak-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: @@ -172,126 +89,41 @@ data: } ``` -### Updating the SPIRE Server statefulset -Next, we need to update the deployment of the statefulset to run Tornjak. These steps will be different depending on what architecture makes sense for you. Note we have deprecated support of the architecture where parts of Tornjak run on the same container as SPIRE. - -Currently, we support two sidecar architectures: +More information on this config file format can be found in [our config documentation](../config-tornjak-agent.md). -1. Only the Tornjak backend is run as a sidecar container that exposes only one port (to communicate with the Tornjak backend). It requires more deployment steps to deploy or use the frontend. However, this deployment type is fully-supported, has a smaller sidecar image without the frontend components, and ensures that the frontend and backend share no memory. -2. The frontend and backend run in the same container that exposes two separate ports (one frontend and one backend). This is experimental and not ready for production, but is useful for getting started with Tornjak with minimal deployment steps. +Additionally, we have sample server-statefulset files in the directory `server-statefulset-examples`. We will copy one of them in depending on which deployment scheme you would like. +### Choosing the Statefulset Deployment -
[Click] For the Tornjak-backend wrapped with the SPIRE server. (WARNING: CURRENTLY DEPRECATED) -The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: - -``` -➜ quickstart git:(master) cat server-statefulset.yaml -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: spire-server - namespace: spire - labels: - app: spire-server -spec: - replicas: 1 - selector: - matchLabels: - app: spire-server - serviceName: spire-server - template: - metadata: - namespace: spire - labels: - app: spire-server - spec: - serviceAccountName: spire-server - containers: - - name: spire-server - image: ghcr.io/spiffe/tornjak-be-spire-server:1.x.x # 👈 NOTE YOUR OWN SPIRE VERSION - imagePullPolicy: Always - args: - - -config - - /run/spire/config/server.conf - - -tornjak-config # 👈 ADDITIONAL ARGUMENT - - /run/spire/tornjak-config/server.conf # 👈 ADDITIONAL ARGUMENT - ports: - - containerPort: 8081 - volumeMounts: - - name: spire-config - mountPath: /run/spire/config - readOnly: true - - name: tornjak-config # 👈 ADDITIONAL VOLUME - mountPath: /run/spire/tornjak-config # 👈 ADDITIONAL VOLUME - readOnly: true # 👈 ADDITIONAL VOLUME - - name: spire-data - mountPath: /run/spire/data - readOnly: false - livenessProbe: - httpGet: - path: /live - port: 8080 - failureThreshold: 2 - initialDelaySeconds: 15 - periodSeconds: 60 - timeoutSeconds: 3 - readinessProbe: - httpGet: - path: /ready - port: 8080 - initialDelaySeconds: 5 - periodSeconds: 5 - volumes: - - name: spire-config - configMap: - name: spire-server - - name: tornjak-config # 👈 ADDITIONAL VOLUME - configMap: # 👈 ADDITIONAL VOLUME - name: tornjak-agent # 👈 ADDITIONAL VOLUME - volumeClaimTemplates: - - metadata: - name: spire-data - namespace: spire - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi -``` +These steps will be different depending on what deployment scheme makes sense for you. Note we have deprecated support of the use case where parts of Tornjak run on the same container as SPIRE. -Note that there are four key differences in this StatefulSet file from that in the SPIRE quickstart: +Currently, we support two deployment schemes: -1. The image name is changed to this one that contains a SPIRE server and the Tornjak backend. -2. There is an additional argument for the Tornjak config. -3. We create a volume named `tornjak-config` that reads from the ConfigMap `tornjak-agent`. -4. We create a volume mount that mounts the `tornjak-config` volume to a path in the container. +1. Only the Tornjak backend (to make Tornjak API calls) is run as a separate container on the same pod that exposes only one port (to communicate with the Tornjak backend). It requires more deployment steps to deploy or use the frontend. However, this deployment type is fully-supported, has a smaller sidecar image without the frontend components, and ensures that the frontend and backend share no memory. +2. The Tornjak frontend (UI) and backend run in the same container that exposes two separate ports (one frontend and one backend). This is useful for getting started with Tornjak with minimal deployment steps. -This is all done specifically to pass the Tornjak config file as an argument to the container. +Choose one of the below to easily copy in the right server-statefulset file for you. -
+
🔴 [Click] For the deployment of the Tornjak backend (API) and frontend (UI) (our default deployment recommended to those getting started) -
[Click] For the Tornjak-backend sidecar implementation +This has the same architecture as deploying with just a Tornjak backend, but with an additional Tornjak frontend process deployed in the same container. This will expose two ports: one for the frontend and one for the backend. There is an additional requirement to mount the SPIRE server socket and make it accessible to the Tornjak backend container. -The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: +The relevant file is called `tornjak-sidecar-server-statefulset.yaml` within the examples directory. Please copy to the relevant file as follows: +```console +cp server-statefulset-examples/tornjak-sidecar-server-statefulset.yaml server-statefulset.yaml ``` -➜ quickstart git:(master) wget -O server-statefulset.yaml https://raw.githubusercontent.com/spiffe/tornjak/main/examples/deployment_sidecar.yaml ---2023-03-08 12:18:01-- https://raw.githubusercontent.com/spiffe/tornjak/main/examples/deployment_sidecar.yaml -Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ... -Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected. -HTTP request sent, awaiting response... 200 OK -Length: 641 [text/plain] -Saving to: 'server-statefulset.yaml' -server-statefulset.yaml 100%[==========================================================>] 641 --.-KB/s in 0s +The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: -2023-03-08 12:18:01 (27.8 MB/s) - 'server-statefulset.yaml' saved [641/641] +```console +cat server-statefulset.yaml +``` -➜ quickstart git:(master) cat server-statefulset.yaml +``` apiVersion: apps/v1 kind: StatefulSet metadata: @@ -327,7 +159,7 @@ spec: - name: spire-data mountPath: /run/spire/data readOnly: false - - name: socket # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME livenessProbe: httpGet: @@ -343,13 +175,20 @@ spec: port: 8080 initialDelaySeconds: 5 periodSeconds: 5 - - name: tornjak-backend ### 👈 BEGIN ADDITIONAL CONTAINER ### - image: ghcr.io/spiffe/tornjak-be:latest + ### 👈 BEGIN ADDITIONAL CONTAINER ### + - name: tornjak + image: ghcr.io/spiffe/tornjak:latest + imagePullPolicy: Always args: - - --config + - -config - /run/spire/config/server.conf - - --tornjak-config + - -tornjak-config - /run/spire/tornjak-config/server.conf + env: + - name: REACT_APP_API_SERVER_URI + value: http://localhost:10000 + - name: NODE_OPTIONS + value: --openssl-legacy-provider ports: - containerPort: 8081 volumeMounts: @@ -363,16 +202,17 @@ spec: mountPath: /run/spire/data readOnly: false - name: socket - mountPath: /tmp/spire-server/private ### 👈 END ADDITIONAL CONTAINER ### + mountPath: /tmp/spire-server/private + ### 👈 END ADDITIONAL CONTAINER ### volumes: - name: spire-config configMap: name: spire-server - - name: tornjak-config # 👈 ADDITIONAL VOLUME - configMap: # 👈 ADDITIONAL VOLUME + - name: tornjak-config # 👈 ADDITIONAL VOLUME + configMap: # 👈 ADDITIONAL VOLUME name: tornjak-agent # 👈 ADDITIONAL VOLUME - - name: socket # 👈 ADDITIONAL VOLUME - emptyDir: {} # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME + emptyDir: {} # 👈 ADDITIONAL VOLUME volumeClaimTemplates: - metadata: name: spire-data @@ -383,28 +223,35 @@ spec: resources: requests: storage: 1Gi -``` - -Note that there are three key differences in this StatefulSet file from that in the SPIRE quickstart: +``` -1. There is a new container in the pod named tornjak-backend. -3. We create a volume named `tornjak-config` that reads from the ConfigMap `tornjak-agent`. -4. We create a volume named `test-socket` so that the containers may communicate. +Note that there are three key differences in the StatefulSet file from that in the SPIRE quickstart: -This is all done specifically to pass the Tornjak config file as an argument to the container and to allow communication between Tornjak and SPIRE. +1. There is a new container in the pod named `tornjak`. + 1. This container uses environment variables to configure the Frontend. + 1. This container uses arguments to pass arguments to the Backend. +2. We create a volume named tornjak-config that reads from the ConfigMap `tornjak-agent`. +3. We create a volume named `test-socket` so that the containers may communicate
-
[Click] For the Tornjak-backend + frontend sidecar implementation (EXPERIMENTAL) - -This has the same architecture as deploying with just a Tornjak backend, but with an additional Tornjak frontend process deployed in the same container. This will expose two ports: one for the frontend and one for the backend. +
🔴 [Click] For the deployment of only the Tornjak backend (API) There is an additional requirement to mount the SPIRE server socket and make it accessible to the Tornjak backend container. -The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: +The relevant file is called `backend-sidecar-server-statefulset.yaml` within the examples directory. Please copy to the relevant file as follows: + +```console +cp server-statefulset-examples/backend-sidecar-server-statefulset.yaml server-statefulset.yaml +``` + +The statefulset will look something like this, where we have commented leading with a 👈 on the changed or new lines: + +```console +cat server-statefulset.yaml +``` ``` -➜ quickstart git:(master) cat server-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: @@ -440,7 +287,7 @@ spec: - name: spire-data mountPath: /run/spire/data readOnly: false - - name: socket # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME livenessProbe: httpGet: @@ -457,19 +304,13 @@ spec: initialDelaySeconds: 5 periodSeconds: 5 ### 👈 BEGIN ADDITIONAL CONTAINER ### - - name: tornjak - image: ghcr.io/spiffe/tornjak:latest - imagePullPolicy: Always + - name: tornjak-backend + image: ghcr.io/spiffe/tornjak-be:latest args: - - -config + - --config - /run/spire/config/server.conf - - -tornjak-config + - --tornjak-config - /run/spire/tornjak-config/server.conf - env: - - name: REACT_APP_API_SERVER_URI - value: http://localhost:10000 - - name: NODE_OPTIONS - value: --openssl-legacy-provider ports: - containerPort: 8081 volumeMounts: @@ -489,11 +330,11 @@ spec: - name: spire-config configMap: name: spire-server - - name: tornjak-config # 👈 ADDITIONAL VOLUME - configMap: # 👈 ADDITIONAL VOLUME + - name: tornjak-config # 👈 ADDITIONAL VOLUME + configMap: # 👈 ADDITIONAL VOLUME name: tornjak-agent # 👈 ADDITIONAL VOLUME - - name: socket # 👈 ADDITIONAL VOLUME - emptyDir: {} # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME + emptyDir: {} # 👈 ADDITIONAL VOLUME volumeClaimTemplates: - metadata: name: spire-data @@ -504,75 +345,234 @@ spec: resources: requests: storage: 1Gi -``` +``` -Note that there are three key differences in the StatefulSet file from that in the SPIRE quickstart: +Note that there are three key differences in this StatefulSet file from that in the SPIRE quickstart: -1. There is a new container in the pod named `tornjak`. - 1. This container uses environment variables to configure the Frontend. - 1. This container uses arguments to pass arguments to the Backend. -2. We create a volume named tornjak-config that reads from the ConfigMap `tornjak-agent`. -3. We create a volume named `test-socket` so that the containers may communicate +1. There is a new container in the pod named tornjak-backend. +3. We create a volume named `tornjak-config` that reads from the ConfigMap `tornjak-agent`. +4. We create a volume named `test-socket` so that the containers may communicate. + +This is all done specifically to pass the Tornjak config file as an argument to the container and to allow communication between Tornjak and SPIRE.
+## Step 2: Deployment +Now that we have the correct deployment files, please follow the below steps to deploy Tornjak and SPIRE! -### Applying and connecting to the Tornjak agent +```console +kubectl apply -f spire-namespace.yaml \ + -f server-account.yaml \ + -f spire-bundle-configmap.yaml \ + -f tornjak-configmap.yaml \ + -f server-cluster-role.yaml \ + -f server-configmap.yaml \ + -f server-statefulset.yaml \ + -f server-service.yaml +``` -First, we must add the ConfigMap: +The above command should deploy the SPIRE server with Tornjak: ``` -➜ quickstart git:(master) ✗ kubectl apply -f tornjak-configmap.yaml +namespace/spire created +serviceaccount/spire-server created +configmap/spire-bundle created configmap/tornjak-agent created +role.rbac.authorization.k8s.io/spire-server-configmap-role created +rolebinding.rbac.authorization.k8s.io/spire-server-configmap-role-binding created +clusterrole.rbac.authorization.k8s.io/spire-server-trust-role created +clusterrolebinding.rbac.authorization.k8s.io/spire-server-trust-role-binding created +configmap/spire-server created +statefulset.apps/spire-server created +service/spire-server created +service/tornjak-be-http created +service/tornjak-be-tls created +service/tornjak-be-mtls created +service/tornjak-fe created ``` -We can then apply the changes of the statefulset deployment: +Before continuing, check that the spire-server is ready: +```console +kubectl get statefulset --namespace spire ``` -➜ quickstart git:(master) ✗ kubectl apply -f server-statefulset.yaml -statefulset.apps/spire-server configured -➜ quickstart git:(master) ✗ kubectl delete po -n spire spire-server-0 -pod "spire-server-0" deleted + ``` +NAME READY AGE +spire-server 1/1 26s +``` + +### Deploying the agent and creating test entries -We will then wait and verify that the `spire-server-0` pod is now started with the new image: +The following steps will configure and deploy the SPIRE agent. + +```console +kubectl apply \ + -f agent-account.yaml \ + -f agent-cluster-role.yaml \ + -f agent-configmap.yaml \ + -f agent-daemonset.yaml +``` + +``` +serviceaccount/spire-agent created +clusterrole.rbac.authorization.k8s.io/spire-agent-cluster-role created +clusterrolebinding.rbac.authorization.k8s.io/spire-agent-cluster-role-binding created +configmap/spire-agent created +daemonset.apps/spire-agent created +``` + +```console +kubectl get daemonset --namespace spire +``` + +``` +NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE +spire-agent 1 1 1 1 1 19s + +``` + +Then, we can create a registration entry for the node. + +```console +kubectl exec -n spire -c spire-server spire-server-0 -- \ + /opt/spire/bin/spire-server entry create \ + -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \ + -selector k8s_sat:cluster:demo-cluster \ + -selector k8s_sat:agent_ns:spire \ + -selector k8s_sat:agent_sa:spire-agent \ + -node +``` + +``` +Entry ID : 03d0ec2b-54b7-4340-a0b9-d3b2cf1b041a +SPIFFE ID : spiffe://example.org/ns/spire/sa/spire-agent +Parent ID : spiffe://example.org/spire/server +Revision : 0 +TTL : default +Selector : k8s_sat:agent_ns:spire +Selector : k8s_sat:agent_sa:spire-agent +Selector : k8s_sat:cluster:demo-cluster +``` + +And we create a registration entry for the workload registrar, specifying the workload registrar's SPIFFE ID: + +```console +kubectl exec -n spire -c spire-server spire-server-0 -- \ + /opt/spire/bin/spire-server entry create \ + -spiffeID spiffe://example.org/ns/default/sa/default \ + -parentID spiffe://example.org/ns/spire/sa/spire-agent \ + -selector k8s:ns:default \ + -selector k8s:sa:default +``` + +``` +Entry ID : 11a367ab-7095-4390-ab89-34dea5fddd61 +SPIFFE ID : spiffe://example.org/ns/default/sa/default +Parent ID : spiffe://example.org/ns/spire/sa/spire-agent +Revision : 0 +TTL : default +Selector : k8s:ns:default +Selector : k8s:sa:default +``` + +Finally, here we deploy a workload container: + +```console +kubectl apply -f client-deployment.yaml +``` + +And also verify that the container can access the workload API UNIX domain socket: + +```console +kubectl exec -it $(kubectl get pods -o=jsonpath='{.items[0].metadata.name}' \ + -l app=client) -- /opt/spire/bin/spire-agent api fetch -socketPath /run/spire/sockets/agent.sock +``` + +``` +Received 1 svid after 8.8537ms + +SPIFFE ID: spiffe://example.org/ns/default/sa/default +SVID Valid After: 2021-04-06 20:13:02 +0000 UTC +SVID Valid Until: 2021-04-06 21:13:12 +0000 UTC +CA #1 Valid After: 2021-04-06 20:12:20 +0000 UTC +CA #1 Valid Until: 2021-04-07 20:12:30 +0000 UTC +``` + +Let's verify that the `spire-server-0` pod is now started with the new image: + +```console +kubectl -n spire describe pod spire-server-0 | grep "Image:" +``` + +Should yield two lines depending on which deployment you used: ``` -➜ quickstart git:(master) ✗ kubectl -n spire describe pod spire-server-0 | grep "Image:" Image: ghcr.io/spiffe/spire-server:1.4.4 - Image: ghcr.io/spiffe/tornjak-be:latest + Image: ``` -## Connecting to the Tornjak agent +where `` is `ghcr.io/spiffe/tornjak:latest` if you deployed the Tornjak with the UI and is `ghcr.io/spiffe/tornjak-be:latest` if you deployed only the Tornjak backend. + +## Step 3: Configuring Access to Tornjak -The Tornjak HTTP server is running on port 10000 on the port. This can easily be accessed by performing a local port forward using `kubectl`. This will cause the local port 10000 to proxy to the Tornjak HTTP server. +### Step 3a: Connecting to the Tornjak backend to make Tornjak API calls + +The Tornjak HTTP server is running on port 10000 on the pod. This can easily be accessed by performing a local port forward using `kubectl`. This will cause the local port 10000 to proxy to the Tornjak HTTP server. + +```console +kubectl -n spire port-forward spire-server-0 10000:10000 +``` + +You'll see something like this: ``` -➜ quickstart git:(master) ✗ kubectl -n spire port-forward spire-server-0 10000:10000 Forwarding from 127.0.0.1:10000 -> 10000 Forwarding from [::1]:10000 -> 10000 ``` -Open a browser to `http://localhost:10000` and you should now be able to make Tornjak API calls! +While this runs, open a browser to + +``` +http://localhost:10000/api/tornjak/serverinfo +``` + +This output represents the backend response. Now you should be able to make Tornjak API calls! -![tornjak-agent-browser](rsrc/tornjak-agent-browser.png) +![tornjak-agent-browser](../rsrc/tornjak-agent-browser.png) -## Connecting the Tornjak UI +### Step 3b: Connecting to the Tornjak frontend to access the Tornjak UI + +Make sure that the backend is accessible from your browser at `http://localhost:10000`, as above, or the frontend will not work. + +If you chose to deploy Tornjak with the UI, connecting to the UI is very simple. Otherwise, you can always run the UI locally and connect. See the two choices below: + +
🔴 [Click] Connect to the Tornjak frontend that is deployed on Minikube Note that if you chose to deploy the Tornjak image that includes the frontend component, you only need to execute the following command to enable access to the frontend that is already running: +```console +kubectl -n spire port-forward spire-server-0 3000:3000 +``` + ``` -➜ quickstart git:(master) ✗ kubectl -n spire port-forward spire-server-0 3000:3000 Forwarding from 127.0.0.1:3000 -> 3000 Forwarding from [::1]:3000 -> 3000 ``` +
-Otherwise, you will need to deploy the separate frontend separately to access the exposed Tornjak backend. We have prebuilt the frontend in a container, so we can simply run it via a single docker command in a separate terminal, which will take a couple minutes to run: +
🔴 [Click] Run the Tornjak frontend locally +You will need to deploy the separate frontend separately to access the exposed Tornjak backend. We have prebuilt the frontend in a container, so we can simply run it via a single docker command in a separate terminal, which will take a couple minutes to run: + +```console +docker run -p 3000:3000 -e REACT_APP_API_SERVER_URI='http://localhost:10000' ghcr.io/spiffe/tornjak-fe:latest ``` -➜ quickstart git:(master) ✗ docker run -p 3000:3000 -e REACT_APP_API_SERVER_URI='http://localhost:10000' ghcr.io/spiffe/tornjak-fe:latest +After the image is downloaded, you will eventually see the following output: + +``` > tornjak-frontend@0.1.0 start > react-scripts --openssl-legacy-provider start @@ -593,7 +593,32 @@ Note that the development build is not optimized. To create a production build, use npm run build. ``` -This exposes the frontend at http://localhost:3000. If you visit in your browser, you should see this page: +Note, it will likely take a few minutes for the applicaiton to compile successfully. + +
+ +Either of the above steps exposes the frontend at http://localhost:3000. If you visit in your browser, you should see this page: + +![tornjak-ui](../rsrc/tornjak-ui.png) + +## Cleanup + +Here are the steps to clean the deployed entities. First, we delete the workload container: + +```terminal +kubectl delete deployment client +``` + +Then, delete the spire agent and server, along with the namespace we created: + +```terminal +kubectl delete namespace spire +``` -![tornjak-ui](rsrc/tornjak-ui.png) +Finally, we can delete the ClusterRole and ClusterRoleBinding: + +```terminal +kubectl delete clusterrole spire-server-trust-role spire-agent-cluster-role +kubectl delete clusterrolebinding spire-server-trust-role-binding spire-agent-cluster-role-binding +``` diff --git a/docs/quickstart/agent-account.yaml b/docs/quickstart/agent-account.yaml new file mode 100644 index 00000000..9091404c --- /dev/null +++ b/docs/quickstart/agent-account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-agent + namespace: spire diff --git a/docs/quickstart/agent-cluster-role.yaml b/docs/quickstart/agent-cluster-role.yaml new file mode 100644 index 00000000..8bfe36c5 --- /dev/null +++ b/docs/quickstart/agent-cluster-role.yaml @@ -0,0 +1,24 @@ +# Required cluster role to allow spire-agent to query k8s API server +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role +rules: +- apiGroups: [""] + resources: ["pods","nodes","nodes/proxy"] + verbs: ["get"] + +--- +# Binds above cluster role to spire-agent service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role-binding +subjects: +- kind: ServiceAccount + name: spire-agent + namespace: spire +roleRef: + kind: ClusterRole + name: spire-agent-cluster-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/docs/quickstart/agent-configmap.yaml b/docs/quickstart/agent-configmap.yaml new file mode 100644 index 00000000..7b22763a --- /dev/null +++ b/docs/quickstart/agent-configmap.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-agent + namespace: spire +data: + agent.conf: | + agent { + data_dir = "/run/spire" + log_level = "DEBUG" + server_address = "spire-server" + server_port = "8081" + socket_path = "/run/spire/sockets/agent.sock" + trust_bundle_path = "/run/spire/bundle/bundle.crt" + trust_domain = "example.org" + } + + plugins { + NodeAttestor "k8s_sat" { + plugin_data { + # NOTE: Change this to your cluster name + cluster = "demo-cluster" + } + } + + KeyManager "memory" { + plugin_data { + } + } + + WorkloadAttestor "k8s" { + plugin_data { + # Defaults to the secure kubelet port by default. + # Minikube does not have a cert in the cluster CA bundle that + # can authenticate the kubelet cert, so skip validation. + skip_kubelet_verification = true + } + } + + WorkloadAttestor "unix" { + plugin_data { + } + } + } + + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/docs/quickstart/agent-daemonset.yaml b/docs/quickstart/agent-daemonset.yaml new file mode 100644 index 00000000..41cca9f1 --- /dev/null +++ b/docs/quickstart/agent-daemonset.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: spire-agent + namespace: spire + labels: + app: spire-agent +spec: + selector: + matchLabels: + app: spire-agent + template: + metadata: + namespace: spire + labels: + app: spire-agent + spec: + hostPID: true + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: spire-agent + initContainers: + - name: init + # This is a small image with wait-for-it, choose whatever image + # you prefer that waits for a service to be up. This image is built + # from https://github.com/lqhl/wait-for-it + image: cgr.dev/chainguard/wait-for-it + args: ["-t", "30", "spire-server:8081"] + containers: + - name: spire-agent + image: ghcr.io/spiffe/spire-agent:1.5.1 + args: ["-config", "/run/spire/config/agent.conf"] + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-bundle + mountPath: /run/spire/bundle + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: false + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: spire-config + configMap: + name: spire-agent + - name: spire-bundle + configMap: + name: spire-bundle + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: DirectoryOrCreate diff --git a/docs/quickstart/client-deployment.yaml b/docs/quickstart/client-deployment.yaml new file mode 100644 index 00000000..b22bdc40 --- /dev/null +++ b/docs/quickstart/client-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: client + labels: + app: client +spec: + selector: + matchLabels: + app: client + template: + metadata: + labels: + app: client + spec: + hostPID: true + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + containers: + - name: client + image: ghcr.io/spiffe/spire-agent:1.5.1 + command: ["/opt/spire/bin/spire-agent"] + args: [ "api", "watch", "-socketPath", "/run/spire/sockets/agent.sock" ] + volumeMounts: + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: true + volumes: + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: Directory diff --git a/docs/quickstart/create-node-registration-entry.sh b/docs/quickstart/create-node-registration-entry.sh new file mode 100644 index 00000000..c5da4c6f --- /dev/null +++ b/docs/quickstart/create-node-registration-entry.sh @@ -0,0 +1,16 @@ +#/bin/bash + +set -e + +bb=$(tput bold) +nn=$(tput sgr0) + + +echo "${bb}Creating registration entry for the node...${nn}" +kubectl exec -n spire spire-server-0 -- \ + /opt/spire/bin/spire-server entry create \ + -node \ + -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \ + -selector k8s_sat:cluster:demo-cluster \ + -selector k8s_sat:agent_ns:spire \ + -selector k8s_sat:agent_sa:spire-agent diff --git a/docs/quickstart/kustomization.yaml b/docs/quickstart/kustomization.yaml new file mode 100644 index 00000000..0a491013 --- /dev/null +++ b/docs/quickstart/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spire + +resources: +- spire-namespace.yaml +- agent-account.yaml +- agent-cluster-role.yaml +- agent-configmap.yaml +- agent-daemonset.yaml +- server-account.yaml +- server-cluster-role.yaml +- server-configmap.yaml +- server-service.yaml +- server-statefulset.yaml +- spire-bundle-configmap.yaml + diff --git a/docs/quickstart/server-account.yaml b/docs/quickstart/server-account.yaml new file mode 100644 index 00000000..51ad4c5e --- /dev/null +++ b/docs/quickstart/server-account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-server + namespace: spire diff --git a/docs/quickstart/server-cluster-role.yaml b/docs/quickstart/server-cluster-role.yaml new file mode 100644 index 00000000..00925d1e --- /dev/null +++ b/docs/quickstart/server-cluster-role.yaml @@ -0,0 +1,49 @@ +# Role (namespace scoped) to be able to push certificate bundles to a configmap +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role + namespace: spire +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["patch", "get", "list"] +--- +# Binds above role to spire-server service account +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role-binding + namespace: spire +subjects: +- kind: ServiceAccount + name: spire-server + namespace: spire +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: spire-server-configmap-role +--- +# ClusterRole to allow spire-server node attestor to query Token Review API +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role +rules: +- apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["create"] +--- +# Binds above cluster role to spire-server service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role-binding +subjects: +- kind: ServiceAccount + name: spire-server + namespace: spire +roleRef: + kind: ClusterRole + name: spire-server-trust-role + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/docs/quickstart/server-configmap.yaml b/docs/quickstart/server-configmap.yaml new file mode 100644 index 00000000..9cdf269d --- /dev/null +++ b/docs/quickstart/server-configmap.yaml @@ -0,0 +1,63 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-server + namespace: spire +data: + server.conf: | + server { + bind_address = "0.0.0.0" + bind_port = "8081" + socket_path = "/tmp/spire-server/private/api.sock" + trust_domain = "example.org" + data_dir = "/run/spire/data" + log_level = "DEBUG" + #AWS requires the use of RSA. EC cryptography is not supported + ca_key_type = "rsa-2048" + + ca_subject = { + country = ["US"], + organization = ["SPIFFE"], + common_name = "", + } + } + + plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/run/spire/data/datastore.sqlite3" + } + } + + NodeAttestor "k8s_sat" { + plugin_data { + clusters = { + # NOTE: Change this to your cluster name + "demo-cluster" = { + use_token_review_api_validation = true + service_account_allow_list = ["spire:spire-agent"] + } + } + } + } + + KeyManager "disk" { + plugin_data { + keys_path = "/run/spire/data/keys.json" + } + } + + Notifier "k8sbundle" { + plugin_data { + } + } + } + + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/docs/quickstart/server-service.yaml b/docs/quickstart/server-service.yaml new file mode 100644 index 00000000..2be964dd --- /dev/null +++ b/docs/quickstart/server-service.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: Service +metadata: + name: spire-server + namespace: spire +spec: + type: NodePort + ports: + - name: grpc + port: 8081 + targetPort: 8081 + protocol: TCP + selector: + app: spire-server +--- +apiVersion: v1 +kind: Service +metadata: + name: tornjak-be-http + namespace: spire +spec: + type: NodePort + ports: + - name: tornjak-be-http + port: 10000 + targetPort: 10000 + protocol: TCP + selector: + app: spire-server +--- +apiVersion: v1 +kind: Service +metadata: + name: tornjak-be-tls + namespace: spire +spec: + type: NodePort + ports: + - name: tornjak-be-tls + port: 20000 + targetPort: 20000 + protocol: TCP + selector: + app: spire-server +--- +apiVersion: v1 +kind: Service +metadata: + name: tornjak-be-mtls + namespace: spire +spec: + type: NodePort + ports: + - name: tornjak-be-mtls + port: 30000 + targetPort: 30000 + protocol: TCP + selector: + app: spire-server +--- +apiVersion: v1 +kind: Service +metadata: + namespace: spire + name: tornjak-fe +spec: + type: LoadBalancer + selector: + app: spire-server + ports: + - name: tornjak-fe + port: 3000 + targetPort: 3000 diff --git a/examples/deployment_sidecar.yaml b/docs/quickstart/server-statefulset-examples/backend-sidecar-server-statefulset.yaml similarity index 82% rename from examples/deployment_sidecar.yaml rename to docs/quickstart/server-statefulset-examples/backend-sidecar-server-statefulset.yaml index 220e40f2..d24fa413 100644 --- a/examples/deployment_sidecar.yaml +++ b/docs/quickstart/server-statefulset-examples/backend-sidecar-server-statefulset.yaml @@ -33,7 +33,7 @@ spec: - name: spire-data mountPath: /run/spire/data readOnly: false - - name: socket # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME livenessProbe: httpGet: @@ -49,7 +49,8 @@ spec: port: 8080 initialDelaySeconds: 5 periodSeconds: 5 - - name: tornjak-backend ### 👈 BEGIN ADDITIONAL CONTAINER ### + ### 👈 BEGIN ADDITIONAL CONTAINER ### + - name: tornjak-backend image: ghcr.io/spiffe/tornjak-be:latest args: - --config @@ -69,16 +70,17 @@ spec: mountPath: /run/spire/data readOnly: false - name: socket - mountPath: /tmp/spire-server/private ### 👈 END ADDITIONAL CONTAINER ### + mountPath: /tmp/spire-server/private + ### 👈 END ADDITIONAL CONTAINER ### volumes: - name: spire-config configMap: name: spire-server - - name: tornjak-config # 👈 ADDITIONAL VOLUME - configMap: # 👈 ADDITIONAL VOLUME + - name: tornjak-config # 👈 ADDITIONAL VOLUME + configMap: # 👈 ADDITIONAL VOLUME name: tornjak-agent # 👈 ADDITIONAL VOLUME - - name: socket # 👈 ADDITIONAL VOLUME - emptyDir: {} # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME + emptyDir: {} # 👈 ADDITIONAL VOLUME volumeClaimTemplates: - metadata: name: spire-data @@ -89,4 +91,3 @@ spec: resources: requests: storage: 1Gi - diff --git a/docs/quickstart/server-statefulset-examples/tornjak-sidecar-server-statefulset.yaml b/docs/quickstart/server-statefulset-examples/tornjak-sidecar-server-statefulset.yaml new file mode 100644 index 00000000..3cef1af5 --- /dev/null +++ b/docs/quickstart/server-statefulset-examples/tornjak-sidecar-server-statefulset.yaml @@ -0,0 +1,99 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: spire-server + namespace: spire + labels: + app: spire-server +spec: + replicas: 1 + selector: + matchLabels: + app: spire-server + serviceName: spire-server + template: + metadata: + namespace: spire + labels: + app: spire-server + spec: + serviceAccountName: spire-server + containers: + - name: spire-server + image: ghcr.io/spiffe/spire-server:1.4.4 + args: + - -config + - /run/spire/config/server.conf + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-data + mountPath: /run/spire/data + readOnly: false + - name: socket # 👈 ADDITIONAL VOLUME + mountPath: /tmp/spire-server/private # 👈 ADDITIONAL VOLUME + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + ### 👈 BEGIN ADDITIONAL CONTAINER ### + - name: tornjak + image: ghcr.io/spiffe/tornjak:latest + imagePullPolicy: Always + args: + - -config + - /run/spire/config/server.conf + - -tornjak-config + - /run/spire/tornjak-config/server.conf + env: + - name: REACT_APP_API_SERVER_URI + value: http://localhost:10000 + - name: NODE_OPTIONS + value: --openssl-legacy-provider + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: tornjak-config + mountPath: /run/spire/tornjak-config + readOnly: true + - name: spire-data + mountPath: /run/spire/data + readOnly: false + - name: socket + mountPath: /tmp/spire-server/private + ### 👈 END ADDITIONAL CONTAINER ### + volumes: + - name: spire-config + configMap: + name: spire-server + - name: tornjak-config # 👈 ADDITIONAL VOLUME + configMap: # 👈 ADDITIONAL VOLUME + name: tornjak-agent # 👈 ADDITIONAL VOLUME + - name: socket # 👈 ADDITIONAL VOLUME + emptyDir: {} # 👈 ADDITIONAL VOLUME + volumeClaimTemplates: + - metadata: + name: spire-data + namespace: spire + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/docs/quickstart/server-statefulset-examples/tornjak-spire-server-statefulset.yaml b/docs/quickstart/server-statefulset-examples/tornjak-spire-server-statefulset.yaml new file mode 100644 index 00000000..f42ee5ab --- /dev/null +++ b/docs/quickstart/server-statefulset-examples/tornjak-spire-server-statefulset.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: spire-server + namespace: spire + labels: + app: spire-server +spec: + replicas: 1 + selector: + matchLabels: + app: spire-server + serviceName: spire-server + template: + metadata: + namespace: spire + labels: + app: spire-server + spec: + serviceAccountName: spire-server + containers: + - name: spire-server + image: ghcr.io/spiffe/tornjak-be-spire-server:1.x.x # 👈 NOTE YOUR OWN SPIRE VERSION + imagePullPolicy: Always + args: + - -config + - /run/spire/config/server.conf + - -tornjak-config # 👈 ADDITIONAL ARGUMENT + - /run/spire/tornjak-config/server.conf # 👈 ADDITIONAL ARGUMENT + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: tornjak-config # 👈 ADDITIONAL VOLUME + mountPath: /run/spire/tornjak-config # 👈 ADDITIONAL VOLUME + readOnly: true # 👈 ADDITIONAL VOLUME + - name: spire-data + mountPath: /run/spire/data + readOnly: false + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: spire-config + configMap: + name: spire-server + - name: tornjak-config # 👈 ADDITIONAL VOLUME + configMap: # 👈 ADDITIONAL VOLUME + name: tornjak-agent # 👈 ADDITIONAL VOLUME + volumeClaimTemplates: + - metadata: + name: spire-data + namespace: spire + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/docs/quickstart/server-statefulset.yaml b/docs/quickstart/server-statefulset.yaml new file mode 100644 index 00000000..fd1eedf6 --- /dev/null +++ b/docs/quickstart/server-statefulset.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: spire-server + namespace: spire + labels: + app: spire-server +spec: + replicas: 1 + selector: + matchLabels: + app: spire-server + serviceName: spire-server + template: + metadata: + namespace: spire + labels: + app: spire-server + spec: + serviceAccountName: spire-server + containers: + - name: spire-server + image: ghcr.io/spiffe/spire-server:1.5.1 + args: + - -config + - /run/spire/config/server.conf + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-data + mountPath: /run/spire/data + readOnly: false + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: spire-config + configMap: + name: spire-server + volumeClaimTemplates: + - metadata: + name: spire-data + namespace: spire + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/docs/quickstart/spire-bundle-configmap.yaml b/docs/quickstart/spire-bundle-configmap.yaml new file mode 100644 index 00000000..4da32241 --- /dev/null +++ b/docs/quickstart/spire-bundle-configmap.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-bundle + namespace: spire \ No newline at end of file diff --git a/docs/quickstart/spire-namespace.yaml b/docs/quickstart/spire-namespace.yaml new file mode 100644 index 00000000..c6ba3499 --- /dev/null +++ b/docs/quickstart/spire-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: spire diff --git a/docs/quickstart/test.sh b/docs/quickstart/test.sh new file mode 100755 index 00000000..ee972f53 --- /dev/null +++ b/docs/quickstart/test.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +set -e + +bold=$(tput bold) +norm=$(tput sgr0) +red=$(tput setaf 1) +green=$(tput setaf 2) +yellow=$(tput setaf 3) + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +MINIKUBEPROFILE="SPIRE-SYSTEMS-TEST" +MINIKUBECMD="minikube -p ${MINIKUBEPROFILE}" +CHECKINTERVAL=1 +if [ -n "${GITHUB_WORKFLOW}" ]; then + CHECKINTERVAL=5 +fi +TMPDIR=$(mktemp -d) +SERVERLOGS=${TMPDIR}/spire-server-logs.log + +start_minikube() { + # GH actions will start up minikube + if [ -z "${GITHUB_WORKFLOW}" ]; then + echo "${bold}Starting minikube... ${norm}" + ${MINIKUBECMD} start + eval $(${MINIKUBECMD} docker-env) + fi +} + +tear_down_config() { + kubectl delete namespace spire > /dev/null || true +} + +stop_minikube() { + # Don't stop the minikube inside of GH actions + if [ -z "${GITHUB_WORKFLOW}" ]; then + ${MINIKUBECMD} stop > /dev/null || true + fi +} + +cleanup() { + echo -n "${bold}Cleaning up... ${norm}" + if [ ! -z "${SUCCESS}" ]; then + # success. remove the tmp dir. + rm -rf ${TMPDIR} + fi + tear_down_config + stop_minikube + echo "${green}ok${norm}." +} + +# apply the k8s configuration +apply_server_config() { + echo -n "${bold}Applying SPIRE server k8s configuration... ${norm}" + kubectl apply -f ${DIR}/spire-namespace.yaml > /dev/null + kubectl apply -f ${DIR}/server-account.yaml > /dev/null + kubectl apply -f ${DIR}/server-cluster-role.yaml > /dev/null + kubectl apply -f ${DIR}/server-configmap.yaml > /dev/null + kubectl apply -f ${DIR}/spire-bundle-configmap.yaml > /dev/null + kubectl apply -f ${DIR}/server-statefulset.yaml > /dev/null + kubectl apply -f ${DIR}/server-service.yaml > /dev/null + echo "${green}ok.${norm}" +} + +apply_agent_config() { + echo -n "${bold}Applying SPIRE agent k8s configuration... ${norm}" + kubectl apply -f ${DIR}/agent-account.yaml > /dev/null + kubectl apply -f ${DIR}/agent-cluster-role.yaml > /dev/null + kubectl apply -f ${DIR}/agent-configmap.yaml > /dev/null + kubectl apply -f ${DIR}/agent-daemonset.yaml > /dev/null + echo "${green}ok.${norm}" +} + +wait_for_pod() { + local prefix=$1 + local outvar=$2 + for i in $(seq 60); do + echo -n "${bold}Checking ${prefix} pod status... ${norm}" + local getpods=$(kubectl -n spire get pods 2>/dev/null | grep ${prefix} || true) + if [ -z "${getpods}" ]; then + echo "${yellow}NotFound${norm}." + sleep ${CHECKINTERVAL} + continue + fi + local podname=$(echo ${getpods} | awk '{print $1}') + local podstatus=$(echo ${getpods} | awk '{print $3}') + if [ "${podstatus}" != "Running" ]; then + echo "${yellow}${podstatus}${norm}." + sleep ${CHECKINTERVAL} + continue + fi + echo "${green}Running (${podname})${norm}." + # I'd rather use name binding, but macOS ships with Bash 3. Silly macOS. + eval $outvar=\${podname} + return + done + + echo "${red}failed${norm}." + echo "${red}FAILED: ${prefix} pod not running in time${norm}" + exit -1 +} + +wait_for_server() { + wait_for_pod spire-server SPIRE_SERVER_POD_NAME +} + +wait_for_agent() { + wait_for_pod spire-agent SPIRE_AGENT_POD_NAME +} + +check_for_node_attestation() { + # spin for 60 seconds, checking to see if the agent attests + for i in $(seq 60); do + sleep ${CHECKINTERVAL} + echo -n "${bold}Checking for node attestation... ${norm}" + kubectl -n spire logs ${SPIRE_SERVER_POD_NAME} > ${SERVERLOGS} || true + if grep -sxq -e ".*Agent attestation request completed.*k8s_sat.*" ${SERVERLOGS}; then + echo "${green}ok${norm}." + return + fi + echo "${yellow}nope${norm}." + done + + echo "${red}FAILED: node attestation did not succeed in time.${norm}" >&2 + echo "${yellow}Log at ${SERVERLOGS}${norm}" >&2 + exit -1 +} + +trap cleanup EXIT +start_minikube +apply_server_config +wait_for_server +apply_agent_config +wait_for_agent +check_for_node_attestation + +echo "${bold}Success.${norm}" diff --git a/docs/quickstart/tornjak-configmap.yaml b/docs/quickstart/tornjak-configmap.yaml new file mode 100644 index 00000000..527cd8f2 --- /dev/null +++ b/docs/quickstart/tornjak-configmap.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: tornjak-agent + namespace: spire +data: + server.conf: | + server { + metadata = "insert metadata" + } + + plugins { + DataStore "sql" { + plugin_data { + drivername = "sqlite3" + filename = "./agentlocaldb" + } + } + + }