Skip to content

Commit

Permalink
Support for JWT Authorization and token refresh
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Conner <kev.conner@getupcloud.com>
  • Loading branch information
knrc committed Sep 24, 2024
1 parent e8cbbfe commit 9b276fd
Show file tree
Hide file tree
Showing 19 changed files with 1,406 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
image: operator
- dockerfile: cmd/worker/Dockerfile
image: worker
- dockerfile: cmd/refreshtoken/Dockerfile
image: refreshtoken
steps:
- name: checkout
uses: actions/checkout@v4
Expand Down
19 changes: 15 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Image URL to use all building/pushing image targets
IMG ?= operator:latest
WORKER_IMG ?= worker:latest
TOKENREFRESH_IMG ?= tokenrefresh:latest

# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.29.3
Expand Down Expand Up @@ -97,9 +98,10 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
##@ Build

.PHONY: build
build: manifests generate fmt vet ## Build manager and worker binaries.
build: manifests generate fmt vet ## Build manager, worker and tokenrefresh binaries.
go build -o bin/manager cmd/main.go
go build -o bin/worker cmd/worker/main.go
go build -o bin/tokenrefresh cmd/tokenrefresh/main.go

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
Expand All @@ -116,6 +118,10 @@ docker-build: test ## Build docker image with the manager.
docker-build-worker: test ## Build docker image with worker.
$(CONTAINER_TOOL) build -t ${WORKER_IMG} -f cmd/worker/Dockerfile .

.PHONY: docker-build-tokenrefresh
docker-build-tokenrefresh: test ## Build docker image with tokenrefresh.
$(CONTAINER_TOOL) build -t ${TOKENREFRESH_IMG} -f cmd/tokenrefresh/Dockerfile .

.PHONY: docker-push
docker-push: ## Push docker image with the manager.
$(CONTAINER_TOOL) push ${IMG}
Expand All @@ -124,6 +130,10 @@ docker-push: ## Push docker image with the manager.
docker-push-worker: ## Push docker image with worker.
$(CONTAINER_TOOL) push ${WORKER_IMG}

.PHONY: docker-push-tokenrefresh
docker-push-tokenrefresh: ## Push docker image with tokenrefresh.
$(CONTAINER_TOOL) push ${TOKENREFRESH_IMG}

# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/myoperator:0.0.1). To use this option you need to:
# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
Expand Down Expand Up @@ -191,9 +201,10 @@ kind-create-cluster: kind ## Create a local Kubernetes cluster with Kind
$(KIND) create cluster --name $(CLUSTER_NAME)

.PHONY: kind-load-images
kind-load-images: kind docker-build docker-build-worker ## Build and load docker images into Kind nodes
$(KIND) load docker-image ${IMG}
$(KIND) load docker-image ${WORKER_IMG}
kind-load-images: kind docker-build docker-build-worker docker-build-tokenrefresh ## Build and load docker images into Kind nodes
$(KIND) load docker-image -n $(CLUSTER_NAME) ${IMG}
$(KIND) load docker-image -n $(CLUSTER_NAME) ${WORKER_IMG}
$(KIND) load docker-image -n $(CLUSTER_NAME) ${TOKENREFRESH_IMG}

##@ Build Dependencies

Expand Down
20 changes: 20 additions & 0 deletions charts/zora/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,26 @@ The following table lists the configurable parameters of the Zora chart and thei
| httpsProxy | string | `""` | HTTPS proxy URL |
| noProxy | string | `"kubernetes.default.svc.*,127.0.0.1,localhost"` | Comma-separated list of URL patterns to be excluded from going through the proxy |
| updateCRDs | bool | `true` for upgrades | Specifies whether CRDs should be updated by operator at startup |
| tokenRefresh.image.repository | string | `"ghcr.io/undistro/zora/tokenrefresh"` | tokenrefresh image repository |
| tokenRefresh.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion |
| tokenRefresh.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| tokenRefresh.rbac.create | bool | `true` | Specifies whether Roles and RoleBindings should be created |
| tokenRefresh.rbac.serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
| tokenRefresh.rbac.serviceAccount.annotations | object | `{}` | Annotations to be added to service account |
| tokenRefresh.rbac.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template |
| tokenRefresh.minRefreshTime | string | `"1m"` | Minimum time to wait before checking for token refresh |
| tokenRefresh.refreshThreshold | string | `"2h"` | Threshold relative to the token expiry timestamp, after which a token can be refreshed. |
| tokenRefresh.nodeSelector | object | `{}` | [Node selection](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) to constrain a Pod to only be able to run on particular Node(s) |
| tokenRefresh.tolerations | list | `[]` | [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration) for pod assignment |
| tokenRefresh.affinity | object | `{}` | Map of node/pod [affinities](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration) |
| tokenRefresh.podAnnotations | object | `{"kubectl.kubernetes.io/default-container":"manager"}` | Annotations to be added to pods |
| tokenRefresh.podSecurityContext | object | `{"runAsNonRoot":true}` | [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context) to add to the pod |
| tokenRefresh.securityContext | object | `{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true}` | [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context) to add to `manager` container |
| zoraauth.domain | string | `""` | The domain associated with the tokens |
| zoraauth.clientId | string | `""` | The client id associated with the tokens |
| zoraauth.accessToken | string | `""` | The access token authorizing access to the SaaS API server |
| zoraauth.tokenType | string | `"Bearer"` | The type of the access token |
| zoraauth.refreshToken | string | `""` | The refresh token for obtaining a new access token |

Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,

Expand Down
23 changes: 23 additions & 0 deletions charts/zora/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ Create the name of the service account to use in Operator
{{- end }}
{{- end }}

{{/*
TokenRefresh selector labels
*/}}
{{- define "zora.tokenRefreshSelectorLabels" -}}
{{ include "zora.selectorLabels" . }}
app.kubernetes.io/component: token-refresh
{{- end }}

{{/*
Create the name of the service account to use in TokenRefresh
*/}}
{{- define "zora.tokenRefreshServiceAccountName" -}}
{{- if .Values.tokenRefresh.rbac.serviceAccount.create }}
{{- default (printf "%s-%s" (include "zora.fullname" .) "token-refresh") .Values.tokenRefresh.rbac.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.tokenRefresh.rbac.serviceAccount.name }}
{{- end }}
{{- end }}

{{- define "zora.imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"auth\":\"%s\"}}}" .registry (printf "%s:%s" .username .password | b64enc) | b64enc }}
Expand Down Expand Up @@ -149,3 +168,7 @@ Truncate a name to a specific length
{{- $isHourBad := not (mustRegexMatch "^(?:\\d|[0-5]\\d)$" $hour) -}}
{{- or $isMinuteBad $isHourBad -}}
{{- end -}}

{{- define "zora.saasTokenSecretName" -}}
{{- printf "%s-saas-tokens" (include "zora.fullname" .) -}}
{{- end }}
1 change: 1 addition & 0 deletions charts/zora/templates/hooks/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ spec:
- |
curl -kfsS -X POST '{{ tpl .Values.saas.installURL . }}' \
-H 'content-type: application/json' \
-H 'Authorization: {{ .Values.zoraauth.tokenType }} {{ .Values.zoraauth.accessToken }}' \
{{- if .Values.httpsProxy }}
-x '{{ .Values.httpsProxy}}' \
{{- end }}
Expand Down
14 changes: 13 additions & 1 deletion charts/zora/templates/operator/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
{{ $secretName := printf "%s-serving-cert" (include "zora.fullname" .) -}}
{{ $saasTokensSecretName := (include "zora.saasTokenSecretName" .) -}}
{{- $serviceName := printf "%s-webhook" (include "zora.fullname" .) -}}
{{- if .Values.operator.webhook.enabled -}}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace $secretName -}}
Expand Down Expand Up @@ -118,6 +119,7 @@ spec:
- --inject-conversion={{ .Values.operator.webhook.enabled }}
- --webhook-service-name={{ $serviceName }}
- --webhook-service-namespace={{ .Release.Namespace }}
- --token-path=/tmp/jwt-tokens/token
image: "{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.operator.image.pullPolicy }}
ports:
Expand All @@ -131,11 +133,16 @@ spec:
- containerPort: 9443
name: webhook-server
protocol: TCP
{{- end }}
volumeMounts:
{{- if .Values.operator.webhook.enabled }}
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
readOnly: true
{{- end }}
- mountPath: /tmp/jwt-tokens
name: jwt-tokens
readOnly: true
livenessProbe:
httpGet:
path: /healthz
Expand All @@ -152,14 +159,19 @@ spec:
{{- toYaml .Values.operator.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.operator.securityContext | nindent 12 }}
{{- if .Values.operator.webhook.enabled }}
volumes:
{{- if .Values.operator.webhook.enabled }}
- name: cert
secret:
defaultMode: 420
secretName: {{ $secretName }}
optional: true
{{- end }}
- name: jwt-tokens
secret:
defaultMode: 420
secretName: {{ $saasTokensSecretName }}
optional: true
securityContext:
{{- toYaml .Values.operator.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "zora.operatorServiceAccountName" . }}
Expand Down
93 changes: 93 additions & 0 deletions charts/zora/templates/tokenrefresh/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2022 Undistro Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{{- if .Values.saas.workspaceID -}}
{{ $secretName := (include "zora.saasTokenSecretName" .) -}}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace $secretName -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $secretName }}
type: undistro.io/jwtTokens
data:
{{- if $existingSecret }}
{{- toYaml $existingSecret.data | nindent 2 }}
{{- else }}
token: {{ printf "{ \"access_token\": \"%s\", \"refresh_token\": \"%s\", \"token_type\": \"%s\" }" .Values.zoraauth.accessToken .Values.zoraauth.refreshToken .Values.zoraauth.tokenType | b64enc }}
{{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "zora.fullname" . }}-tokenrefresh
labels:
{{- include "zora.tokenRefreshSelectorLabels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "zora.tokenRefreshSelectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.tokenRefresh.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "zora.tokenRefreshSelectorLabels" . | nindent 8 }}
spec:
containers:
- name: tokenrefresh
{{- if .Values.httpsProxy }}
env:
- name: HTTPS_PROXY
value: {{ .Values.httpsProxy | quote }}
- name: NO_PROXY
value: {{ .Values.noProxy | quote }}
{{- end }}
command:
- /tokenrefresh
args:
- --secret-name={{ $secretName }}
- --namespace={{ .Release.Namespace }}
- --domain={{ .Values.zoraauth.domain }}
- --client-id={{ .Values.zoraauth.clientId }}
- --min-refresh-time={{ .Values.tokenRefresh.minRefreshTime }}
- --refresh-threshold={{ .Values.tokenRefresh.refreshThreshold }}
image: "{{ .Values.tokenRefresh.image.repository }}:{{ .Values.tokenRefresh.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.tokenRefresh.image.pullPolicy }}
resources:
{{- toYaml .Values.tokenRefresh.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.tokenRefresh.securityContext | nindent 12 }}
volumes:
- name: jwt-tokens
secret:
defaultMode: 420
secretName: {{ $secretName }}
securityContext:
{{- toYaml .Values.tokenRefresh.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "zora.tokenRefreshServiceAccountName" . }}
terminationGracePeriodSeconds: 10
{{- with .Values.tokenRefresh.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tokenRefresh.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tokenRefresh.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end -}}
53 changes: 53 additions & 0 deletions charts/zora/templates/tokenrefresh/rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2023 Undistro Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

{{- if .Values.saas.workspaceID -}}
{{ if .Values.tokenRefresh.rbac.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "zora.tokenRefreshServiceAccountName" . }}
labels:
{{- include "zora.tokenRefreshSelectorLabels" . | nindent 4 }}
{{- with .Values.tokenRefresh.rbac.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{ end }}
{{- if .Values.tokenRefresh.rbac.create -}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "zora.tokenRefreshServiceAccountName" . }}-secret-access
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "zora.tokenRefreshServiceAccountName" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "zora.tokenRefreshServiceAccountName" . }}-secret-access
subjects:
- kind: ServiceAccount
name: {{ include "zora.tokenRefreshServiceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
{{- end -}}
---
51 changes: 51 additions & 0 deletions charts/zora/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,54 @@ noProxy: kubernetes.default.svc.*,127.0.0.1,localhost
# -- (bool) Specifies whether CRDs should be updated by operator at startup
# @default -- `true` for upgrades
updateCRDs:

tokenRefresh:
image:
# -- tokenrefresh image repository
repository: ghcr.io/undistro/zora/tokenrefresh
# -- Overrides the image tag whose default is the chart appVersion
tag: ""
# -- Image pull policy
pullPolicy: IfNotPresent
rbac:
# -- Specifies whether Roles and RoleBindings should be created
create: true
serviceAccount:
# -- Specifies whether a service account should be created
create: true
# -- Annotations to be added to service account
annotations: {}
# -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template
name: ""
# -- Minimum time to wait before checking for token refresh
minRefreshTime: "1m"
# -- Threshold relative to the token expiry timestamp, after which a token can be refreshed.
refreshThreshold: "2h"
# -- [Node selection](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) to constrain a Pod to only be able to run on particular Node(s)
nodeSelector: {}
# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration) for pod assignment
tolerations: []
# -- Map of node/pod [affinities](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration)
affinity: {}
# -- Annotations to be added to pods
podAnnotations:
kubectl.kubernetes.io/default-container: manager
# -- [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context) to add to the pod
podSecurityContext:
runAsNonRoot: true
# -- [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context) to add to `manager` container
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true

zoraauth:
# -- The domain associated with the tokens
domain: ""
# -- The client id associated with the tokens
clientId: ""
# -- The access token authorizing access to the SaaS API server
accessToken: ""
# -- The type of the access token
tokenType: "Bearer"
# -- The refresh token for obtaining a new access token
refreshToken: ""
Loading

0 comments on commit 9b276fd

Please sign in to comment.