Creating an Ory Kratos + Keto Auth Plugin (vault-auth-plugin-ory)

Creating an Ory Kratos + Keto Auth Plugin (vault-auth-plugin-ory)

Hello, I’ve just finished implementing a custom auth backend for Vault and thought I’d run a few things past the Hashicorp community.

The plugin uses the Ory Kratos and Keto APIs to authenticate users and authorise them to access secrets.

The plugin takes 4 fields:

  • kratos_session_cookie - Ory Kratos session cookie (including the cookie name)

  • namespace - Ory Keto namespace (in our case ‘workspace’)

  • object - UUID of a specific resource you want to access (in our case a workspace ID)

  • relation - The relation/role you wish to authenticate the user against said object (in our case ‘editor’)

The plugin then makes a request to Kratos to check if the user is authenticated by validating the session cookie. On success, we take the user ID from the session to use as the Keto subject, and use it along with the namespace, object, and relation to make a request to Keto to check if the user has the specified relation against the object.

If the user is authenticated and has the correct relation against the object, a token is created with a policy that follows the following format:

namespace_relation

So in our case it would be:

workspace_editor

The policy is then attached to the token and the user is authenticated. We also store the namespace, object, relation, and subject (user ID) in the alias metadata for access within the policy (though, we only really use object at the minute).

Now we have a system that can authenticate users against Ory Kratos and authorise them against an object with Ory Keto. We can use the alias metadata within a policy template to create policies that are specific to the relationship and object they are trying to access.

Here is an example of a policy that would allow a user to access a workspace’s secrets if they have the editor role (policy named workspace_editor):

path "secret/data/workspace/{{identity.entity.aliases.auth_vault-plugin-auth-ory_3a50a4f6.metadata.object}}*" {
    capabilities = ["create", "update", "read"]
}

This seems to work well, but I was wondering if there is a better way to achieve the same result, or if there are any security concerns I should be aware of.

One thing I was immediately concerned with is the fact that we pass in the relation tuple data (namespace, object, and relation). I thought there could be a risk of malicious requests being made against some arbitrary relation, but as Keto has to authorise the user against the relation tuple, I don’t think this is a concern. The plugin will return a 403 if the user is not authorised against the relation tuple.

I did consider using the Keto List call instead of a Check. This means we wouldn’t need to pass the namespace, object, or relation field into the plugin and can instead use the relations Keto provides for the user to create policies based on the relationships they have. However, I have yet to figure out a way to store the object IDs in the alias metadata in a way that we can use them in the policy template, as a user can have the same relation to another object in the same namespace (e.g. workspace:1234#editor@subject + workspace:5678#editor@user results in two workspace_editor policies, but the ID can’t be stored against the same object metadata but needs to be accessible within the policy template).

Another concern/question I have is around the policy templating and the use of the {{identity.entity.aliases.auth_vault-plugin-auth-ory_3a50a4f6.metadata.object}} field. I’m not sure if this is the best way to access the object ID, or if there is a better way to do this. Is there a way of accessing this metadata without having to know the auth plugin accessor that created the alias? (i.e. the auth_vault-plugin-auth-ory_3a50a4f6 part). I do give the alias a name, but I have yet to have any luck in accessing it any other way. At the minute, if we disable/re-enable the auth plugin, the policies need to be updated with the latest accessor. This could be generated automatically with the help of vault auth list but if there is a way to have a policy use a static name/accessor, that would be great.

Finally, I have temporarily disabled the config storage lookup, as accessing the logical.Storage within the request results in a TLS error. I suspect this is just my dev environment not playing ball with certs that I have setup for other projects.

error getting config from storage: error="rpc error: code = Unavailable desc = connection error: desc = \"transport: authentication handshake failed: x509: “localhost” certificate is not standards compliant\""

And to tack on one more question, could this system be improved using Vault roles/groups? I don’t want to maintain two copies of the relation data from Keto, and ideally don’t want to have to do any manual work other than writing policies, but if there is a way to leverage roles/groups and still have Keto be the source of truth, I would be interested in your thoughts!

Although not fully complete (hence this post), I open sourced the code for anyone to take a look! GitHub - comnoco/vault-plugin-auth-ory: A HashiCorp Vault plugin for Ory Kratos and Ory Keto.

Any help or advice would be greatly appreciated! :pray:

I just found this post that mentions some nuances in the metadata

When using {{identity.entity.aliases.auth_jwt_abcdef12.metadata.meta_key}} it returns the value from the first alias in the entity’s alias map, not the value from the alias that generated the token.

This should be considered when using a token for more than one set of Kratos + Keto requests. I think at the moment a token is only good for a single namespace + object.

Hello,

I got flagged since you referenced my post. Just FYI, in Vault 1.7.x this behaviour was changed, and so the first entity should be the only entity per auth method mount.

So if you’re on a recent version of Vault, this probably isn’t a consideration anymore.

Oh nice, thanks for the info!

1 Like