Skip to content

Investigate coder as an auth method for vault #13127

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

Open
matifali opened this issue May 2, 2024 · 5 comments
Open

Investigate coder as an auth method for vault #13127

matifali opened this issue May 2, 2024 · 5 comments
Labels
use-case A novel and interesting way to use Coder

Comments

@matifali
Copy link
Member

matifali commented May 2, 2024

Extracted from #11084 (comment) Probably depends on #11084

For vault we may look into as becoming one of the supported auth methods
More details: https://developer.hashicorp.com/vault/docs/concepts/auth

I think our flow can work best similar to GitHub auth method. Where we pre-configure vault with a Coder OAuth app and then can login simply
With

vault login -method=coder token=$CODER_SESSION_TOKEN

Reference: https://developer.hashicorp.com/vault/docs/auth/github

Use case(s)

  1. We can build a new module that automatically authenticates each coder workspace with vault without involving any 3rd party. We currently have a vault module that depends on GitHub auth.
  2. Could also help realize User-level secrets #7087 by using Vault as the backend.
@coder-labeler coder-labeler bot added the use-case A novel and interesting way to use Coder label May 2, 2024
@matifali
Copy link
Member Author

matifali commented Sep 9, 2024

We can also investigate this by creating a vault plugin.

@matifali
Copy link
Member Author

matifali commented Nov 5, 2024

We have published a vault module that allows using the OIDC access tokenn from the shared IdP provider for Coder and Vault and automatically logs the user into Vault.

This works by configuring Vault with JWT auth and reusing the Coder's OIDC access token to exchange for a Vault token.

@moo-im-a-cow
Copy link

We have published a vault module that allows using the OIDC access tokenn from the shared IdP provider for Coder and Vault and automatically logs the user into Vault.

This works by configuring Vault with JWT auth and reusing the Coder's OIDC access token to exchange for a Vault token.

from a security point of view,
the jwt token that was used to log into coder has an audience of https://coder.example.com, and should only ever be accepted by the coder instance itself.
if vault is configured correctly, it should reject this token,
and only accept tokens issued with an audience of https://vault.example.com

it would make sense to do what gitlab CI does for authentication to vault / other systems,
https://about.gitlab.com/blog/2023/02/28/oidc/
gitlab acts as its own oidc provider, issues tokens to individual CI jobs.
(the CI JOB + the machine it runs on is the subject, the user who logged into gitlab is not the subject)

in the CI config file, you ask gitlab to issue a token for https://vault.example.com
in vault, you create a new jwt auth method that is dedicated to gitlab, and accepts tokens issued by gitlab (using bound_claims to only accept trusted projects/namespaces)
vault knows everything about the CI job, because the token contains CI specific information, e.g. which branch, what repo, what user pushed, what gitlab namespace, the job number, plus more.

how this translates to coder:

the agent (not user) can request a token using it's authentication
the token is generated, but includes a lot of non configurable information, workspace name, provisioner info, workspace owner, organisation, login method, you cant control this, this tells vault exactly how it should treat it

the key thing though,
is that the subject is not the user, the subject is the workspace.
vault isnt accepting authentication from the user, it is accepting authentication from the workspace,
it is technically a different entity to the user.which may or may not have all the same permissions as the user (that's for vault policies to decide),
"automated process X on behalf of user Y", vs "user Y themselves".
this gives vault the choice of whether to treat them as the same entity or a different entity

(if you want to treat the token generated by coder as being the the user themselves, then you would configure vault's jwt config with user_claim="workspace_owner", and add the workspace owner as an alias to the user's entity in vault, so the workspace is issued a token for the user)

e.g. you would be able to use this to give user X different permissions depending on which coder workspace they use to access vault from
if they are accessing from coder organization "top secret", then give them access to the vault secrets in the kv mount called "top secret"

additionally, this token can be refreshed for the entire lifetime of the workspace

once you pass a short lived OIDC token that you have no way to renew without user intervention,
and then start working via vs code desktop
no need to access the coder website and refresh the login token for what seems like months,
that token issued by the authentication provider is long expired,
and you get locked out of vault while still working in your workspace.


how i see this looking from a technical point of view:
http endpoint: /.well-known/openid-configuration: openid config, tells the oauth client where to find the keys, this is part of
http endpoint: /oauth/v2/keys contains the actual keys that coder uses to sign any jwt token issued by itself
(there are probably more endpoints required for oauth that i havent listed above)

the next part, issuing the tokens is up for debate
http endpoint: /api/v2/workspaceagents/oidctoken (or something else that makes sense):
an agent authenticates, coder looks up all the info it can find about the agent and the workspace the agent belongs to, (put as much info as you can in the token)
and then coder responds with a jwt token for authenticating that specific agent to vault or whatever else.

then, there would be a command for coder cli to call that endpoint from inside a workspace
then another "Hashicorp Vault Integration" module can be created that calls a coder command then uses the output of the coder command to authenticate to vault.

from the looks of it, the agents currently authenticate with a session token issued to the user + their agent uuid.
(but ideally, the agent would have it's own coder login tokens that identify it as an agent, not as a user - you can still give it exact same permissions as user who owns the workspace though, just that there's many many good reasons/scenarios to want to be able to tell those apart)

@moo-im-a-cow
Copy link

it might make sense to handle this similar to the external auth providers,
except instead of sending the user somewhere when creating the workspace, coder generates a token itself, issued by coder, but accepted by the external integration.

@moo-im-a-cow
Copy link

moo-im-a-cow commented Apr 22, 2025

this template sort of emulates what i'm talking about, though i dont want to be storing a private key in the template, would much prefer it be generated by coder and given to the template
it uses a modified version of the vault-jwt module that allows you to pass a custom jwt token (pr submitted),
as well as a random jwt generating module i found online, with a private key in the template folder.

https://gist.github.com/moo-im-a-cow/002e18137f5956893e610f85096e04e9#file-main-tf-L394-L422

this generates a jwt token like this:

{
  "agent": "a36714d7-7be9-4f87-ba04-fab7afcb5114",
  "aud": "https://vault.birb.au",
  "iat": 1745244035,
  "iss": "https://code.birb.au",
  "owner": "b94eeac0-5cb4-43bf-93a1-6b1756ccd3ae",
  "owner_email": "birdie@birb.au",
  "owner_groups": [
    "Everyone"
  ],
  "owner_login_type": "password",
  "owner_name": "birdie",
  "provisioner": "ffc654cc-b3e8-45f0-abae-173ca0d9e031",
  "provisioner_arch": "amd64",
  "provisioner_os": "linux",
  "sub": "f29b73bf-d0d0-42fd-a327-be19d1ffe7f8",
  "template": "5956ace7-2442-4827-a9e4-44e4e7173bc5",
  "template_name": "test",
  "template_version": "friendly_maxwell4",
  "workspace": "f29b73bf-d0d0-42fd-a327-be19d1ffe7f8",
  "workspace_name": "brown-chipmunk-42",
  "workspace_port": 443,
  "workspace_url": "https://code.birb.au"
}

then, configure 2 vault roles like this (one with user_claim=sub and one with user_claim=owner_email):

vault write auth/coder/role/workspace -<<EOF
{
  "user_claim": "sub",
  "bound_audiences": "https://vault.birb.au",
  "role_type": "jwt",
  "ttl": "1h",
  "claim_mappings": {
    "owner": "owner",
    "owner_email": "owner_email",
    "owner_login_type": "owner_login_type",
    "owner_name": "owner_name",
    "provisioner": "provisioner",
    "provisioner_arch": "provisioner_arch",
    "provisioner_os": "provisioner_os",
    "sub": "sub",
    "template": "template",
    "template_name": "template_name",
    "template_version": "template_version",
    "workspace": "workspace",
    "workspace_name": "workspace_name",
    "workspace_id": "workspace_id"

}
}
EOF
vault write auth/coder/role/user -<<EOF
{
  "user_claim": "owner_email",
  "bound_audiences": "https://vault.birb.au",
  "role_type": "jwt",
  "ttl": "1h",
  "claim_mappings": {
    "owner": "owner",
    "owner_email": "owner_email",
    "owner_login_type": "owner_login_type",
    "owner_name": "owner_name",
    "provisioner": "provisioner",
    "provisioner_arch": "provisioner_arch",
    "provisioner_os": "provisioner_os",
    "sub": "sub",
    "template": "template",
    "template_name": "template_name",
    "template_version": "template_version",
    "workspace": "workspace",
    "workspace_name": "workspace_name",
    "workspace_id": "workspace_id"
}
}
EOF

The reason this is so useful, is that vault can use every field in that token for policies.

Image

all the data, sent and cryptographically secured by coder can be seen as trustworthy by vault, and can be used to inform decisions

e.g. a very simple example, the vault admin wants to allow me to access a secret only from one specific workspace:
they create a policy:

path "kv/data/app/{{identity.entity.aliases.auth_jwt_5eba5b26.name}}/{{identity.entity.aliases.auth_jwt_5eba5b26.metadata.owner_name}}/{{identity.entity.aliases.auth_jwt_5eba5b26.metadata.workspace_name}}" {
  capabilities = ["create", "read", "update", "delete", "list", "subscribe"]
  subscribe_event_types = ["*"]
}

they place the secret at path kv/app/coder/birdie/brown-chipmunk-42/secret

now, I can only access it from the vault CLI in that one specific workspace in coder,
i cant look at the secret from outside the workspace, its only there
very basic example, but it creates so much opportunity for data security and data loss protection scenarios,
once that data is in vault, there are so many different ways to take advantage of it.

context in oidc token = "user just typed their password in, completed mfa and wanted to access application coder (not vault)"
vs the above: "user created a coder workspace, with template X, and that workspace wants access to vault"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
use-case A novel and interesting way to use Coder
Projects
None yet
Development

No branches or pull requests

2 participants