conjur icon indicating copy to clipboard operation
conjur copied to clipboard

Flexible jwt authenticator

Open AndrewCopeland opened this issue 5 years ago • 6 comments

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-groupand 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 avatar Feb 05 '20 17:02 AndrewCopeland

@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?

InbalZilberman avatar Feb 05 '20 21:02 InbalZilberman

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.

AndrewCopeland avatar Feb 06 '20 21:02 AndrewCopeland

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 avatar May 27 '20 16:05 AndrewCopeland

👍👍

saravanant23 avatar Oct 09 '20 17:10 saravanant23

@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?

InbalZilberman avatar Oct 14 '20 14:10 InbalZilberman

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

AndrewCopeland avatar Oct 26 '20 16:10 AndrewCopeland