Using Painless stored script in role mapping

The Kibana interface suggests that it should be possible to use a painless stored script to perform role mapping:

I would like to use this feature to put logic around which roles a given SSO realm is allowed to assign (so the examplemoustache template {{#json}}groups{{/json}}, which is used by default for elastic cloud logon, would be fine except i want to have a whitelist and prefix-based filter of the groups).

I have not been able to find any documentation about this feature, however. Does anyone know (a) which parameters are available in a painless script in the role mapping context and (b) what is the script supposed to return in this context?

Painless scripts are not officially supported (not all stored scripts are painless - you can have stored mustache scripts as well).

It can be made to work with painless, but there's no docs because it's not officially supported.

Thanks Tim. Yes, the Kibana interface does say "painless or mustache script", but mustache has very limited "scriptability" - they pride themselves on being "logic-less templates". But I will accept that Painless is not officially supported for this purpose and so avoid it.

An alternative approach to this is to perform prefixing on the roles based on the realm using a Mustache template like [{{#groups}}"myprefix-{{.}}",{{/groups}}]. Unfortunately, it only almost works, because this isn't valid JSON: There's a trailing comma. Perhaps that's for another topic, but do you know of a way to handle this with Mustache in Elasticsearch?

Can you provide a concrete example of what you're trying to do?

Mustache is limited, but I'm happy to try and help if you can give me an example of what you need.

(Also, you really shouldn't be modifying the elastic-cloud-sso-kibana-do-not-change mapping)

Right, the screenshot only shows the default role mapping because I was exploring the UI, i have no intention of editing it :slight_smile:

We want to integrate an id-provider from a third party. We actually have quite a lot of different roles for users from that third party, and we're fine (even thankful) to let them decide for themselves who should have which roles, so that we don't have to manage it for them. So far, a role mapping similar to that used in the default mapping ({{#tojson}}groups{{/tojson}}) might be fine. However, we don't want to let the third party set any group. For example, superuser would be out of bounds. So we're just trying to "role-namespace" their realm somehow. It could be by a whitelist or by simply prefixing roles we get from them.

Edit: The workaround is to create an equal number of role mappings that we have roles for them, using the "standard" role mapping method. I have considered that, and we will do it as a fallback. I just thought it would be simpler to have a single role mapping that fulfilled this requirement instead of a lot of them.

I think that claim_patterns are the closest solution to getting "anything but with whitelist/prefix" right now, it's just impractical that any hypothetical changes would require a reboot.

For example,

xpack.security.authc.realms.oidc.oidc1:
  order: 2
  rp: {...}
  op: {...}
  claims:
    groups: groups
  claim_patterns:
    groups: "^(group1|group2|group3|prefix-.*)$"

Haven't tested it yet though, but I assume it should work.

I'll think about other ways to solve this, but I think you could probably get what want by making the rules reject mappings for groups that fall outside of your pattern.

This assumes you can tell your 3rd party IdP that "group" claims must being with a prefix that matches their organization. That is you force the prefixing to happen on the id provider side, not in the role mapping.
I don't know whether that's true for your case.

If it is, then you can do something that says:

  • For the given realm
  • Map the "group" names to "role" names
  • Unless there is a group that has an invalid pattern

That would be

{
  "role_templates": [
    {
      "template": { "source": "{{#tojson}}groups{{/tojson}}" }, 
      "format" : "json" 
    }
  ],
  "rules": {
    "all": [
      {
        "field" : { "realm.name" : "oidc-org1" } 
      },
      {
        "except" : {
          "field": { "groups": "/~(org1-.*)/" }
        }
      }
    ]
  },
  "enabled": true
}

The key bit is the except.

The idea here is that the rules will match if realm.name = "oidc-org1" except when groups matches "/~(org1-.*)/"

So, the mapping is skipped in cases where one or more of the groups match the regular expression ~(org1-.*)

That's a Lucene style regular expression that say "match anything except things that start with org1-

So, if there is a group that starts with something other than org1- then the role mapping will be skipped.

If you're willing to write and maintain a Java plugin then this can also be done with a custom realm, but that feels like overkill for what you're asking for.

Also, assuming you're on a paid license (not a trial) then can you raise a support ticket and ask them to submit an enhancement request to have a better solution for this (which might be to officially support painless scripts for role mappings) ?
You can ask the support engineer to talk to me if they want more details about what to put in the enhancement request.