conjur
conjur copied to clipboard
Flexible jwt authenticator
As a conjur admin, I want a more flexible jwt authenticator, so that I can use any metadata within a JWT as a host identity.
SUMMARY:
The proposed azure authenticator flow does the following: Client sends JWT to conjur and conjur will then fetch and parse specific attributes from the JWT and then validate the parsed information with the hosts annotations.
This process seems like it could be made more generic to support not just azure JWT but any JWT.
Here is a sample policy I made. I am creating an authn-jwt
authenticator that allows the ability to authenticate using subscription-id
, resource-group
and resource-name
. jsonPath is used to parse the attributes and then we can use regex to granularly obtain information within a json attribute.
GIVEN the policy
- !policy
id: conjur/authn-jwt/azure-resource-name
body:
- !webservice
annotations:
# provider uri will be defined in annotations
provider-uri: https://sts.azure.com/tenantID
# the jwt is json,
# so lets first parse with jsonPath
# and then if we need to be more granular use
# methods such as split or regex
subscription-id/parse: $.sub
# xms_mirid is a more complex attribute
# and we must use the split method to
# receive the needed resource group.
# And a regex method for more
# complex attributes.
resource-group/parse: $.xms_mirid
# splitting on parsed attribute
resource-group/parse/split: /
# fetching this index from the array of the split
resource-group/parse/index: 4
# lets say I want to use regex
resource-name/parse: $.xms_mirid
# regex should only supports 1 match group
resource-name/parse/regex: .*\/(.*)$
# only 1 match should be returned in this use case
resource-name/parse/index: 0
- !group apps
- !permit
role: !group apps
resource: !webservice
privilege: [ read, authenticate ]
- !host
id: cyberark/azure/example_vm
annotations:
subscription-id: 24d6c3d7-d3eb-4973-b899-974bf79f48a
resource-group: Conjur-ResourceGroup
resource-name: ExampleVM
- !grant
role: !group conjur/authn-oidc/azure-resource-group/apps
member: !host cyberark/azure/example_vm
# now when `!host app1` authenticates the `subscription-id`, `resource-group` and `resource-name` will be validated and a conjur session token will be returned.
WHEN
The authn-jwt/azure-resource-name
is configured and enabled
THEN The following JWT should successfully authenticate to conjur
{
"aud": "https://management.azure.com/",
"iss": "https://sts.windows.net/df242c82.../",
"iat": 157,
"nbf": 157,
"exp": 157,
"aio": "42Vg...",
"appid": "7230cc60...",
"appidacr": "2",
"idp": "https://sts.windows.net/df242c82...",
"oid": "14751f4a...",
"sub": "24d6c3d7-d3eb-4973-b899-974bf79f48a",
"tid": "df242c82...",
"uti": "g1mKQ0DE...",
"ver": "1.0",
"xms_mirid": "/subscriptions/24d6c3d7-d3eb-4973-b899-974bf79f48a/resourceGroups/Conjur-ResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/ExampleVM"
}
I still think having default authenticators for each cloud provider such as iam
, azure
and gcp
with jwt pre configured to the highest security standard is best. This solution would give us the ability to authenticate JWTs from any vendor.
On the conjur core side I think creating a jwt_validator
class would be beneficial. An example of implementation below:
subscription_id_attr = {
"annotation": "subscription-id",
"json_path": "$.sub"
}
resource_group_attr = {
"annotation": "resource-group",
"json_path": "$.xms_mirid",
"regex": "/",
"index": 4
}
resource_name_attr = {
"annotation": "resource-name",
"json_path": "$.xms_mirid",
"split": ".*\/(.*)$",
"index": 0
}
expected_attributes = [
subscription_id_attr,
resource_group_attr,
resource_name_attr
]
validated = jwt_validator(jwt, host_annotations, expected_attributes)
raise Err::Jwt_Validation_Error if !validated
@AndrewCopeland we have discussed this today in the design review of Azure authenticator: https://github.com/cyberark/conjur/pull/1288 The scope you are describing here is Azure specific I would suggest an even more generic approach for an JWT authenticator. a mechanism where you can specify a field in the JWT by which you would like to authenticate and its value. in Azure this field can be subscription id but in another identity provider it could be "myIdentity". WDYT?
This example is azure specific but it can be applied to any JWT.
What I am proposing is to parse the JWT using jsonPath and further parsing should be done with actions like regex
or split
all defined within the authenticators !webservice
annotations.
Having a JWT authenticator would allow me to remove secret zero for many DevOps products that already have a certificate and private key. We could integrate with these products, generate a JWT (from the present private key) which can then be used to authenticate to conjur to then fetch secrets. This removes the need to pass around API keys to these devops tools.
👍👍
@AndrewCopeland I do think JWT authn is the right way to go but I would dream of a way to "inherit" a basic jwt authn in a way that support authenticators that can be built on top of it that speak a native language of the target. I'll give a HL example Given a simple jwt autenticator I as a developer can contribute easily an Azure authenticator so that the hosts will have reasonable host annotations.
for example authn-azure inherits authn-jwt according to these configurations [{ "annotation": "resource-group", "json_path": "$.xms_mirid", "regex": "/", "index": 4 }, { "annotation": "resource-name", "json_path": "$.xms_mirid", "split": "./(.)$", "index": 0 }] WDYT?
Where would
[{
"annotation": "resource-group",
"json_path": "$.xms_mirid",
"regex": "/",
"index": 4
},
{
"annotation": "resource-name",
"json_path": "$.xms_mirid",
"split": "./(.)$",
"index": 0
}]
Be applied? All of this information should be configured on the policy itself and not implement within ruby code. Conjur admins typically are not well versed in ruby meaning that configuration should be applied on the policy level.
Thanks, Andrew