Vault Secret Engine+ Terraform + Spring

I’ve stumbled across a problem with Spring and Vault Secret Engine. I do have a set database connection configured in vault, for which the lease can be generated succesfully via vault cli (vault read database/creds/my-role)

Now, I do have a spring service deployed via Terraform and a vault policy document:

data "vault_policy_document" "this" {
  rule {
    path         = "/database/creds/my-role"
    capabilities = ["read"]
    description  = "Allow service to write and read into/from it's vault database path"
  }
}

The service logs:

{"type":"application","service":"identcheck-service","timestamp":"2022-02-17 11:54:29,822","level":"WARN","message":"[RequestedSecret [path='database/creds/my-role', mode=RENEW]] Lease  │
│ [leaseId='database/creds/my-role/ej2iuCzhcO8gToGjX3mFeOgg', leaseDuration=PT5M, renewable=true] Status 403 Forbidden: 1 error occurred:\n\t* permission denied\n\n; nested exception is o │
│ rg.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden: \"{\"errors\":[\"1 error occurred:\\n\\t* permission denied\\n\\n\"]}<EOL>\"","thread":"main","process_id":"8","logger":"org.s │
│ pringframework.vault.core.lease.SecretLeaseEventPublisher$LoggingErrorListener","class":"org.springframework.boot.logging.DeferredLog","method":"logTo"}

If, however, I change the path to “*” and grant all capabilities it works just fine.I’d really like to reduce the capabilities for the service to the specific db path.

Any ideas what could cause this?

Thanks in advance

Not sure if this would contribute but when I create policies I never use a leading “/” in the path.

Try using path = "database/creds/my-role" instead and see if that helps out.

already tried that, same result unfortunately :confused:

Looking at the error further it looks like an existing lease is attempting to be renewed. This permissions should be granted in the default policy (sys/leases/renew with update capability). You’re not setting the token_no_default_policy setting to disable the default policy from being applied, correct?

Yeah it’s not set. So what you’re saying, just to be clear, is that I should add the update cap to the default policy (sys/leases/renew)?

I would verify that the role used in your spring app is inheriting the default policy and also verify the default policy has the update capability on the sys/leases/renew path within the default policy.

For reference, here’s the full default policy from a dev instance I just started (v1.9.3)

# Allow tokens to look up their own properties
path "auth/token/lookup-self" {
    capabilities = ["read"]
}

# Allow tokens to renew themselves
path "auth/token/renew-self" {
    capabilities = ["update"]
}

# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own capabilities on a path
path "sys/capabilities-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own entity by id or name
path "identity/entity/id/{{identity.entity.id}}" {
  capabilities = ["read"]
}
path "identity/entity/name/{{identity.entity.name}}" {
  capabilities = ["read"]
}


# Allow a token to look up its resultant ACL from all policies. This is useful
# for UIs. It is an internal path because the format may change at any time
# based on how the internal ACL features and capabilities change.
path "sys/internal/ui/resultant-acl" {
    capabilities = ["read"]
}

# Allow a token to renew a lease via lease_id in the request body; old path for
# old clients, new path for newer
path "sys/renew" {
    capabilities = ["update"]
}
path "sys/leases/renew" {
    capabilities = ["update"]
}

# Allow looking up lease properties. This requires knowing the lease ID ahead
# of time and does not divulge any sensitive information.
path "sys/leases/lookup" {
    capabilities = ["update"]
}

# Allow a token to manage its own cubbyhole
path "cubbyhole/*" {
    capabilities = ["create", "read", "update", "delete", "list"]
}

# Allow a token to wrap arbitrary values in a response-wrapping token
path "sys/wrapping/wrap" {
    capabilities = ["update"]
}

# Allow a token to look up the creation time and TTL of a given
# response-wrapping token
path "sys/wrapping/lookup" {
    capabilities = ["update"]
}

# Allow a token to unwrap a response-wrapping token. This is a convenience to
# avoid client token swapping since this is also part of the response wrapping
# policy.
path "sys/wrapping/unwrap" {
    capabilities = ["update"]
}

# Allow general purpose tools
path "sys/tools/hash" {
    capabilities = ["update"]
}
path "sys/tools/hash/*" {
    capabilities = ["update"]
}

# Allow checking the status of a Control Group request if the user has the
# accessor
path "sys/control-group/request" {
    capabilities = ["update"]
}

# Allow a token to make requests to the Authorization Endpoint for OIDC providers.
path "identity/oidc/provider/+/authorize" {
	capabilities = ["read", "update"]
}

I’m using the 1.9.2 image with the following policy store:

seems like it’s the same as yours

My next course of action would be to inspect the audit logs to see which API endpoint is resulting in the permission denied message. The message should include details about the request path and the entity attempting to interact and how.

I think you’ll need an audit device configured for this, but you can inspect locally on your Vault leader (via journalctl or similar) or if you have a log aggregation tool, like Kibana, you can query through there.

sorry for the late response. I do have vault deployed in kubernetes so I can just fetch the logs. Weirdly enough it doesn’t log anything regarding the error.