From 073ee560e498ab000bc8115541207a4f77ff6fb7 Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Thu, 8 Feb 2024 14:28:19 -0500 Subject: [PATCH 1/8] Allow for scoped down group access tokens --- path_token_create.go | 11 +++++++++++ path_token_create_test.go | 2 ++ test_utils.go | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/path_token_create.go b/path_token_create.go index abdb9a2..55c8e18 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -24,6 +24,10 @@ func (b *backend) pathTokenCreate() *framework.Path { Type: framework.TypeDurationSecond, Description: `Override the maximum TTL for this access token. Cannot exceed smallest (system, backend) maximum TTL.`, }, + "scope": { + Type: framework.TypeString, + Description: `Override the scope for this access token.`, + }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ @@ -88,6 +92,13 @@ func (b *backend) pathTokenCreatePerform(ctx context.Context, req *logical.Reque } } + scope := data.Get("scope").(string) + + //use the overridden scope rather than role default + if len(scope) != 0 { + role.Scope = scope + } + var ttl time.Duration if value, ok := data.GetOk("ttl"); ok { ttl = time.Second * time.Duration(value.(int)) diff --git a/path_token_create_test.go b/path_token_create_test.go index b3e8a98..3186986 100644 --- a/path_token_create_test.go +++ b/path_token_create_test.go @@ -16,7 +16,9 @@ func TestAcceptanceBackend_PathTokenCreate(t *testing.T) { t.Run("configure backend", accTestEnv.UpdatePathConfig) t.Run("create role", accTestEnv.CreatePathRole) + t.Run("create admin role", accTestEnv.CreatePathAdminRole) t.Run("create token for role", accTestEnv.CreatePathToken) + t.Run("create scoped down token for admin role", accTestEnv.CreatePathScopedDownToken) t.Run("delete role", accTestEnv.DeletePathRole) t.Run("cleanup backend", accTestEnv.DeletePathConfig) } diff --git a/test_utils.go b/test_utils.go index 90a291b..b08b619 100644 --- a/test_utils.go +++ b/test_utils.go @@ -215,6 +215,27 @@ func (e *accTestEnv) CreatePathRole(t *testing.T) { assert.Nil(t, resp) } +func (e *accTestEnv) CreatePathAdminRole(t *testing.T) { + roleData := map[string]interface{}{ + "role": "admin-role", + "username": "admin", + "scope": "applied-permissions/groups:*", + "audience": "*@*", + "default_ttl": 30 * time.Minute, + "max_ttl": 45 * time.Minute, + } + + resp, err := e.Backend.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.UpdateOperation, + Path: "roles/admin-role", + Storage: e.Storage, + Data: roleData, + }) + + assert.NoError(t, err) + assert.Nil(t, resp) +} + func (e *accTestEnv) ReadPathRole(t *testing.T) { resp, err := e.Backend.HandleRequest(context.Background(), &logical.Request{ Operation: logical.ReadOperation, @@ -259,6 +280,25 @@ func (e *accTestEnv) CreatePathToken(t *testing.T) { assert.Equal(t, "applied-permissions/user", resp.Data["scope"]) } +func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { + resp, err := e.Backend.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.ReadOperation, + Path: "token/admin-role", + Storage: e.Storage, + Data: map[string]interface{}{ + "Scope": "applied-permissions/group:readonly", + }, + }) + + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.NotEmpty(t, resp.Data["access_token"]) + assert.NotEmpty(t, resp.Data["token_id"]) + assert.Equal(t, "admin", resp.Data["username"]) + assert.Equal(t, "admin-role", resp.Data["role"]) + assert.Equal(t, "applied-permissions/groups:readonly", resp.Data["scope"]) +} + func (e *accTestEnv) CreatePathUserToken(t *testing.T) { resp, err := e.Backend.HandleRequest(context.Background(), &logical.Request{ Operation: logical.ReadOperation, From 7084dc64e85d7caf77acd2a8f818b1ade1ccc466 Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Fri, 9 Feb 2024 14:45:12 -0500 Subject: [PATCH 2/8] PR Feedback --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++ path_token_create.go | 2 +- test_utils.go | 4 ++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0730c55..0e4db58 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,55 @@ token_id 06d962b2-63e2-4279-a25d-d2a9cab6507f username v-jenkins-x4mohTA8 ``` +### Scoped Access Tokens + +In order to decouple Artifactory Group maintenance from Vault plugin configuration, you can configure a single role to request Access Tokens for specific groups. This option should be used with extreme care to ensure that your Vault policies are restricting which groups it can request tokens on behalf of. + +Create a role (scope for artifactory >= 7.21.1) + +```sh +vault write artifactory/roles/jenkins \ + username="jenkins-vault" + scope="applied-permissions/groups:admin" \ + default_ttl=1h max_ttl=3h +``` + +Request Access Token for test-group + +```sh +vault read artifactory/token/jenkins scope=applied-permissions/groups:test-group +``` + +Example output (token truncated): + +```console +Key Value +--- ----- +lease_id artifactory/token/jenkins/9hHxV1NlyLzPgmNIzjssRCa9 +lease_duration 1h +lease_renewable true +access_token eyJ2ZXIiOiIyIiw.... +role jenkins +scope applied-permissions/groups:test-group +token_id 06d962b2-63e2-4279-a25d-d2a9cab6507f +username v-jenkins-b0ftbTAG +``` + +Example Vault Policy + +```console +path "artifactory/token/jenkins" { + capabilities = ["read"], + required_parameters = ["scope"], + allowed_parameters = { + "scope" = ["applied-permissions/groups:test-group"] + } + denied_parameters = { + "scope" = ["applied-permissions/groups:admin"] + } +} +``` + ### User Token Path User tokens may be obtained from the `/artifactory/user_token/` endpoint. This is useful in conjunction with [ACL Policy Path Templating](https://developer.hashicorp.com/vault/tutorials/policies/policy-templating) to allow users authenticated to Vault to obtain API tokens in Artfactory for their own account. Be careful to ensure that Vault authentication methods & policies align with user account names in Artifactory. For example the following policy allows users authenticated to the `azure-ad-oidc` authentication mount to obtain a token for Artifactory for themselves, assuming the `upn` metadata is populated in Vault during authentication. diff --git a/path_token_create.go b/path_token_create.go index 55c8e18..a7f0714 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -26,7 +26,7 @@ func (b *backend) pathTokenCreate() *framework.Path { }, "scope": { Type: framework.TypeString, - Description: `Override the scope for this access token.`, + Description: `Override the scope for this access token. This is for advanced use only and should be used in conjunction with Vault policies to manage access. Please consult the Readme`, }, }, Operations: map[logical.Operation]framework.OperationHandler{ diff --git a/test_utils.go b/test_utils.go index b08b619..013b31d 100644 --- a/test_utils.go +++ b/test_utils.go @@ -219,7 +219,7 @@ func (e *accTestEnv) CreatePathAdminRole(t *testing.T) { roleData := map[string]interface{}{ "role": "admin-role", "username": "admin", - "scope": "applied-permissions/groups:*", + "scope": "applied-permissions/groups:admin", "audience": "*@*", "default_ttl": 30 * time.Minute, "max_ttl": 45 * time.Minute, @@ -286,7 +286,7 @@ func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { Path: "token/admin-role", Storage: e.Storage, Data: map[string]interface{}{ - "Scope": "applied-permissions/group:readonly", + "Scope": "applied-permissions/group:test-group", }, }) From dd42b0e9e881824f3991bf03a5c79d7a79f44e41 Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Thu, 8 Feb 2024 14:28:19 -0500 Subject: [PATCH 3/8] Allow for scoped down group access tokens --- path_token_create.go | 19 ++++++++++++++----- test_utils.go | 4 ++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/path_token_create.go b/path_token_create.go index 6ec5c9a..4256bcc 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -26,7 +26,11 @@ func (b *backend) pathTokenCreate() *framework.Path { }, "scope": { Type: framework.TypeString, +<<<<<<< HEAD Description: `Override the scope for this access token. This is for advanced use only and should be used in conjunction with Vault policies to manage access. Please consult the Readme`, +======= + Description: `Override the scope for this access token.`, +>>>>>>> 3a8b970 (Allow for scoped down group access tokens) }, }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -100,11 +104,16 @@ func (b *backend) pathTokenCreatePerform(ctx context.Context, req *logical.Reque } } - scope := data.Get("scope").(string) - - //use the overridden scope rather than role default - if len(scope) != 0 { - role.Scope = scope + if config.AllowScopedTokens { + scope := data.Get("scope").(string) + if len(scope) != 0 { + match, _ := regexp.MatchString(`^applied-permissions/groups:.+$`, scope) + if match == false { + return logical.ErrorResponse("provided scope is invalid"), errors.New("provided scope is invalid") + } + //use the overridden scope rather than role default + role.Scope = scope + } } maxLeaseTTL := b.Backend.System().MaxLeaseTTL() diff --git a/test_utils.go b/test_utils.go index 4e97997..2e301a6 100644 --- a/test_utils.go +++ b/test_utils.go @@ -325,7 +325,11 @@ func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { Path: "token/admin-role", Storage: e.Storage, Data: map[string]interface{}{ +<<<<<<< HEAD "Scope": "applied-permissions/group:test-group", +======= + "Scope": "applied-permissions/group:readonly", +>>>>>>> 3a8b970 (Allow for scoped down group access tokens) }, }) From e8d24bb08c56521936f459aa2746b99d3296284e Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Fri, 9 Feb 2024 14:45:12 -0500 Subject: [PATCH 4/8] PR Feedback --- path_token_create.go | 4 ---- test_utils.go | 4 ---- 2 files changed, 8 deletions(-) diff --git a/path_token_create.go b/path_token_create.go index 4256bcc..4f98076 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -26,11 +26,7 @@ func (b *backend) pathTokenCreate() *framework.Path { }, "scope": { Type: framework.TypeString, -<<<<<<< HEAD - Description: `Override the scope for this access token. This is for advanced use only and should be used in conjunction with Vault policies to manage access. Please consult the Readme`, -======= Description: `Override the scope for this access token.`, ->>>>>>> 3a8b970 (Allow for scoped down group access tokens) }, }, Operations: map[logical.Operation]framework.OperationHandler{ diff --git a/test_utils.go b/test_utils.go index 2e301a6..a9667d0 100644 --- a/test_utils.go +++ b/test_utils.go @@ -325,11 +325,7 @@ func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { Path: "token/admin-role", Storage: e.Storage, Data: map[string]interface{}{ -<<<<<<< HEAD - "Scope": "applied-permissions/group:test-group", -======= "Scope": "applied-permissions/group:readonly", ->>>>>>> 3a8b970 (Allow for scoped down group access tokens) }, }) From 2030e53c6dd324c6d261ddf0bec551220f59668e Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Tue, 27 Feb 2024 14:50:06 -0500 Subject: [PATCH 5/8] Add configuration option to opt into scoped tokens --- path_config.go | 14 ++++++++++++++ path_config_test.go | 5 +++++ path_token_create.go | 24 ++++++++++++------------ path_token_create_test.go | 1 + test_utils.go | 26 +++++++++++++++++++++++--- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/path_config.go b/path_config.go index 038737a..9530723 100644 --- a/path_config.go +++ b/path_config.go @@ -40,6 +40,11 @@ func (b *backend) pathConfig() *framework.Path { Default: false, Description: "Optional. Bypass certification verification for TLS connection with Artifactory. Default to `false`.", }, + "allow_scoped_tokens": { + Type: framework.TypeBool, + Default: false, + Description: "Optional. Determine if scoped tokens should be allowed. This is an advanced configuration option. Default to `false`.", + }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ @@ -72,6 +77,9 @@ usernames if a static one is not provided. An optional "bypass_artifactory_tls_verification" parameter will enable bypassing the TLS connection verification with Artifactory. +An optional "allow_scoped_tokens" parameter will enable issuing scoped tokens with Artifactory. This is an advanced option that must +have more sophisticated Vault policies. Please see README for an example. + No renewals or new tokens will be issued if the backend configuration (config/admin) is deleted. `, } @@ -81,6 +89,7 @@ type adminConfiguration struct { baseConfiguration UsernameTemplate string `json:"username_template,omitempty"` BypassArtifactoryTLSVerification bool `json:"bypass_artifactory_tls_verification,omitempty"` + AllowScopedTokens bool `json:"allow_scoped_tokens,omitempty"` } // fetchAdminConfiguration will return nil,nil if there's no configuration @@ -143,6 +152,10 @@ func (b *backend) pathConfigUpdate(ctx context.Context, req *logical.Request, da config.BypassArtifactoryTLSVerification = val.(bool) } + if val, ok := data.GetOk("allow_scoped_tokens"); ok { + config.AllowScopedTokens = val.(bool) + } + if config.AccessToken == "" { return logical.ErrorResponse("access_token is required"), nil } @@ -219,6 +232,7 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, _ *f "version": b.version, "use_expiring_tokens": config.UseExpiringTokens, "bypass_artifactory_tls_verification": config.BypassArtifactoryTLSVerification, + "allow_scoped_tokens" : config.AllowScopedTokens, } // Optionally include username_template diff --git a/path_config_test.go b/path_config_test.go index cdb2623..77250f4 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -25,6 +25,7 @@ func TestAcceptanceBackend_PathConfig(t *testing.T) { t.Run("read", accTestEnv.ReadPathConfig) t.Run("expiringTokens", accTestEnv.PathConfigUpdateExpiringTokens) t.Run("bypassArtifactoryTLSVerification", accTestEnv.PathConfigUpdateBypassArtifactoryTLSVerification) + t.Run("allowScopedTokens", accTestEnv.PathConfigUpdateAllowScopedTokens) t.Run("usernameTemplate", accTestEnv.PathConfigUpdateUsernameTemplate) t.Run("delete", accTestEnv.DeletePathConfig) t.Run("errors", accTestEnv.PathConfigUpdateErrors) @@ -45,6 +46,10 @@ func (e *accTestEnv) PathConfigUpdateBypassArtifactoryTLSVerification(t *testing e.pathConfigUpdateBooleanField(t, "bypass_artifactory_tls_verification") } +func (e *accTestEnv) PathConfigUpdateAllowScopedTokens(t *testing.T) { + e.pathConfigUpdateBooleanField(t, "allow_scoped_tokens") +} + func (e *accTestEnv) pathConfigUpdateBooleanField(t *testing.T, fieldName string) { // Boolean e.UpdateConfigAdmin(t, testData{ diff --git a/path_token_create.go b/path_token_create.go index 4f98076..9109b61 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -100,18 +100,6 @@ func (b *backend) pathTokenCreatePerform(ctx context.Context, req *logical.Reque } } - if config.AllowScopedTokens { - scope := data.Get("scope").(string) - if len(scope) != 0 { - match, _ := regexp.MatchString(`^applied-permissions/groups:.+$`, scope) - if match == false { - return logical.ErrorResponse("provided scope is invalid"), errors.New("provided scope is invalid") - } - //use the overridden scope rather than role default - role.Scope = scope - } - } - maxLeaseTTL := b.Backend.System().MaxLeaseTTL() b.Logger().Debug("initialize maxLeaseTTL to system value", "maxLeaseTTL", maxLeaseTTL) @@ -151,6 +139,18 @@ func (b *backend) pathTokenCreatePerform(ctx context.Context, req *logical.Reque role.ExpiresIn = maxLeaseTTL } + if config.AllowScopedTokens { + scope := data.Get("scope").(string) + if len(scope) != 0 { + match, _ := regexp.MatchString(`^applied-permissions/groups:.+$`, scope) + if !match { + return logical.ErrorResponse("provided scope is invalid"), errors.New("provided scope is invalid") + } + //use the overridden scope rather than role default + role.Scope = scope + } + } + resp, err := b.CreateToken(config.baseConfiguration, *role) if err != nil { return nil, err diff --git a/path_token_create_test.go b/path_token_create_test.go index 46b7512..b2aa9f6 100644 --- a/path_token_create_test.go +++ b/path_token_create_test.go @@ -19,6 +19,7 @@ func TestAcceptanceBackend_PathTokenCreate(t *testing.T) { t.Run("create admin role", accTestEnv.CreatePathAdminRole) t.Run("create token for role", accTestEnv.CreatePathToken) t.Run("create scoped down token for admin role", accTestEnv.CreatePathScopedDownToken) + t.Run("create scoped down token for admin role with bad token", accTestEnv.CreatePathScopedDownTokenBadScope) t.Run("delete role", accTestEnv.DeletePathRole) t.Run("cleanup backend", accTestEnv.DeletePathConfig) } diff --git a/test_utils.go b/test_utils.go index a9667d0..d251cc1 100644 --- a/test_utils.go +++ b/test_utils.go @@ -117,8 +117,9 @@ func (e *accTestEnv) revokeTestToken(t *testing.T, accessToken string, tokenID s func (e *accTestEnv) UpdatePathConfig(t *testing.T) { e.UpdateConfigAdmin(t, testData{ - "access_token": e.AccessToken, - "url": e.URL, + "access_token": e.AccessToken, + "url": e.URL, + "allow_scoped_tokens": true, }) } @@ -335,7 +336,26 @@ func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { assert.NotEmpty(t, resp.Data["token_id"]) assert.Equal(t, "admin", resp.Data["username"]) assert.Equal(t, "admin-role", resp.Data["role"]) - assert.Equal(t, "applied-permissions/groups:readonly", resp.Data["scope"]) + assert.Equal(t, "applied-permissions/groups:test-group", resp.Data["scope"]) +} + +func (e *accTestEnv) CreatePathScopedDownTokenBadScope(t *testing.T) { + resp, err := e.Backend.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.ReadOperation, + Path: "token/admin-role", + Storage: e.Storage, + Data: map[string]interface{}{ + "scope": "blueberries?pancakes", + }, + }) + + assert.Error(t, err, "provided scope is invalid") + assert.NotNil(t, resp) + assert.Empty(t, resp.Data["access_token"]) + assert.Empty(t, resp.Data["token_id"]) + assert.NotEqual(t, "admin", resp.Data["username"]) + assert.NotEqual(t, "admin-role", resp.Data["role"]) + assert.NotEqual(t, "applied-permissions/groups:test-group", resp.Data["scope"]) } func (e *accTestEnv) CreatePathUserToken(t *testing.T) { From 417f0cfe9c343e8ca3596e6b77feec43af5bcbae Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Tue, 27 Feb 2024 15:22:04 -0500 Subject: [PATCH 6/8] Update readme with scoped down token config --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 44e3140..42d3b3a 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,17 @@ vault write artifactory/config/admin \ use_expiring_tokens=true ``` +#### Use Scoped down Tokens + +In order to decouple Artifactory Group maintenance from Vault plugin configuration, you can configure a single role to request Access Tokens for specific groups. This option should be used with extreme care to ensure that your Vault policies are restricting which groups it can request tokens on behalf of. + +```sh +vault write artifactory/config/admin \ + url=https://artifactory.example.org \ + access_token=$TOKEN \ + allow_scoped_tokens=true +``` + ## Usage Create a role (scope for artifactory >= 7.21.1) @@ -339,7 +350,7 @@ vault write artifactory/roles/jenkins \ default_ttl=1h max_ttl=3h ``` -Request Access Token for test-group +Request Access Token for `test-group` ```sh vault read artifactory/token/jenkins scope=applied-permissions/groups:test-group @@ -495,10 +506,10 @@ Configures default values for the `user_token/:user-name` path. The optional `us * `access_token` (stirng) - Optional. User identity token to access Artifactory. If `username` is not set then this token will be used for *all* users. * `refresh_token` (string) - Optional. Refresh token for the user access token. If `username` is not set then this token will be used for *all* users. * `audience` (string) - Optional. See the JFrog Platform REST documentation on [Create Token](https://jfrog.com/help/r/jfrog-rest-apis/create-token) for a full and up to date description. Service ID must begin with valid JFrog service type. Options: jfrt, jfxr, jfpip, jfds, jfmc, jfac, jfevt, jfmd, jfcon, or *. For instructions to retrieve the Artifactory Service ID see this [documentation](https://jfrog.com/help/r/jfrog-rest-apis/get-service-id) -* `refreshable` (boolean) - Optional. A refreshable access token gets replaced by a new access token, which is not what a consumer of tokens from this backend would be expecting; instead they'd likely just request a new token periodically. Set this to `true` only if your usage requires this. See the JFrog Platform documentation on [Generating Refreshable Tokens](https://jfrog.com/help/r/jfrog-platform-administration-documentation/generating-refreshable-tokens) for a full and up to date description. Defaults to `false`. -* `include_reference_token` (boolean) - Optional. Generate a Reference Token (alias to Access Token) in addition to the full token (available from Artifactory 7.38.10). A reference token is a shorter, 64-character string, which can be used as a bearer token, a password, or with the `X-JFrog-Art-Api`header. Note: Using the reference token might have performance implications over a full length token. Defaults to `false`. -* `use_expiring_tokens` (boolean) - Optional. If Artifactory version >= 7.50.3, set `expires_in` to `ttl` and `force_revocable = true`. Defaults to `false`. -* `default_ttl` (int64) - Optional. Default TTL for issued user access tokens. If unset, uses the backend's `default_ttl`. Cannot exceed `max_ttl`. +* `refreshable` (boolean) - Optional. A refreshable access token gets replaced by a new access token, which is not what a consumer of tokens from this backend would be expecting; instead they'd likely just request a new token periodically. Set this to `true` only if your usage requires this. See the JFrog Platform documentation on [Generating Refreshable Tokens](https://jfrog.com/help/r/jfrog-platform-administration-documentation/generating-refreshable-tokens) for a full and up to date description. Defaults to `false`. +* `include_reference_token` (boolean) - Optional. Generate a Reference Token (alias to Access Token) in addition to the full token (available from Artifactory 7.38.10). A reference token is a shorter, 64-character string, which can be used as a bearer token, a password, or with the `X-JFrog-Art-Api`header. Note: Using the reference token might have performance implications over a full length token. Defaults to `false`. +* `use_expiring_tokens` (boolean) - Optional. If Artifactory version >= 7.50.3, set `expires_in` to `ttl` and `force_revocable = true`. Defaults to `false`. +* `default_ttl` (int64) - Optional. Default TTL for issued user access tokens. If unset, uses the backend's `default_ttl`. Cannot exceed `max_ttl`. * `default_description` (string) - Optional. Default token description to set in Artifactory for issued user access tokens. #### Examples @@ -541,7 +552,7 @@ vault delete artifactory/config/user_token/myuser * `audience` (string) - Optional. See the JFrog Platform REST documentation on [Create Token](https://jfrog.com/help/r/jfrog-rest-apis/create-token) for a full and up to date description. Service ID must begin with valid JFrog service type. Options: jfrt, jfxr, jfpip, jfds, jfmc, jfac, jfevt, jfmd, jfcon, or *. For instructions to retrieve the Artifactory Service ID see this [documentation](https://jfrog.com/help/r/jfrog-rest-apis/get-service-id) * `include_reference_token` (boolean) - Optional. Generate a Reference Token (alias to Access Token) in addition to the full token (available from Artifactory 7.38.10). A reference token is a shorter, 64-character string, which can be used as a bearer token, a password, or with the `X-JFrog-Art-Api`header. Note: Using the reference token might have performance implications over a full length token. Defaults to `false`. * `default_ttl` (int64) - Default TTL for issued user access tokens. If unset, uses the backend's `default_ttl`. Cannot exceed `max_ttl`. -* `max_ttl` (int64) - Maximum TTL that an access token can be renewed for. If unset, uses the backend's `max_ttl`. Cannot exceed backend's `max_ttl`. +* `max_ttl` (int64) - Maximum TTL that an access token can be renewed for. If unset, uses the backend's `max_ttl`. Cannot exceed backend's `max_ttl`. #### Examples @@ -604,7 +615,7 @@ Provides optional parameters to override default values for the user_token/:user * `description` (string) - Optional. Override the token description to set in Artifactory for issued user access tokens. * `refreshable` (boolean) - Optional. Override the `refreshable` for this access token. Defaults to `false`. * `include_reference_token` (boolean) - Optional. Override the `include_reference_token` for this access token. Defaults to `false`. -* `use_expiring_tokens` (boolean) - Optional. Override the `use_expiring_tokens` for this access token. If Artifactory version >= 7.50.3, set `expires_in` to `ttl` and `force_revocable = true`. Defaults to `false`. +* `use_expiring_tokens` (boolean) - Optional. Override the `use_expiring_tokens` for this access token. If Artifactory version >= 7.50.3, set `expires_in` to `ttl` and `force_revocable = true`. Defaults to `false`. * `ttl` (int64) - Optional. Override the default TTL when issuing this access token. Cannot exceed smallest (system, backend, role, this request) maximum TTL. * `max_ttl` (int64) - Optional. Override the maximum TTL for this access token. Cannot exceed smallest (system, backend) maximum TTL. From 2bd224c15bb68e373c53d223eac02650ace6fc97 Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Tue, 27 Feb 2024 15:22:56 -0500 Subject: [PATCH 7/8] Remove dup string --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 42d3b3a..ff07877 100644 --- a/README.md +++ b/README.md @@ -339,8 +339,6 @@ username v-jenkins-x4mohTA8 ### Scoped Access Tokens -In order to decouple Artifactory Group maintenance from Vault plugin configuration, you can configure a single role to request Access Tokens for specific groups. This option should be used with extreme care to ensure that your Vault policies are restricting which groups it can request tokens on behalf of. - Create a role (scope for artifactory >= 7.21.1) ```sh From 5cd6689b9858f7b4228a1706360ad44d6762d85d Mon Sep 17 00:00:00 2001 From: Kevin Kronenbitter Date: Mon, 11 Mar 2024 14:46:16 -0400 Subject: [PATCH 8/8] PR feedback --- README.md | 8 ++++++-- path_config.go | 12 ++++++------ path_config_test.go | 6 +++--- path_token_create.go | 11 +++++++++-- path_token_create_test.go | 2 +- test_utils.go | 5 +++-- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ff07877..853d0bb 100644 --- a/README.md +++ b/README.md @@ -265,15 +265,16 @@ vault write artifactory/config/admin \ use_expiring_tokens=true ``` -#### Use Scoped down Tokens +#### Enable Scoped down Tokens +[!WARNING] In order to decouple Artifactory Group maintenance from Vault plugin configuration, you can configure a single role to request Access Tokens for specific groups. This option should be used with extreme care to ensure that your Vault policies are restricting which groups it can request tokens on behalf of. ```sh vault write artifactory/config/admin \ url=https://artifactory.example.org \ access_token=$TOKEN \ - allow_scoped_tokens=true + allow_scope_override=true ``` ## Usage @@ -339,6 +340,9 @@ username v-jenkins-x4mohTA8 ### Scoped Access Tokens +[!IMPORTANT] +In order to use this functionality, you must enable `allow_scope_override` when configuring the plugin, see [Enable Scoped down Tokens](#Use-scoped-down-tokens) + Create a role (scope for artifactory >= 7.21.1) ```sh diff --git a/path_config.go b/path_config.go index 9530723..d76c7c3 100644 --- a/path_config.go +++ b/path_config.go @@ -40,7 +40,7 @@ func (b *backend) pathConfig() *framework.Path { Default: false, Description: "Optional. Bypass certification verification for TLS connection with Artifactory. Default to `false`.", }, - "allow_scoped_tokens": { + "allow_scope_override": { Type: framework.TypeBool, Default: false, Description: "Optional. Determine if scoped tokens should be allowed. This is an advanced configuration option. Default to `false`.", @@ -77,7 +77,7 @@ usernames if a static one is not provided. An optional "bypass_artifactory_tls_verification" parameter will enable bypassing the TLS connection verification with Artifactory. -An optional "allow_scoped_tokens" parameter will enable issuing scoped tokens with Artifactory. This is an advanced option that must +An optional "allow_scope_override" parameter will enable issuing scoped tokens with Artifactory. This is an advanced option that must have more sophisticated Vault policies. Please see README for an example. No renewals or new tokens will be issued if the backend configuration (config/admin) is deleted. @@ -89,7 +89,7 @@ type adminConfiguration struct { baseConfiguration UsernameTemplate string `json:"username_template,omitempty"` BypassArtifactoryTLSVerification bool `json:"bypass_artifactory_tls_verification,omitempty"` - AllowScopedTokens bool `json:"allow_scoped_tokens,omitempty"` + AllowScopeOverride bool `json:"allow_scope_override,omitempty"` } // fetchAdminConfiguration will return nil,nil if there's no configuration @@ -152,8 +152,8 @@ func (b *backend) pathConfigUpdate(ctx context.Context, req *logical.Request, da config.BypassArtifactoryTLSVerification = val.(bool) } - if val, ok := data.GetOk("allow_scoped_tokens"); ok { - config.AllowScopedTokens = val.(bool) + if val, ok := data.GetOk("allow_scope_override"); ok { + config.AllowScopeOverride = val.(bool) } if config.AccessToken == "" { @@ -232,7 +232,7 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, _ *f "version": b.version, "use_expiring_tokens": config.UseExpiringTokens, "bypass_artifactory_tls_verification": config.BypassArtifactoryTLSVerification, - "allow_scoped_tokens" : config.AllowScopedTokens, + "allow_scope_override" : config.AllowScopeOverride, } // Optionally include username_template diff --git a/path_config_test.go b/path_config_test.go index 77250f4..5a32079 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -25,7 +25,7 @@ func TestAcceptanceBackend_PathConfig(t *testing.T) { t.Run("read", accTestEnv.ReadPathConfig) t.Run("expiringTokens", accTestEnv.PathConfigUpdateExpiringTokens) t.Run("bypassArtifactoryTLSVerification", accTestEnv.PathConfigUpdateBypassArtifactoryTLSVerification) - t.Run("allowScopedTokens", accTestEnv.PathConfigUpdateAllowScopedTokens) + t.Run("allowScopedTokens", accTestEnv.PathConfigUpdateAllowScopeOverride) t.Run("usernameTemplate", accTestEnv.PathConfigUpdateUsernameTemplate) t.Run("delete", accTestEnv.DeletePathConfig) t.Run("errors", accTestEnv.PathConfigUpdateErrors) @@ -46,8 +46,8 @@ func (e *accTestEnv) PathConfigUpdateBypassArtifactoryTLSVerification(t *testing e.pathConfigUpdateBooleanField(t, "bypass_artifactory_tls_verification") } -func (e *accTestEnv) PathConfigUpdateAllowScopedTokens(t *testing.T) { - e.pathConfigUpdateBooleanField(t, "allow_scoped_tokens") +func (e *accTestEnv) PathConfigUpdateAllowScopeOverride(t *testing.T) { + e.pathConfigUpdateBooleanField(t, "allow_scope_override") } func (e *accTestEnv) pathConfigUpdateBooleanField(t *testing.T, fieldName string) { diff --git a/path_token_create.go b/path_token_create.go index 9109b61..1cb5469 100644 --- a/path_token_create.go +++ b/path_token_create.go @@ -2,6 +2,8 @@ package artifactory import ( "context" + "errors" + "regexp" "time" "github.com/hashicorp/vault/sdk/framework" @@ -139,10 +141,15 @@ func (b *backend) pathTokenCreatePerform(ctx context.Context, req *logical.Reque role.ExpiresIn = maxLeaseTTL } - if config.AllowScopedTokens { + if config.AllowScopeOverride { scope := data.Get("scope").(string) if len(scope) != 0 { - match, _ := regexp.MatchString(`^applied-permissions/groups:.+$`, scope) + re, err := regexp.Compile(`^applied-permissions\/groups:.+$`) + if err != nil { + return nil, err + } + match := re.MatchString(scope) + if !match { return logical.ErrorResponse("provided scope is invalid"), errors.New("provided scope is invalid") } diff --git a/path_token_create_test.go b/path_token_create_test.go index b2aa9f6..d53c73b 100644 --- a/path_token_create_test.go +++ b/path_token_create_test.go @@ -19,7 +19,7 @@ func TestAcceptanceBackend_PathTokenCreate(t *testing.T) { t.Run("create admin role", accTestEnv.CreatePathAdminRole) t.Run("create token for role", accTestEnv.CreatePathToken) t.Run("create scoped down token for admin role", accTestEnv.CreatePathScopedDownToken) - t.Run("create scoped down token for admin role with bad token", accTestEnv.CreatePathScopedDownTokenBadScope) + t.Run("create scoped down token for admin role with bad scope", accTestEnv.CreatePathScopedDownTokenBadScope) t.Run("delete role", accTestEnv.DeletePathRole) t.Run("cleanup backend", accTestEnv.DeletePathConfig) } diff --git a/test_utils.go b/test_utils.go index d251cc1..baed864 100644 --- a/test_utils.go +++ b/test_utils.go @@ -2,6 +2,7 @@ package artifactory import ( "context" + "net/http" "os" "testing" @@ -119,7 +120,7 @@ func (e *accTestEnv) UpdatePathConfig(t *testing.T) { e.UpdateConfigAdmin(t, testData{ "access_token": e.AccessToken, "url": e.URL, - "allow_scoped_tokens": true, + "allow_scope_override": true, }) } @@ -326,7 +327,7 @@ func (e *accTestEnv) CreatePathScopedDownToken(t *testing.T) { Path: "token/admin-role", Storage: e.Storage, Data: map[string]interface{}{ - "Scope": "applied-permissions/group:readonly", + "scope": "applied-permissions/groups:test-group", }, })