Reading the docs a user can quickly and easily discover how to:
Once a user has read the docs, there's a low hurdle to attaching credentials to their CI workloads.
Our auth.md doc describes a system for getting credentials out of Secrets and into Steps.
The process for users is documented as follows:
The number of steps isn't too bad but there's a lot of room for confusion:
On the controller-side Pod startup looks as follows with credentials:
There are a couple of challenges with this:
emptyDir volumes). This diminishes a security feature built into the platform.Our guidance on credentials for Task Authors and Users is not very strong, despite how long auth.md is. It's not easy to figure out precisely what you should implement or expect, regardless if you're a Task Author or a Task User.
Every Step gets access to all credentials. Auth.md states at the end: "For sensitive credentials that should not be made available to some steps, do not use the mechanisms outlined here. Instead, the user should declare an explicit Volume from the Secret and manually VolumeMount it into the Step." So we're kind of acknowledging here that there are problems with the existing flow and Tekton's treatment of creds.
I'm collecting creds-related issues as I spot them.
The fact that creds are often mounted as volumes and manifest as files on disk leads me to think that Workspaces may make a good primitive for credentials. This idea proposes the following changes to specs and the Pipelines controller to accomodate credentials in workspaces:
mountPath; it's just the container's regular filesystem.git-clone expects ~/.ssh to be available for authenticated clones but works just fine without it for public repos.workspaces:
- name: my-sensitive-workspace
description: Provide git credentials here.
sensitive: true # only mounted if explicitly requested by step
mountPath: /home/.ssh
- name: my-shared-output-workspace
description: This is where the code will be put.
mountPath: /source
steps:
- name: do-something-authenticated
workspaces:
- my-sensitive-workspace # requests sensitive workspace mount
- my-shared-output-workspace # documents use but not required
image: foo
# ...
~/.git-credentials).Projected Volumes allow individual items of multiple Secrets to be exposed together in a single directory but these also have problems.$HOME.$HOME with a combination of all the Secret credentials that a Task needs. $HOME would be read-only, breaking a large number of tools and scripts.$HOME variable doesn't really get resolved until runtime. Some platforms randomize the UID or enforce non-root users.First, a quick example of how I imagine this syntax looking in a Task spec:
credentials:
- name: ssh-directory
optional: true
description: |
An .ssh directory with id_rsa, known_hosts, etc...
directory: $HOME/.ssh
- name: git-credentials-file
optional: true
description: |
A file with each line formatted https://user:[email protected]
file: $HOME/.git-credentials
- name: git-password-envvar
optional: true
description: |
Not a real git env var, just illustrating possible syntax.
envVar: GIT_PASSWORD
steps:
- name: clone
credentials:
- ssh-directory
- git-credentials-file
- git-password-envvar
image: foo
# ...
And possible syntax for providing those credentials from a TaskRun spec:
credentials:
- name: ssh-directory
secretName: my-secret
- name: git-credentials-file
secretName: another-secret
key: gitCredsContent # file, must specify one key from secret
- name: git-password-envvar
secretName: yet-another-secret
key: myPassword # envVar, must specify one key in secret
And a breakdown of the individual features:
Depending on the choice of directory, file, or envVar in a Credential declaration, Tekton will attach Secrets in different ways:
directory credential would likely be mounted as a Secret volume.~/.ssh directory from a Secret containing known_hosts, id_rsa keys.file credentials would be mounted as Secret volumes with individual item entries and then those mounted items would be symlinked (or, perhaps by configuration, copied) to their intended destination.~/.git-credentials file with user/password lines.envVar credentials would be attached using Container's env.valueFrom to expose the Secret's key as the env var.AWS_ACCESS_KEY_ID ~/.ssh dir, ~/.git-credentials file, or public repos without auth.$HOME without making the entire $HOME dir read-only), Tekton should do so while maintaining the reduced permission bits.credentials:
- name: ssh-directory
optional: true
directory: $HOME/.ssh
steps:
- name: do-something-authenticated
credentials:
- ssh-directory # this step receives the ssh-directory volume mount
image: foo
Related, I think it would be really cool if tkn supported something like:
tkn tr start my-git-task --credential git-ssh-directory="~/.ssh"
That automatically created a Secret with contents of file/directory and ran the TaskRun with it. Maybe even deleting the Secret afterwards?
I definitely prefer the design idea 2. It's more explicit and make a clear distinction from the workspaces
Excellent, I am building a POC of #2 first.
It is great that we focus on improving UX here!
- creds-init copies the credentials out of those Volumes into /tekton/home or /tekton/creds
- entrypoint, before each Step, copies the credentials out of /tekton/creds into the Step Container's $HOME
There are a couple of challenges with this:
Secrets get mounted by Kubernetes as in-memory filesystems but creds-init copies those credentials to disk (/tekton/home and /tekton/creds are emptyDir volumes). This diminishes a security feature built into the platform.
I agree, it would be nice to avoid these "moving parts". From a security standpoint - in a best of worlds - I would like to be able to use readOnlyRootFilesystem in the pod security policy and instead have every thing declaratively exposed to the Task/Step.
To follow up from #2398. I think some use of projected volumes can be very clean and useful piece for this kind of solution. We will need some support for secret but also serviceAccountToken (the newer - more secure variant, see No More Forever-tokens) that projected volumes has. We currently seem to use _workspaces_ for many things, but here we could move some things to projected volumes instead (possibly in combination with task-specific syntax)
It's more explicit and make a clear distinction from the workspaces
I agree with that.
Projected Volumes and Secrets get mounted as read-only.
But I see this as a positive thing, it is easier to reason about it from a security standpoint as well.
So a git Task that asks for a single Secret to mount into ~ would not be able to clone a repo into ~/src.
There are many use cases. But for e.g. git it is usually possible to configure credentials path and for e.g. aws cli using AWS_CONFIG_FILE and for ssh it is possible to use -i for Identity key location.
I earlier promised to come up with a proposal on how we can use "projected service accounts" - with tokens that are regularly rotated by the kubelet. (see No more forever tokens )
I have tried this out. Actually, it works as-is with Tekton, without any changes. By using a projected volume. Also, secrets (also optional) and configmaps, specific for a step can be used with the same solution. See my comment with example of secret from projected volume
And this is:
What we lack is:
Here is an example of using a "regularly rotated token" (but for us - the value is that it is a token that expire - and single-audience) in a Task:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: task
spec:
steps:
- name: lister
image: <my-custom-pod-lister-image>
command: list-pods
volumeMounts:
- mountPath: /var/run/secrets/rotating-tokens
name: rotating-token
volumes:
- name: rotating-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 7200
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: taskrun-serviceaccount
spec:
serviceAccountName: lister
taskRef:
name: task
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: lister
For a non-root pod, this need to be added to the template:
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
From the Kubernetes documentation it looks like this solution plays well with a service like Vault - it is used as an example.
If wanted, I can provide a more extensive example or also contribute to documentation about this.
Very interesting! Would there ever be a reason that a TaskRun would want to override the audience? I have to admit I don't fully understand how the audience field works.
This definitely seems worthwhile to document (at least to me!)
Edit to add: I would be really curious to see a real-world example which uses one of these tokens to perform some operation.
Very interesting! Would there ever be a reason that a TaskRun would want to override the audience? I have to admit I don't fully understand how the audience field works.
Yes! When a serviceAccountToken from a projected volume is _without audience_ it is intended to be used for requests to the ApiServer in the same cluster.
By setting a different audience, e.g. audience: vault or audience: docker-registry or audience: deploy-to-cloud-run, the token _can not be used_ for requests to the ApiServer (or any service other than written in audience).
This is great for security. If I have a normal service-account-token today (not from projected volume) and use if from Task-A to authenticate to Service-B. Then if Service-B is compromised, it could use the received token and initiate requests to the ApiServer and do the operations that Task-A is authorized to do (perhaps create namespaces and deploy pods?). The token could aslo be logged and this is dangerous since it never expire - but tokens from projected volumes will expire after minutes/hours.
These different tokens: audience: vault or audience: docker-registry or audience: deploy-to-cloud-run can now only be used for authenticating to the intended services. e.g. within Google Cloud, by using workload identity service accounts can now be "linked" to GCP IAM service-accounts, so that there is no need to copy and distribute secrets (as what I understand, haven't tried).
Essentially, using service accounts to authenticating to different services, is a lot more easy than creating passwords and other credentials that then need to be distributed. ServiceAccounts with tokens that are often automatically rotated and has a single-audience is a very secure way to solve authentication. I would love to only have serviceAccountTokens in my CI/CD pipeline - without any need to handle and distribute secrets.
Hopping in late, but here's an idea to add on to approach 2, but relies more on the existing k8s Secrets API.
Treat secrets like workspaces by providing a mechanism to bind them in tasks and allow users to reference them by name to let users use them with the Secrets API.
Task:
secretBinding:
- name: foo
config: github
...
spec:
...
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: $(secrets.foo.name)
key: username
volumes:
- name: secret-volume
secret:
secretName: $(secrets.foo.name)
TaskRun:
secretBinding:
- name: foo
ref: my-secret # Reference to secret within the Namespace
In the task definition, users can specify a user configurable label for what secret to default to if not provided. This allows for most operators to set a credential once, and be able to use the same one automatically across multiple tasks (e.g. remove the burden of having to specify the GitHub token secret for all runs).
If needed, this can be configured per namespace, or overridden at Task/PipelineRun time by providing secretBinding.
e.g.
apiVersion: v1
kind: ConfigMap
metadata:
name: tekton-creds-config. # Name TBD, but should be some Tekton identifiable config for the namespace.
data:
github: github-token. # Reference to Secret in the namespace.
external-sa: sa-config
...
Most importantly, this also avoids and list permissions needed for secrets, as long as the controller has access to the configmap (which I expect to not be an issue).
The main advantage of configuring things by reference is that it unlocks the full range of the Secret API within tasks, allowing normal pod-like configuration for both file and env var secrets. For example, this would allow Tasks to individually configure projected volumes as requested by @jlpettersson.
Coupling this with a namespaced default configuration means that in most cases, users shouldn't need to actually provide credentials in their task runs assuming they've been set up once for the namespace. Since Secrets are also configured per namespace, this should be a reasonable to do, and we could possibly make this easier to do via tkn (e.g. tkn secrets config <label> <secretRef>?)
This has some drawbacks - notably this takes away some of the ease of setup for secrets within Tasks, and makes the optional secrets difficult since the expectation of most tasks is that if it requires a secret that it will be provided. That said, I'm not sure how common this behavior would be in practice.
At minimum, I think it'd be great to add the config defaulting behavior to make it easier to provide credentials without explicit user configuration.
Open question whether the direct usage of the Secrets API is better than the credentials setup proposed in approach 2.
Thoughts? :D
@sbwsg I created an _alternative_ git-clone task in Catalog, https://github.com/tektoncd/catalog/pull/309 and wrote a README about how to setup SSH authentication to e.g. GitHub, solely by creating Secrets and mount them with a projected volume, and configure the git command to use paths to located them.
It shows an authentication:
It's different. But I can see how the current git-clone originated from a PipelineResource. I don't know the requirements or history of the current one, but this was an initiative to PoC an alternative.
@sbwsg I created an alternative git-clone task in Catalog, tektoncd/catalog#309 and wrote a README about how to setup SSH authentication to e.g. GitHub, solely by creating Secrets and mount them with a projected volume, and configure the git command to use paths to located them.
It took me way too long to fully understand the implications of this. Essentially I think we're talking about removing any credentials-specific code from Tekton and simply relying on existing Kubernetes mechanisms (and/or external systems like Vault) for credential handling. Is that right? I can definitely see the desirability of this both from a user perspective (don't need to learn something Tekton specific) and from an implementer's view (dont need to maintain something).
@sbwsg I created an alternative git-clone task in Catalog, tektoncd/catalog#309 and wrote a README about how to setup SSH authentication to e.g. GitHub, solely by creating Secrets and mount them with a projected volume, and configure the git command to use paths to located them.
It took me way too long to fully understand the implications of this. Essentially I think we're talking about removing any credentials-specific code from Tekton and simply relying on existing Kubernetes mechanisms (and/or external systems like Vault) for credential handling. Is that right? I can definitely see the desirability of this both from a user perspective (don't need to learn something Tekton specific) and from an implementer's view (dont need to maintain something).
Is that right?
Yes, thats how I meant about it. But I also see that it is not a very clear way forward.
I brought this up in last weeks API WG, and elaborated about this in https://github.com/tektoncd/pipeline/issues/2680
I also create a new git-clone-ssh PR https://github.com/tektoncd/catalog/pull/332/commits/92fb4a0e6b68e98c6d8027ff6ba34c9fe3aca325 that includes instructions on how to configure authentication for this alternative task. But this only address ssh-setup.
Note that https://github.com/tektoncd/catalog/pull/332 contains improvements (using TaskRunSpec introduced in https://github.com/tektoncd/pipeline/pull/2389) that was not avaliable when I created the tasks in earlier comments above here. E.g. my PR https://github.com/tektoncd/catalog/pull/309 - as how I mounted the volume there is "deprecated"
Also imagePullSecrets was added to TaskRunSpec in https://github.com/tektoncd/pipeline/pull/2547
@sbwsg we could create a design doc on the way forward here? As you commented in slack, the way above does not have a clear way forward for ENV, but that could possibly be added to TaskRunSpec as well? We could also elaborate about a "migration path" from the auth we have today, to a more k8s native?
As you commented in slack, the way above does not have a clear way forward for ENV
Looking at the variable replacement code it seems we might have a way to do this today as well.
A Task could declare:
params:
- name: access-key-secret-name
- name: access-key-secret-key
steps:
- name: use-access-key
image: # something
env:
name: SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: $(params.access-key-secret-name)
key: $(params.access-key-secret-key)
script: |
# Do some stuff with $SECRET_ACCESS_KEY
I'm starting to come around to the idea that we might not need to provide Tekton-specific mechanisms for getting credentials into Tasks and Runs. There are still quite a lot of questions in my mind about this - What do errors look like for missing or invalid Secrets? How well do external systems like Vault inject Secrets into Runs? - but it does seem quite desirable to lean on the existing ecosystem for this problem area rather than invent another system.
I also really like the idea of documenting migration away from today's auth/creds-init workflow!
@sbwsg we could create a design doc on the way forward here?
What would be the best way to approach this? Document the existing possibilities (creds-init, Workspace Secrets, "k8s-native" approach that you've described) and then suggest recommendations which we could create documentation for? Beyond documentation do you think there's more we could do?
@sbwsg we could create a design doc on the way forward here?
What would be the best way to approach this? Document the existing possibilities (creds-init, Workspace Secrets, "k8s-native" approach that you've described) and then suggest recommendations which we could create documentation for? Beyond documentation do you think there's more we could do?
I am not sure. I was thinking, maybe we could generate Event from creds-init if the user is using e.g. Secrets with annotations and inform that the user is using a feature that will be deprecated/removed. At the same time we must have a good documentation story about how to do it the "other" way? Eventually we could hide creds-init behind a toggle, that is initially on by default, and a release later it could be off by default (but still manually enabled) and later remove the older auth?
A few streps for current users. But new users could follow and use a newer workflow for configuring auth.
We possibly have to think about how to do this in a smooth way for git-init-task.
Any other ideas?
Ah, nice idea about the Events! I hadn't considered that.
The other thing, and it might be totally impossible, would be to try and introduce guidelines for catalog tasks so that they all uniformly accept credentials in the same way. I'm unsure if that would be feasible though.
The one major benefit of the creds-init approach is that it is a cross-cutting style compared to Workspaces and k8s-native. A Task does not need to specify credentials accepted, the system just implicitly "knows" the way to wire things up. Unfortunately it is just so difficult to debug effectively when it goes wrong.
The one major benefit of the
creds-initapproach is that it is a cross-cutting style compared to Workspaces and k8s-native. A Task does not need to specify credentials accepted, the system just implicitly "knows" the way to wire things up. Unfortunately it is just so difficult to debug effectively when it goes wrong.
I think this is just "so so true". The fact is that _few_ Catalog tasks uses "credentials". It mostly is git-init - but that can use credentials for both SSH or token.
All Tasks that interact with an external system should be possible to configure with authentication, a Secret or a ServiceAccountToken.
E.g. the kubectl task does not seem to have "credentials" managed by creds-init.
There is probably a lot of use cases to use the curl task that require authentication.
Same for e.g. the gcloud task
The other thing, and it might be totally impossible, would be to try and introduce guidelines for catalog tasks so that they all uniformly accept credentials in the same way. I'm unsure if that would be feasible though.
Yes, it would be great to have common auth guidelines for tasks. I think a problem is that there are multiple kinds of authentication types, e.g. certificates, passwords and most modern is probably serviceAccountTokens. But maybe we could document the different cases.
One additional idea I have is to audit the catalog and make a tally of the existing tasks' creds usage:
OK, here's a breakdown of the catalog by Task. For each entry I've recorded the kinds of credential mechanisms supported, how the creds appear in the container (env var / file / etc...), the "type" of cred (user/pass/token/key), whether the cred is exposed to multiple steps or isolated to an individual step, and then any notes i recorded along the way.
https://docs.google.com/spreadsheets/d/1lESYHGDYnx4RfYAs3BQZXrCw5Aa81EWHCaUuxLpp_Sw/edit#gid=0
I haven't processed the data at all yet but it _looks_ like the most common pattern right now is "k8s-native", by which I mean the task explicitly mounts a Secret rather than exposing a workspace or relying on creds-init.
So, made a couple of quick charts from the catalog repo.
First, the breakdown of credential "mechanism". Does the Task expose a workspace? Does it use creds-init? Does it use a platform-dependent mechanism like GKE's Workload Identity? A Task may support multiple mechanisms and so there can be double counts here. It's immediately obvious that the largest grouping are "k8s-native" mechanisms where they mount a Secret as an Env Var or Volume. Some of these Tasks expose params to configure precisely which Secret and Secret Key is used while many do not and simply rely on fixed Secret names and keys.

Second, how are the credentials manifested inside the container? Are they files? Env vars? Arguments passed to a CLI tool? Again a Task may support multiple mechanisms and the mechanisms might manifest differently so there could be double counts. The largest group of Tasks are relying on credentials written to files. There are a number of tasks that are relying on arguments passed to command line tools to provide e.g. username / password. These are a bit worrying to me because it means their usage will expose those credentials in logs / TaskRuns.

Third, what "type" of credential is being supplied? I've categorized these as "userpass", "token", "key" (which includes ssh keys), "cert", "platform-dependent", and "serviceaccount". "platform-dependent" is an interesting case - a Task can expose a workspace and through that accept either files structured for e.g. GKE (.json file) _or_ files structured for e.g. AWS (.aws directory w/ config/credential files). Again caveats about multiple types being supported simultaneously - one task can be counted multiple times.

Edit: I'm going to put some thoughts together and present these at the API WG on Monday.
Over the past few weeks I've been running a survey related to user's knowledge and usage of creds-init. I plan to present the results at the API WG later today but am summarizing them here to make the information more accessible / searchable. We had 21 responses in total, which isn't a huge amount, but I think it's worth being transparent with the data regardless.
Raw data follows but TL;DR here are the highlights from these responses:
First question in the survey asked whether users were aware of Tekton's built-in credential mechanism. Response was 90% in the affirmative:

Second question in the survey asked whether the user or their organization uses Tekton's built-in credentials mechanism. 80% said that they did:

The third question asked how well Tekton's creds-init mechanism supports the user's organization's use-cases more broadly. 24% responded that Tekton supported all of their existing use-cases. 67% responded that _some_ of their use-cases were met by Tekton. 5% responded that they do not use Tekton in their organization and 5% requested improved documentation specifically around the Kaniko task's use of Docker credentials:

The final question was a request for any further comments. I've paraphrased them a bit here to avoid identifying info:
We absolutely depend on this feature because it makes it way easier for customers to bring their own auth credentials and wire them up in to their pipelines. It would be nice if this could be expanded to allow a user to configure multiple keys into the same Tekton task.
Works fine tbh, not had any gripes or complaints from users in the field using \
In Openshift console you can change the service account and I might not have permission to edit secrets. In this case I would not use tekton's built in auth and just use config maps.
I'd prefer to use an explicit binding to declare what credentials should be used at the TaskRun/Resource level, similar to how the ClusterResource and PRResource auth work. The implicit one (annotation hints on secrets) is hard to understand and requires the controller to run with more permissions that it should need (list secrets in all namespaces).
We use Tekton's existing auth mechanism in \
Auth plugins for docker seem tricky to get right. Not sure I have a better solution, but they seem to cause headaches. I don't know if a sidecar/proxy model would work better for developers (it would be more of a pain for managing, and might run into other issues, since it would effectively MITM the tasks from the registry/git).
(Related to the way Kaniko's support for docker creds works) Docker authentication using docker config file ~/.docker/config.json is something I would like to see it as documented here: https://github.com/tektoncd/pipeline/blob/master/docs/auth.md#basic-authentication-docker in addition to username and password.
Something @bobcatfish raised today while we were talking about credentials is the idea of conformance and platforms conforming to the Tekton API.
In a world where creds-init is part of Tekton conformance it's very difficult to imagine a system being Tekton-compatible on anything but Kubernetes. This is down to the heavy reliance of creds-init on specific k8s concepts like Secrets, Annotations, Service Accounts, etc. But Tekton as an API has always had a goal of being implementable independent of the underlying infra.
Most helpful comment
I definitely prefer the design idea 2. It's more explicit and make a clear distinction from the workspaces