jfrog / vault-plugin-secrets-artifactory

HashiCorp Vault Secrets Plugin for Artifactory
https://jfrog.com
Apache License 2.0
42 stars 21 forks source link

username template / dynamic username for role #35

Closed TJM closed 1 year ago

TJM commented 1 year ago

Enhancement to allow creating a templated/dynamic username instead of a "static" account. Maybe default to something like a prefix-unixtimestamp or whatever (automation-1677017997)?

Backstory

We have had a problem where "someone" (something) was using an expired token, and because all the different namespaces were using the same role, and thus the same "username" that username was being "blocked" by artifactory. So, even if they had the correct token, they were getting a backoff. If each retrieved token had a dynamically created user account, they couldn't affect eachother.

This may also be relevant to #27

TJM commented 1 year ago

Username Templating: https://developer.hashicorp.com/vault/docs/concepts/username-templating

alexhung commented 1 year ago

@TJM Can you provide an example of how you envision this is used? Reason I ask is that username isn't an arbitrary thing. It must correspond to a valid user in Artifactory. May be I am missing something (since I'm still relatively green on Vault)?

TJM commented 1 year ago

Actually I have that above.. (rolename-UNIXTIMESTAMP). The username does not need to correspond to a valid user in artifactory, they are created dynamically. The group name might need to exist? I need to determine what the "specs" for username are (max length of a dynamic user account, allowed character set, etc).

alexhung commented 1 year ago

Perhaps I'm conflating Vault username with Artifactory token's username. Sounds like they are not the same thing.

TJM commented 1 year ago

https://www.jfrog.com/confluence/display/JFROG/JFrog+Platform+REST+API#JFrogPlatformRESTAPI-CreateToken

We would be using applied-permissions/group:GROUPNAME, but #27 would be using the applied-permissions/USERNAME (cool)

This is fine. I think we could get by with documentation for this, rather than "verifying" the admin token's scope.

Also:

The username is then used to set the subject of the token: <service-id>/users/<username> Limited to 255 characters.

alexhung commented 1 year ago

I'm very familiar with that API and its intricacies 😄

If the scope includes applied-permissions/user then the token's username must be a valid existing user that is enabled (and not locked or disabled).

This is what I'm referring to as the username must correspond to an existing user in Artifactory.

Can you please provide an end-to-end example (vault cli, vault info, artifactory user data?) to illustrate what you are proposing? These contradictory requirements are not clear to me at all.

For example, I attempted to create a token for an user foobar who doesn't exist in my local Artifactory and this is the error message I received:

{
    "error": "invalid_request",
    "error_description": "requested username does not exists or not enabled. username [foobar] user status [DOES NOT EXISTS]"
}
TJM commented 1 year ago

Using access tokens with generated users: https://www.jfrog.com/confluence/display/JFROG/Access+Tokens#AccessTokens-SupportAuthenticationforNon-existingUsers

More docs: https://discuss.hashicorp.com/t/username-templates/46020/4

Source code for mysql database plugin (Template stuff): https://github.com/hashicorp/vault/blob/main/plugins/database/mysql/mysql.go#L29-L99

Source code for template mod: https://github.com/hashicorp/vault/blob/v1.12.2/sdk/helper/template/template.go

I think we can make this work.

TJM commented 1 year ago

Sorry, my last comment was just some docs that were apparently saved in my browser instead of being submitted?

to respond to your last comment...

I am suggesting that we create a group scoped token, such as:

scope=applied-permissions/group:readers ... then you can set username=whatever-you-want-up-to-256-chars, and it should create a token with a dynamic (?) (not created in the user database) for it.

Example:

[tmcneely@local artifactory-secrets-plugin]$ curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:8082/access/api/v1/tokens -d "scope=applied-permissions/groups:readers" -d "username=whatever-you-want-up-to-256-chars"
{
  "token_id" : "0fe0cff5-8e12-43b4-b3c6-4a88d3584707",
  "access_token" : "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJZZjFINmx6SEdhWlcya2NPZGZxdUpCYTY1N2JfamVZcDRraE10NHFEQVBjIn0.eyJleHQiOiJ7XCJyZXZvY2FibGVcIjpcInRydWVcIn0iLCJzdWIiOiJqZmFjQDAxZ3QwOTl4OWptZGRyMGFhYWgwYjkwMGhtXC91c2Vyc1wvd2hhdGV2ZXIteW91LXdhbnQtdXAtdG8tMjU2LWNoYXJzIiwic2NwIjoiYXBwbGllZC1wZXJtaXNzaW9uc1wvZ3JvdXBzOnJlYWRlcnMiLCJhdWQiOiIqQCoiLCJpc3MiOiJqZmFjQDAxZ3QwOTl4OWptZGRyMGFhYWgwYjkwMGhtIiwiaWF0IjoxNjc3NTMwNDIyLCJqdGkiOiIwZmUwY2ZmNS04ZTEyLTQzYjQtYjNjNi00YTg4ZDM1ODQ3MDcifQ.MhSgut0w6emAgPILIh7R6q5FEUnQj4hcnpJw5oOXbjdprHW7W07OXtawTZWA5d0KwmtIOu8v1zt3ByCStmrFSNuZDJY-oy2V0-6cyGXCadn0N6q65FXliRw7Cz79rq-V0S0f5h5CmVtMiyiyv_4t1RlmPNvjVOJlKfnHzBJIa2kqts-8C0lblF5yb4UHSxaKm6GRSKv2kdcugnLxfGamBUGblZM2RYlGTRo2c8-HVMWhBaqyVmruEq2tIdUa6wJl9r0GaQYtnVyqCm6hBcPmhWKcjCnFd5D7Oq4Uj5NIJXI3t_RcFoN1iaooglYZjsZC2OcPrKphT-3GSy-1lp7-vw",
  "scope" : "applied-permissions/groups:readers",
  "token_type" : "Bearer"
}
[tmcneely@local artifactory-secrets-plugin]$ jwt decode eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJZZjFINmx6SEdhWlcya2NPZGZxdUpCYTY1N2JfamVZcDRraE10NHFEQVBjIn0.eyJleHQiOiJ7XCJyZXZvY2FibGVcIjpcInRydWVcIn0iLCJzdWIiOiJqZmFjQDAxZ3QwOTl4OWptZGRyMGFhYWgwYjkwMGhtXC91c2Vyc1wvd2hhdGV2ZXIteW91LXdhbnQtdXAtdG8tMjU2LWNoYXJzIiwic2NwIjoiYXBwbGllZC1wZXJtaXNzaW9uc1wvZ3JvdXBzOnJlYWRlcnMiLCJhdWQiOiIqQCoiLCJpc3MiOiJqZmFjQDAxZ3QwOTl4OWptZGRyMGFhYWgwYjkwMGhtIiwiaWF0IjoxNjc3NTMwNDIyLCJqdGkiOiIwZmUwY2ZmNS04ZTEyLTQzYjQtYjNjNi00YTg4ZDM1ODQ3MDcifQ.MhSgut0w6emAgPILIh7R6q5FEUnQj4hcnpJw5oOXbjdprHW7W07OXtawTZWA5d0KwmtIOu8v1zt3ByCStmrFSNuZDJY-oy2V0-6cyGXCadn0N6q65FXliRw7Cz79rq-V0S0f5h5CmVtMiyiyv_4t1RlmPNvjVOJlKfnHzBJIa2kqts-8C0lblF5yb4UHSxaKm6GRSKv2kdcugnLxfGamBUGblZM2RYlGTRo2c8-HVMWhBaqyVmruEq2tIdUa6wJl9r0GaQYtnVyqCm6hBcPmhWKcjCnFd5D7Oq4Uj5NIJXI3t_RcFoN1iaooglYZjsZC2OcPrKphT-3GSy-1lp7-vw

Token header
------------
{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "Yf1H6lzHGaZW2kcOdfquJBa657b_jeYp4khMt4qDAPc"
}

Token claims
------------
{
  "aud": "*@*",
  "ext": "{\"revocable\":\"true\"}",
  "iat": 1677530422,
  "iss": "jfac@01gt099x9jmddr0aaah0b900hm",
  "jti": "0fe0cff5-8e12-43b4-b3c6-4a88d3584707",
  "scp": "applied-permissions/groups:readers",
  "sub": "jfac@01gt099x9jmddr0aaah0b900hm/users/whatever-you-want-up-to-256-chars"
davidcorrigan714 commented 1 year ago

Trying to understand the root problem here as I think we're going to be doing something similar. Essentially having CI/CD processes assume "roles" in JFrog (ie group) after logging into Vault with the CI/CD pipeline's OIDC tokens (yay secret-free authentication!). In our proof-of-concept I'm constructing the roles through terraform with something like this:

locals {
  jfrog_group_roles = [
    "rnd-pipelines-pre",
    "rnd-pipelines-prod",
    "it-pipelines-pre",
    "it-pipelines-prod",
  ]
}

resource "vault_generic_endpoint" "artifactory_group_roles" {
  depends_on = [
    module.plugins.secret_mounts
  ]

  for_each             = { for group in local.jfrog_group_roles : group => group }
  path                 = "artifactory/roles/${each.key}"
  ignore_absent_fields = true

  data_json = jsonencode({
    username = each.key
    scope    = "applied-permissions/groups:${each.key}"
    # 43200 seconds = 12 hours. Vault will accept '12h' but show a diff
    # because it uses seconds when returning the current state
    default_ttl = 43200
    max_ttl     = 43200
  })
}

In this case the "username" of the token is just the role name, would this end up hitting rate limits as described in the ticket if too many pipelines assumed the same role simultaneously?

TJM commented 1 year ago

Closed by #47