Authentication practices with Tekton
The upstream official documentation around authentication is available here. It mainly focus on authenticating through annotated secrets and service accounts. This document will try to explore the different way to use "secrets" for authentication in your Pipeline
and Task
.
There is at least three ways to use secrets to authenticate to Github or Docker or.
-
Using secrets and workspaces highly recommended
-
Using a
ServiceAccount
recommended -
Using
VolumeMount
not recommended
Using Secret
(s) and Workspace
(s)
This is the most flexible approach, especially if you use the optional workspace feature.
The general idea behind this approach is:
- We use a workspace to bind a secret
- In the Task
definition, we tell the command line (through a flag or an environment variable) where to find the secret content (aka the authentication content).
We’ll take the example with a very simple git-clone
Task, as well as a very simple docker build
, but it can apply to anything else.
A git clone
example
For this example, we assume we are using git
with ssh
to authenticate. Later in the document, we will show different approaches.
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: git-clone
spec:
workspaces:
- name: output
description: The git repo will be cloned onto the volume backing this Workspace.
- name: ssh-directory # (1)
description: |
A .ssh directory with private key, known_hosts, config, etc. Copied to
the user's home before git commands are executed. Used to authenticate
with the git remote when performing the clone. Binding a Secret to this
Workspace is strongly recommended over other volume types
params:
- name: url
description: Repository URL to clone from.
type: string
- name: revision
description: Revision to checkout. (branch, tag, sha, ref, etc...)
type: string
default: ""
- name: gitInitImage
description: The image providing the git-init binary that this Task runs.
type: string
default: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.37.0"
results:
- name: commit
description: The precise commit SHA that was fetched by this Task.
- name: url
description: The precise URL that was fetched by this Task.
steps:
- name: clone
image: "$(params.gitInitImage)"
script: |
#!/usr/bin/env sh
set -eu
# This is necessary for recent version of git
git config --global --add safe.directory '*'
# (2)
cp -R "$(workspaces.ssh-directory.path)" "${HOME}"/.ssh
chmod 700 "${HOME}"/.ssh
chmod -R 400 "${HOME}"/.ssh/*
CHECKOUT_DIR="$(workspaces.output.path)/"
/ko-app/git-init \
-url="$(params.url)" \
-revision="$(params.revision)" \
-path="${CHECKOUT_DIR}"
cd "${CHECKOUT_DIR}"
RESULT_SHA="$(git rev-parse HEAD)"
EXIT_CODE="$?"
if [ "${EXIT_CODE}" != 0 ] ; then
exit "${EXIT_CODE}"
fi
printf "%s" "${RESULT_SHA}" > "$(results.commit.path)"
printf "%s" "$(params.url)" > "$(results.url.path)"
-
Defines the workspace ; here the name is
ssh-directory
, and the description explain how should the secret be created. An example of an ssh secret with aed25519
private key.apiVersion: v1 kind: Secret metadata: name: my-github-ssh-credentials type: Opaque data: id_ed25519: # […] known_hosts: # […] # config: # […] # optional
This can be created with the following command-line:
$ kubectl create secret generic my-github-ssh-credentials \ --from-file=id_ed25519=/path/to/.ssh/id_ed25519 \ --from-file=known_hosts=/path/to/.ssh/known_hosts secret/my-github-ssh-credentials created
-
In the
script
, we are copying the content of the secret (as a folder) to${HOME}/.ssh
which is the standard folder wheressh
(and thusgit
) will look for credentials.
$ tkn task start git-clone \
--param url=git@github.com:vdemeester/buildkit-tekton \
--workspace name=output,emptyDir="" \
--workspace name=ssh-directory,secret=my-github-ssh-credentials \
--use-param-defaults --showlog
TaskRun started: git-clone-run-kt7fv
Waiting for logs to be available...
[clone] {"level":"warn","ts":1657102809.7319033,"caller":"git/git.go:273","msg":"URL(\"git@github.com:vdemeester/buildkit-tekton\") appears to need SSH authentication but no SSH credentials have been provided"}
[clone] {"level":"info","ts":1657102813.1506214,"caller":"git/git.go:178","msg":"Successfully cloned git@github.com:vdemeester/buildkit-tekton @ e6afd054a907ee447a040c6e95f23fabe038ce6d (grafted, HEAD) in path /workspace/output/"}
[clone] {"level":"info","ts":1657102813.1600826,"caller":"git/git.go:217","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
Note that the warning URL(\"git@github.com:vdemeester/buildkit-tekton\") appears to need SSH authentication but no SSH credentials have been provided
appears because the entrypoint didn’t see any credentials coming from the annotated secrets attached to a ServiceAccount
(see Using ServiceAccount
(s)). It can safely be ignored in that case.
A docker configuration
For this example, we will be using an existing docker configuration file to be used inside a Task
, similar to the A git clone
example example.
The Task
definition is slightly similar to the A git clone
example one. Here we will use skopeo
to copy an image to our own repository — it could be applied to other tools (podman
, buildah
, docker
, …), basically any tool that is capable of reading a docker client configuration file.
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: skopeo-copy
spec:
workspaces:
- name: dockerconfig # (1)
description: Includes a docker `config.json`
steps:
- name: clone
image: quay.io/skopeo/stable:v1.8.0
env:
- name: DOCKER_CONFIG
value: $(workspaces.dockerconfig.path) # (2)
script: |
#!/usr/bin/env sh
set -eu
skopeo copy docker://docker.io/library/ubuntu:latest docker://docker.io/vdemeester/ubuntu-copy:latest
-
Similar to A
git clone
example, we define a workspace that should contain aconfig.json
file. For a secret, this means a key namedconfig.json
. An example of an ssh secret with aed25519
private key.apiVersion: v1 kind: Secret metadata: name: regcred type: Opaque data: config.json: # […]
This can be created with the following command-line:
$ kubectl create secret generic regcred \ --from-file=config.json=/path/to/.docker/config.json secret/regcred created
-
Here we are just setting the
DOCKER_CONFIG
environment variable to point to thedockerconfig
workspace path.skopeo
(as a lot of docker-ish client) do read this environment variable to get the docker client configuration information, and in our case, the authentication informations.
$ tkn task start skopeo-copy --workspace name=dockerconfig,secret=regcred --showlog
TaskRun started: skopeo-copy-run-cfg7l
Waiting for logs to be available...
[clone] DOCKER_CONFIG=/workspace/dockerconfig
Getting image source signatures
[clone] Copying blob sha256:405f018f9d1d0f351c196b841a7c7f226fb8ea448acd6339a9ed8741600275a2
[clone] Copying config sha256:27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee
[clone] Writing manifest to image destination
[clone] Storing signatures
With optional workspaces
This approach is extremely similar to the one above but using the optional feature of workspaces. In a gist, this means a Workspace
might be bound to it (a.k.a. mounted as a volume in the Pod
/Container
) ; but it could also not be present.
The only difference here will be, to make sure our Task
takes this optional property into account. The git-clone
upstream Task
shows an example of that. Let’s adapt the A git clone
example example with optional Workspace
support.
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: git-clone
spec:
workspaces:
- name: output
description: The git repo will be cloned onto the volume backing this Workspace.
- name: ssh-directory # (1)
description: |
A .ssh directory with private key, known_hosts, config, etc. Copied to
the user's home before git commands are executed. Used to authenticate
with the git remote when performing the clone. Binding a Secret to this
Workspace is strongly recommended over other volume types
params:
- name: url
description: Repository URL to clone from.
type: string
- name: revision
description: Revision to checkout. (branch, tag, sha, ref, etc...)
type: string
default: ""
- name: gitInitImage
description: The image providing the git-init binary that this Task runs.
type: string
default: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.37.0"
results:
- name: commit
description: The precise commit SHA that was fetched by this Task.
- name: url
description: The precise URL that was fetched by this Task.
steps:
- name: clone
image: "$(params.gitInitImage)"
script: |
#!/usr/bin/env sh
set -eu
# This is necessary for recent version of git
git config --global --add safe.directory '*'
# (2)
if [ "$(workspaces.ssh-directory.bound)" = "true" ] ; then
cp -R "$(workspaces.ssh-directory.path)" "${HOME}"/.ssh
chmod 700 "${HOME}"/.ssh
chmod -R 400 "${HOME}"/.ssh/*
fi
CHECKOUT_DIR="$(workspaces.output.path)/"
/ko-app/git-init \
-url="$(params.url)" \
-revision="$(params.revision)" \
-path="${CHECKOUT_DIR}"
cd "${CHECKOUT_DIR}"
RESULT_SHA="$(git rev-parse HEAD)"
EXIT_CODE="$?"
if [ "${EXIT_CODE}" != 0 ] ; then
exit "${EXIT_CODE}"
fi
printf "%s" "${RESULT_SHA}" > "$(results.commit.path)"
printf "%s" "$(params.url)" > "$(results.url.path)"
-
Defines the workspace ; here the name is
ssh-directory
, and the description explain how should the secret be created. The example in Agit clone
example apply here as well. -
In the
script
, we are conditionally copying the content of the secret (as a folder) to${HOME}/.ssh
which is the standard folder wheressh
(and thusgit
) will look for credentials. If theWorkspace
is bound we copy, if it’s not bound, we don’t do anything.
The main advantage of using optionnal Workspace
is that it makes your Task
a bit more flexible. For example, with git-clone
, if you are going to clone a publicly available git repository, you won’t bind a Secret
to the Workspace
.
With git-credentials
We can apply both the approach with and without the optional Workspace
with other means of authenticating than ssh
keys for git-clone
, for example. Git has a notion of git-credential
that could be used here. Using optional workspace as above, it could look like the following.
if [ "$(workspaces.basic_auth.bound)" = "true" ] ; then
cp "$(workspaces.basic_auth.path)/.git-credentials" "${HOME}/.git-credentials"
cp "$(workspaces.basic_auth.path)/.gitconfig" "${HOME}/.gitconfig"
chmod 400 "${HOME}/.git-credentials"
chmod 400 "${HOME}/.gitconfig"
fi
Using ServiceAccount
(s)
The ServiceAccount
approach is the most widely documentated, especially upstream. The goal here is not to paraphrase the upstream documentation, so we’ll just describe what the flow is.
This methods constits, in a gist, of the following:
- Secret
(s) annotated specifcally, see Annotations
- ServiceAccount
(s) that are linked to those annotated Secret
(s), see Link Secret
(s) to ServiceAccount
(s)
- PipelineRun
(s) or TaskRun
(s) using this/those ServiceAccount
(s), see Specify SerivceAccount
(s) on PipelineRun
/TaskRun
(s)
Annotations
Tetkon support two different type of authentication for secrets : git
and docker
. The annotation "reflect" this :
-
tekton.dev/git-{}
mark the annotated secret as agit
secret. -
tekton.dev/docker-{}
mark the annotated secret as adocker
secret.
In addition, Tekton supports different types of secret per type of authentication. See the Types of Secrets kubernetes documentation for more details on those secrets.
-
For
git
secrets: -
kubernetes.io/basic-auth
: basic authentication -
kubernetes.io/ssh-auth
: ssh based authentication (keys, …) -
For
docker
secrets: -
kubernetes.io/basic-auth
: basic authentications -
kubernetes.io/dockercfg
: serialized~/.dockercfg
file -
kubernetes.io/dockerconfigjson
: serialized~/.docker/config.json
file
We’ll just show a set of example of annotated secrets with a quick description of them.
Almost all the example below are using stringData , which are direct content of the secret (visible for everybody that can get Secret ). They can also all work with data , which is the base64 encoded version of the data, that gives a little bit more obfuscation to the Secrets .
|
-
basic authentication for
git
: for gitlab.com as well as github.com. It seems not very secure to use the same user/password for authenticating different git provider, but that’s just to show that aSecret
can be used to target multiple host.apiVersion: v1 kind: Secret metadata: annotations: tekton.dev/git-0: https://github.com tekton.dev/git-1: https://gitlab.com type: kubernetes.io/basic-auth stringData: username: <cleartext username> password: <cleartext password>
-
ssh authentication for
git
:apiVersion: v1 kind: Secret metadata: annotations: tekton.dev/git-0: github.com type: kubernetes.io/ssh-auth stringData: ssh-privatekey: <private-key> # This is non-standard, but its use is encouraged to make this more secure. # Omitting this results in the server's public key being blindly accepted.
-
basic authentication for
docker
apiVersion: v1 kind: Secret metadata: annotations: tekton.dev/docker-0: https://gcr.io type: kubernetes.io/basic-auth stringData: username: <cleartext username> password: <cleartext password>
-
kubernetes.io/dockerconfigjson
authentication fordocker
. This type of secret (withkubernetes.io/dockercfg
) do not need to be annotated as it can contain authentication for multiple hosts.apiVersion: v1 kind: Secret metadata: name: regcred type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: # base64 encoded content of a docker `config.json` file
It can be created easily with the following command-line
$ kubectl create secret generic regcred \ --from-file=.dockerconfigjson=<path/to/.docker/config.json> \ --type=kubernetes.io/dockerconfigjson`.
Link Secret
(s) to ServiceAccount
(s)
To link a Secret
to a ServiceAccount
, you need to reference the Secret
, by name, in the secrets
field of the ServiceAccount
.
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-bot
secrets:
- name: regcred
- name: a-git-auth-secret
Specify SerivceAccount
(s) on PipelineRun
/TaskRun
(s)
To add a ServiceAccount
to a PipelineRun
or a TaskRun
you can use the serviceAccountName
field.
# For a TaskRun
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: build-with-basic-auth
spec:
serviceAccountName: build-bot
taskRef:
name: demo-task
# ...
---
# For a PipelineRun
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: demo-pipeline
namespace: default
spec:
serviceAccountName: build-bot
pipelineRef:
name: demo-pipeline
# […]
It is also possible to use tkn
to achieve the same.
---
# For a TaskRun
$ tkn task start demo-task --serviceaccount build-bot
# For a PipelineRun
$ tkn pipeline start demo-pipeline --serviceaccount build-bot --param # […]
---
Using VolumeMount
(s)
Because this solution is highly discouraged, mainly due to the fact that Volume
and VolumeMount
might go away in the API, see Remove "volumes" from Task before V1, we’ll only scratch the surface on how it would work.
The idea of this solution is very similar to Using Secret
(s) and Workspace
(s) but using Volume
and VolumeMount
instead. You wount a Secret
in a Volume
using VolumeMount
in the Task
, and you act based on the path it is mounted on to copy the secrets to the right place.