-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve UX of getting credentials into Tasks #2343
Comments
Design Idea 1 - Use Workspaces for CredentialsThe 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: A Workspace can be declared as "optional" by a Task.
A Workspace can be declared as "sensitive" by a Task.
Steps of a Task can declare the Workspaces they need access to
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
# ... Problems with this design
|
Design Idea 2 - Introduce Credentials Sections to CRDsFirst, 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: Credentials are attachable as Directories, Files, or EnvVarsDepending on the choice of
Credentials can be declared as "optional"
Credentials mount with very restrictive access control
Steps must explicitly request credentials
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 Problems with this design
|
Related, I think it would be really cool if 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!
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 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
I agree with that.
But I see this as a positive thing, it is easier to reason about it from a security standpoint as well.
There are many use cases. But for e.g. |
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:
For a non-root pod, this need to be added to the template:
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 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. |
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. 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: 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. IdeaTreat 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. ExampleTask: 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 Config labelsIn 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 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). ComparisonThe 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. 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 Thoughts? :D |
@sbwsg I created an alternative It shows an authentication:
It's different. But I can see how the current |
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). |
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 #2680 I also create a new git-clone-ssh PR tektoncd/catalog@92fb4a0 that includes instructions on how to configure authentication for this alternative task. But this only address ssh-setup. Note that tektoncd/catalog#332 contains improvements (using Also @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 |
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! |
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 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 |
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 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
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 ( 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:
|
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. |
Issues go stale after 90d of inactivity. /lifecycle stale Send feedback to tektoncd/plumbing. |
/remove-lifecycle stale We're quite close to having this issue wrapped up, so keeping it around. |
Expected Behavior
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.
Actual Behavior
Problems with Documentation and User Flow
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:
Problems with Implementation
On the controller-side Pod startup looks as follows with credentials:
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.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.
Requirements and Constraints
Requirements
Constraints
Related Issues
I'm collecting creds-related issues as I spot them.
ssh-keyscan
#2801The text was updated successfully, but these errors were encountered: