Skip to content
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

Using identity metadata in token roles #27

Open
davidcorrigan714 opened this issue Jan 23, 2023 · 7 comments
Open

Using identity metadata in token roles #27

davidcorrigan714 opened this issue Jan 23, 2023 · 7 comments
Assignees
Labels
enhancement New feature or request

Comments

@davidcorrigan714
Copy link
Contributor

We're looking to get this setup in the next few weeks, though I've played with in the past. I'm trying to figure out how I might allow users to authenticate to Vault and then get an API token through Vault for their specific account in Artifactory. For example some templated policy like:

path "artifactory/users/{{identity.entity.id}}" {
  capabilities = [ "read" ]
}

That would allow users to use their authenticated identity in Vault to get an API token for JFrog for their account. Is this doable somehow? The way the roles path is structured now it looks like I could only map devs to some shared role and not their exact identity in Artifactory.

@alexhung alexhung self-assigned this Jan 24, 2023
@alexhung alexhung added the enhancement New feature or request label Jan 24, 2023
@alexhung
Copy link
Member

@davidcorrigan714 AFAIK the plugin does not current support what you suggest. This is an excellent feature to add though. We will add this to our backlog.

@davidcorrigan714
Copy link
Contributor Author

Sounds good, mostly making sure I hadn't missed anything in the configuration. I think I'll have time to work on this later this quarter myself as it's something we were prototyping with a stand-alone service before deciding to stand up Vault. Happy to collaborate to make sure it meets code standards and makes sense with how you all would want it to work so that our work can be upstreamed.

@alexhung
Copy link
Member

@davidcorrigan714 Sounds good. When you're ready to contribute, the process is pretty standard so please open a PR and we can work through any comments, feedback, etc.

@TJM
Copy link
Contributor

TJM commented Feb 28, 2023

The ability to do this would depend on username and scope templates. Once we have the ability to use "variables" when generating the request for the token, we need to find out what is available in the context and make the necessary variables available to the template. Hashicorp has surprising little to no information, other than the code examples. :)

@davidcorrigan714
Copy link
Contributor Author

@TJM not quite sure what you're getting at, have an example of what you have in mind? I just hacked together a proof-of-concept to play with the idea it seemed to work, though maybe not as fine-grained as I might like as far as the policy or configuration which might be what you're getting at.

I created a path at /user/* where it takes the path and issues a token for the user, like so:

func (b *backend) pathUserTokenCreatePerform(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	b.rolesMutex.RLock()
	b.configMutex.RLock()
	defer b.configMutex.RUnlock()
	defer b.rolesMutex.RUnlock()

	user := strings.TrimPrefix(req.Path, "user/")

	config, err := b.fetchAdminConfiguration(ctx, req.Storage)
	if err != nil {
		return nil, err
	}

	if config == nil {
		return logical.ErrorResponse("backend not configured"), nil
	}

	var ttl time.Duration = 1 * time.Hour

	maxLeaseTTL := b.Backend.System().MaxLeaseTTL()
	maxTTL := 1 * time.Hour

	if maxTTL == 0 {
		maxTTL = maxLeaseTTL
	} else if maxTTL > maxLeaseTTL {
		maxTTL = maxLeaseTTL
	}

	if maxTTL > 0 && ttl > maxTTL {
		ttl = maxTTL
	}

	b.Logger().Warn("Path: " + user)

	role := &artifactoryRole{
		GrantType:  "client_credentials",
		Username:   user,
		Scope:      "applied-permissions/user",
		Audience:   "*@*",
		DefaultTTL: time.Duration(ttl.Seconds()),
	}
	roleName := user

	resp, err := b.createToken(*config, *role)
	if err != nil {
		return nil, err
	}

	response := b.Secret(SecretArtifactoryAccessTokenType).Response(map[string]interface{}{
		"access_token": resp.AccessToken,
		"role":         roleName,
		"scope":        resp.Scope,
		"token_id":     resp.TokenId,
	}, map[string]interface{}{
		"role":         roleName,
		"access_token": resp.AccessToken,
		"token_id":     resp.TokenId,
	})

	response.Secret.TTL = ttl
	response.Secret.MaxTTL = maxTTL

	return response, nil
}

I created a policy:

path "artifactory/user/{{identity.entity.name}}" {
  capabilities = [ "read" ]
}

I then added the OIDC provider to log me in through the corporate SSO, which aligns with our federated user names into Artifactory, so I could get a token for myself from artifactory/user/[email protected] but not artifactory/user/[email protected] .

@TJM
Copy link
Contributor

TJM commented Feb 28, 2023

Hah, see I was trying to make it more difficult. I was looking at creating a role, for example named "my-account" ... which would lookup the user identity (somehow) and then use the username during the b.CreateToken process... you are creating a new path with the username in it... nice.

@davidcorrigan714
Copy link
Contributor Author

Initial PR for feedback: #113

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants