Skip to content

User-level secrets #7087

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
bpmct opened this issue Apr 11, 2023 · 14 comments
Open

User-level secrets #7087

bpmct opened this issue Apr 11, 2023 · 14 comments
Labels
enterprise Enterprise-license / premium functionality roadmap https://coder.com/roadmap. Only humans may set this.

Comments

@bpmct
Copy link
Member

bpmct commented Apr 11, 2023

Some users want their developers to be pre-authenticated with external providers (e.g. Artifactory) when they first create their workspace.

This may be to pull data in the startup script or avoid manual tokens/steps when a user enters their workspace.

Requirements

  • Users can set key/value pairs in their account settings
  • If a template requires these secrets to be set, disable account creation until it is set
  • It is documented that these values should be hidden from startup script output (perhaps mounted as env vars or k8s secrets instead)

To explore

  • Can/should we encrypt secrets in the database?

Potential enhancements

These should be considered in the design of these features

  • Many users would prefer to fetch these values (using the user's OIDC token) from Hashicorp Vault or AWS Secrets engine instead of them being stored in the Coder database
  • Secrets can be fetched or rotated via Terraform
  • Specific templates should not be able to access user secrets
  • Support generic parameters (non-secrets), but not user-wide (e.g. dotfiles URL)

Note

Coder already stores secrets on behalf of users which can be used in templates (OIDC access token, git auth token, SSH key) but arbitrary secrets cannot be defined by an admin.

Related issues

#6636 #7280

@bpmct bpmct added the enterprise Enterprise-license / premium functionality label Apr 11, 2023
@bpmct bpmct added roadmap https://coder.com/roadmap. Only humans may set this. roadmap-maybe Ideas we're considering! Only humans may set this. labels May 8, 2023
@matifali
Copy link
Member

matifali commented May 9, 2023

As the duplicate issue was closed, I am adding my suggestion here,

Secrets should be encrypted and can only be accessed by runtime(e.g., coder_agent)

Also, secrets can be set via CLI or UI

coder secrets set SECRET_NAME

A related example: https://fly.io/docs/reference/secrets/

@ammario
Copy link
Member

ammario commented Jun 14, 2023

An idea is providing a mechanism via Terraform to declare secrets, e.g.:

resource "coder_user_secret" "twilio" {
   id = "twilio"
}

which could then be consumed by another resource, e.g. the agent environment.

The advantages here are:

  1. We can confirm with users which secrets the Template needs before creating a workspace
  2. The template admin has a great deal of control over how secrets are used in the workspace

@matifali
Copy link
Member

matifali commented Jan 13, 2024

I think we should not brand these as secrets and call them user-level environment variables. This will spare us from hiding them in outputs, encrypting them in database.
For actual secrets we should recommend it using third party secret management solutions like, Hashicorp Vault, HVS (Hashicorp Vault Secrets), AWS Secret Manager etc.

@michaelbrewer
Copy link
Contributor

i would help alot if we could configure Hashicorp Vault to store user level secrets.

@matifali
Copy link
Member

@michaelbrewer Take a look at the two integrations we have with Hashicorp.

  1. https://registry.coder.com/modules/vault-github
  2. https://registry.coder.com/modules/hcp-vault-secrets

Using these modules, you should be able to fetch secrets from Hashicorp and use them for other terraform resources. As far as I know, there are still issues with using the fetched secrets in the provider definition.

@matifali
Copy link
Member

matifali commented Mar 29, 2024

@bpmct @kylecarbs, We should probably have a native vault integration to fetch and store deployment/user secrets. This has the added benefit of not worrying about the encryption, as we only fetch them when needed and do not store them in db.

As a nice to have, we can provide a UI to add/update/delete vault tokens.

This could be a Coder-Vault Enterprise feature where the user will link a Vault enterprise namespace with their Coder deployment using AppRole authentication or a totally new coder auth method.

They also offer a go-client to integarte.

@ccll
Copy link

ccll commented May 30, 2024

We should probably have a native vault integration to fetch and store deployment/user secrets

IMHO I don't think this is the real pain here.
Tools like direnv can easily integrate ANY secret store in the workspace, a native Vault integration seems an overkill, and not very extensible as there are many other secret management solutions out there.

Back when Gitpod OSS is still alive I use this technique a lot, in my .envrc I can do:

# /workspace/.envrc

export ONE_SECRET=$(VAULT_TOKEN=<...> vault kv get -address <...> -field=password kv/path/to/my/secret)

export ANOTHER_SECRET=$(call whatever vault API to get my secret)

cat <<EOF > .env
YET_ANOTHER_SECRET=$(curl https://api.my-secret-store.com/v1/path/to/my/secret -H ...)
EOF

Simple, convenient, and elegant.

But the real pain here is I still need to manually transfer and setup my VAULT_TOKEN or some "root" access token to my online secret store, to fetch all my other "child" secrets, each time when I updated my workspace or created new workspace.

I believe the same pain also exists if user is using AWS/GCP online secret stores or using blackbox/sops/git-crypt to store secrets in the git repo, the user's access token or GPG key still needs to be transfered (or gpg-agent be forwarded), none of this is a joy.

The essence here is no matter what secret store you use, there must be at least one "root" secret needs to be inplace.

Also I don't think it is a good idea to put the secret fetching logic in Terraform templates, this would hugely prevent the re-use of templates, it not only force me to create a customized template for each of my project, it also does not help with the real pain here, where should the "root" secret go? How should I pass it in?

The best solution I can think of is just let Coder manage the secrets for user, then expose the secrets in workspace as env vars through Terraform tricks, or through CLI coder secrets get ..., let user decide
whatever they want with these secrets, either use them directly in the app, or use them to fetch more other secrets from another store (maybe with the help of direnv).

This might be the most flexible and simple solution.

Just my two cents, hope I didn't misunderstand the situation here.

@matifali
Copy link
Member

matifali commented May 30, 2024

Thank you, @ccll, for your input.

But the real pain here is I still need to manually transfer and setup my VAULT_TOKEN or some "root" access token to my online secret store, to fetch all my other "child" secrets, each time when I updated my workspace or created new workspace.

There is a module https://registry.coder.com/modules/vault-github, which can use the existing GitHub external auth to authenticate with the Vault and fetch secrets on the user's behalf.

The best solution I can think of is just let Coder manage the secrets for user, then expose the secrets in workspace as env vars through Terraform tricks, or through CLI coder secrets get ..., let user decide
whatever they want with these secrets, either use them directly in the app, or use them to fetch more other secrets from another store (maybe with the help of direnv).

The problem is that Coder has to implement a method to encrypt/decrypt secrets in the DB. That is why it was suggested that a third-party secret manager be used. Even if the coder manages the secrets, it's best to do that by talking to a 3rd party secret management solution. Secret management could be offloaded to the Coder to create/update/delete secrets on the user's behalf. Still, the secrets are never stored in the coder database but in the secret management solution.

@mthemis-provenir
Copy link

If I may be so bold as to add my 2 pennies; while integrations with external secret stores would probably be a boon to Coder, storing and retrieving secrets from a database is not a novel problem. There are plenty of libraries out there that'd handle this very easily, and requiring users to use an external store is, as previously stated, overkill. We don't want to have developers need to go somewhere else to define a password before creating their workspace. Hell, we can already do that if we want, but it's not a great user experience.

We should also consider administrator access. If it's stored in Vault (or other secret store), the likelihood is admins will be able to retrieve those credentials. If they were properly hashed and stored in your database, it would be impossible to admins to get those secrets. Given users will likely use their own cloud / API credentials within their workspace, the possibility of abuse should also be considered.

@ammario
Copy link
Member

ammario commented Oct 4, 2024

We will almost certainly release Secrets w/o any hard requirement to use an external KMS or Vault but instead allow that as an option for increased security. It's hard to do security well w/o an external KMS because we'd either have to store the secrets plaintext in the DB (exposed to DB admins and backups) or accept an encryption key via configuration (tedious UX around rotation).

We don't want to have developers need to go somewhere else to define a password before creating their workspace. Hell, we can already do that if we want, but it's not a great user experience.

Passwords and API keys are indeed a solved problem but a bit different because the server never needs to provide the user with the plaintext representation of either. Instead, we just need to check that $$H(c)=H(c_1)$$ where $$H(c)$$ is stored on the server, never $$c$$.

"Native" Secrets will be sufficient for small teams and trial deployments. We'll push serious deployments towards a secure backend.

@ggjulio
Copy link
Contributor

ggjulio commented Oct 16, 2024

It would be nice to take the devcontainer spec into account.

Gitpod just dropped devcontainer support 2 weeks ago.
IMO coder lack way behind gitpod and daytona when it comes to devcontainer.

This issue would be a good excuse catch up...
First to fill the feature parity gap with daytona, gitpod, and hopefully codespaces.
If the devcontainer experience is improved my company may choose coder over other solutions.

We really like coder's flexibility, but the devcontainer UX is not fun at all.


User story - devcontainer secrets

As a developer

As a user I want to easily specify which secrets are required to run a workspace
Example codespaces :

source: https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/configuring-dev-containers/specifying-recommended-secrets-for-a-repository#about-recommended-secrets

As an administrator

As a template maintainer my goal is to maintain only one or two generic devcontainer templates, I don't want to deal with developer's secrets in my templates. (no hardcoded secrets names)
User should declare the secrets they want in their devcontainer.json, but it should not impact the TF templates.
Coder can retrieve the values in an external vault or in it's own database, I don't really care as long as it is secure.

// devcontainer.json
// [...]
"secrets": {
  "JF_TOKEN": {
    "description": "Jfrog artifactory token used by the post-start script.",
    "documentationUrl": "..."
  },
    "FOO_API_SECRET": {
    "description": "The required api key to target service foo..." 
    "documentationUrl": "..."
  }
}

Implementation ?

To implement this the coder binary should be able to parse the devcontainer.json.
Both before provisionning time (server side) and at runtime (coder agent). (reuse code from envbuilder ? )

Provision time:

In a similar way github do, coder should query the git provider to know which branches exists and fetch the current configuration.

Image

A new field kind in coder parameter may do the trick.
Each value could then be associated with server side code for validation and completion :

data "coder_parameter" "repository_url" {
  name          = "repository_url"
  order         = 1
  display_name  = "Repository URL"
  default       = "https://hostname/.../repo.git"
  description   = "The repository to use"
  mutable       = true
  # The server would use this field to know it's the repository url.
  # It could also guess the coder_external_auth using the hostname in the url 
  kind             = repository_url
  # Not a great UX, no drop down list of projects and repositories...
  # Maybe use separate fields instead...
}
data "coder_parameter" "target_branch" {
  name          = "target_branch"
  order         = 2
  display_name  = "Branch"
  description   = "This branch will be checked out on creation"
  mutable       = true
  kind = target_branch # point to some server side code that will query the git provider
}
data "coder_parameter" "devcontainer_config" {
  order         = 3
  name          = "devcontainer_config"
  display_name  = "Dev container configuration"
  description   = "Your workspace will use this configuration"
  default       = ".devcontainer/devcontainer.json"
  mutable       = true
  type          = "string"
  kind = devcontainer_config # Server will fetch the file right away to parse the secrets object. 
}
# we could add other `kinds` for validating `hostRequirements` ... ram, cpu, storage...
# eg: devcontainer_ram, devcontainer_cpu, devcontainer_storage...

In addition to theses declared coder_parameters, coder server would parse the secrets object and eventually add new parameters on the fly for secrets are not already set. Can be a popup or a second page idk...
(not sure how the UX would look like... depend on the user story)

// devcontainer.json
// [...]
"secrets": {
  "NAME_OF_SECRET_1": {
    "description": "This is the description of the secret.",
    "documentationUrl": "https://example.com/link/to/info"
  },
  "NAME_OF_SECRET_2": { }
}

The runtime

Once we have the secrets saved in the DB or vault, we need to let the devcontainer cli / envbuilder get them.
The devcontainer cli takes an option called --secrets-file :

  --secrets-file                    Path to a json file containing secret environment variables as key-value pairs.  [string]

We could create a new command to generate the secrets file :
CODER_AGENT_TOKEN='...' coder devcontainer secrets --type json -o /path/to/secrets.json

The server would fetch the secrets from it's DB or external vault and return a json file:

// file /path/to/secrets.json
{
  "NAME_OF_SECRET_1": "secret-value1",
  "NAME_OF_SECRET_2": "secret-value2"
}

Then simply running the devcontainer would to the job :
devcontainer up [...] \ --secrets-file /path/to/secrets.json

To refresh secrets without restarting the workspace we could reuse the command to update the secret file + trigger a devcontainer rebuild.

Coder agent may also compare the effective-devcontainer.json with the one in the local git repo.
If user has changed the secrets list, a rebuild may occur to update the secrets in the environment.

And then ?

Once coder binary can parse the config it would be possible to solve other issues + add new features :

  • Fix IDE feedback loop (popup to rebuild devcontainer when effective-devcontainer.json != devcontainer.json
    (coder agent + coder vscode extension)
  • reflect port config from the devcontainer.json back in the workspace UI.
     "forwardPorts": [6080],
     "portsAttributes": {
     	"6080": {
     		"label": "Desktop access (password: vscode)",
     		"onAutoForward": "silent"
     	},
     	"5901": {
     		"label": "VNC TCP port (password: vscode)",
     		"onAutoForward": "silent"
     	}
     },
  • Fixing url port sharing on web ide (with the coder binary + vscode coder extension ? )
  • Use the hostRequirements to ensure workspaces are provionned properly
  • Add new configuration options specific to coder in the customize section
    "customize": {
      "vscode": {},
      "jetbrain": {},
      "codespaces": {},
      "coder":{
         // customize the template per git repository / branch
         "disableAutomaticConfiguration": true,
         "openFiles": [
            "README.md"
         ],
         "repositories": {
           "coder/terraform-provider-coderd": {
              "permissions": {}
           },
           "coder/modules": {},
           "coder/websocket": {},
        },
        "defaultTemplate": {
           "id": "devcontainer-aws-template-id",
            defaultAutoStop: "4h",
           // etc
        },
        "prebuilds": {
            // Configure prebuilds for this repo ? see https://github.com/coder/coder/issues/5325#issuecomment-2227517151
         }
      }
    } 

Happy to help if needed.

@ggjulio
Copy link
Contributor

ggjulio commented Oct 17, 2024

@ammario @matifali is there any plan to work on devcontainer support ?
(not talking about envbuilder but the server, agent, frontend and IDEs )

I haven't seen such a thing so far on github issues.

@matifali
Copy link
Member

Thank you @ggjulio for the detailed user story. We will consider your input when designing this feature. This is definitely on our roadmap and we will keep trying to improve devcontainer compatibility.

@matifali matifali removed the roadmap-maybe Ideas we're considering! Only humans may set this. label Nov 5, 2024
@matifali
Copy link
Member

Proposed an RFC at #17965

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enterprise Enterprise-license / premium functionality roadmap https://coder.com/roadmap. Only humans may set this.
Projects
None yet
Development

No branches or pull requests

7 participants