in the example above, it would start with actions:...
# help
j
in the example above, it would start with actions: ['*'], then actions['view'], etc. and cerbos stops evaluation the moment a rule match is encountered.
so i can do this:
Copy code
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "1"
  importDerivedRoles:
    - hm_employee
  resource: "businessassets"
  rules:
    - actions: ['read']
      effect: EFFECT_ALLOW
      derivedRoles:
        - internal_user
    - actions: ['read']
      effect: EFFECT_ALLOW
      condition:
        match:
          expr: "read:businessassets" in request.aux_data.jwt.scope.split(" ")
d
🤔 Yes, but in my thinking, it is an implementation detail. Does the order matter in your case?
If the evaluation starts backwards, the outcome will be the same, won’t it?
j
yes, that's an implementation detail.
ok!
so i should code my policies in such a way that each action should be unique.....
to avoid ambiguity
wait a minute...in the jwt example, isn't the jwt submitted as a bas64 encoded value....then wouldn't request.aux_data.jwt need to be decoded by cerbos?
Copy code
curl -POST -H "Content-type: application/json" -d '{
  "requestId":  "1",
  "actions":  ["read"],
  "resource":  {
    "policyVersion": "1",
    "kind":  "businessassets",
    "instances": {
      "prod": {}
    }
  },
  "principal":  {
    "id":  "user01",
    "policyVersion": "1",
    "roles":  ["valid_user"]
  },
  "includeMeta": true,
  "aux_Data": { 
    "jwt": {
        "token": "myjwt....redacted"
	}
  }
}' 'theurl'
if i submit the above
d
I’d say a combination of actions and conditions. Tagging @Charith (Cerbos) to comment
j
and i have a resource policy like this:
Copy code
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "1"
  importDerivedRoles:
    - hm_employee
  resource: "businessassets"
  rules:
    - actions: ['read']
      effect: EFFECT_ALLOW
      derivedRoles:
        - internal_user
    - actions: ['read']
      effect: EFFECT_ALLOW
      condition:
        match:
          expr: "read:businessassets" in request.aux_data.jwt.scope.split(" ")
i should get an EFFECT_ALLOW from cerbos?
d
Depends on JWT content. You check either
scope
or
internal_user
claim
j
yeah correct
but if internal_user claim is missing and scope contains "read:businessassets" then it should come back with EFFECT_ALLOW
d
yes
j
hmmm
d
I don’t see a role for the second rule
j
Copy code
{
  "requestId": "1",
  "resourceInstances": {
    "prod": {
      "actions": {
        "read": "EFFECT_DENY"
      }
    }
  },
  "meta": {
    "resourceInstances": {
      "prod": {
        "actions": {
          "read": {
            "matchedPolicy": "NO_MATCH"
          }
        },
        "effectiveDerivedRoles": []
      }
    }
  }
}
well i have 2 types of users
1. internal user. this is tagged with is_internal_user claim in the JWT set to TRUE. 2. external user. this has is_internal_user set to FALSE or is blank, but has scope with "read:businessassets" in it
so what i want to achieve is when the request to the businessassets API is called and a JWT token is presented, Cerbos will get EFFECT_ALLOW to both #1 and #2
d
Sorry, I meant this rule:
Copy code
- actions: ['read']
      effect: EFFECT_ALLOW
      condition:
        match:
          expr: "read:businessassets" in request.aux_data.jwt.scope.split(" ")
should also refer to a role, albeit made up.
j
ooooooooooh
d
Please add
roles: ["valid_user"]
to it
j
ok
question: in aux_data, do i pass the base64 DECODED JWT or the base64 encoded JWT?
it looks like it should be the former.
d
base64 encoded JWT
j
ok
and i can name the key in auxdata or aux_data?
in the json payload i mean.
in order for cerbos to accept it as a base-64 encoded jwt, i have to pass it in the json as auxData.jwt ?
if i pass it as auxData.mytoken - it would not work?
d
Sorry, let me double check this
j
sorry, dennis. so many questions.
it just looks like aux_data is a generic way to pass any data to cerbos. so i'm just wondering how cerbos will know when it's a jwt.
d
Well, no JWT parts are already encoded, so you don’t need to encode JWT again.
j
when i get the jwt from my idp, it appears like this: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllkZFpJYVJ5a3dpcjYwMmp0bFNvRCJ9.eyJodHRwczo................."
so i just pass that to cerbos as is?
d
yes
j
as part of the json payload to /api/check like this?
👍 1
what happens if i do this?
then this would need to change right?
?
d
Cerbos will complain about invalid request, I guess
j
oh
d
aux_data.jwt
is a required part
then you can have anything
j
{"code":3,"message":"failed to extract auxData","details":[]
d
aux_data.jwt.mywonderfultoken.tokenvalue
j
i put this in my json payload
so should it be "aux_data" or "auxData" ?
d
no, I was wrong.
The JSON section above looks good to me
but it does not work, does it?
j
doesn't work
d
Can you check you token with https://jwt.io/
j
getting this {"code":3,"message":"failed to extract auxData","details":[]}
i know myu token is valid. i did check with jwt.io.
👍 1
so just to summarize again...to make sure we're on the same page
resourcepolicy has this
d
oops
j
my json payload has this:
looks like it's a match...but i'm still getting that error from /api/check
d
shouldn’t it be
request.auxData.jwt.token.scope.split(" ")
j
holy ....
d
token part is missing in your policy
j
ok so i've changed the policy to.
still getting {"code":3,"message":"failed to extract auxData","details":[]}
d
Copy code
- expr: '"cerbos-jwt-tests" in request.aux_data.jwt.aud'
              - expr: '"A" in request.aux_data.jwt.customArray'
from our example
j
hmmm so maybe i should change it to "aux_data" instead of "auxData" in the policy?
d
looks like it is
aux_data
and no
token
in the policy expression
j
ok
d
hmmm so maybe i should change it to “aux_data” instead of “auxData” in the policy?
In the policy, but not in the request
j
ok so policyi s this now:
still the same response
this is the json payload, yes?
i tried in the json
and it works now. but i'm geetting a EFFECT_DENY
Copy code
{
  "requestId": "1",
  "resourceInstances": {
    "prod": {
      "actions": {
        "read": "EFFECT_DENY"
      }
    }
  },
  "meta": {
    "resourceInstances": {
      "prod": {
        "actions": {
          "read": {
            "matchedPolicy": "NO_MATCH"
          }
        },
        "effectiveDerivedRoles": []
      }
    }
  }
}
it's a bit confusing that in the policy it has to be aux_data, while in the json payload it is auxdata
ok the error about unable to extract auxdata is actually due to the payload. cerbos is expecting "auxdata".
in the payload.
d
not “auxData”?
j
oh wow
so i submitted json payload with auxData and auxdata
auxdata is accepted
auxData is rejected but the error message that comes back says it cannot extract auxData, which is misleading
d
auxdata might be silently ignored. Give me 5-10 minutes, I will check this locally.
j
ok
my response was confusing
with "auxData" in payload, i see this: {"code":3,"message":"failed to extract auxData","details":[]}
with "auxdata" in the payload, it is accepted
so the error returned by cerbos should really say "failed to extract auxData; was expecting auxdata"
d
I think it silently ignores
auxdata
and fails to load
auxData
for a different reason
It should be
auxData
Have you disabled JWT verification in the Cerbos config?
j
wait let me check on this
d
Copy code
auxData:
  jwt:
    disableVerification: true
j
isn't wasnt se
i have just set it
do i need to restart the pods?
d
yes
j
ok
ouch.
message has been deleted
this is going to take some time to fix
i'll pick this up later. jumping into a call now
d
kk
Double checked the following works as expected: Request:
Copy code
{
  "requestId": "test",
  "actions": [
    "defer"
  ],
  "principal": {
    "id": "john",
    "policyVersion": "20210210",
    "roles": [
      "employee"
    ],
    "attr": {
      "department": "marketing",
      "geography": "GB",
      "team": "design"
    }
  },
  "resource": {
    "kind": "leave_request",
    "policyVersion": "20210210",
    "instances": {
      "XX125": {
        "attr": {
          "department": "marketing",
          "geography": "GB",
          "id": "XX125",
          "owner": "john",
          "team": "design"
        }
      }
    }
  },
  "auxData": {
    "jwt": {
      "token": "eyJhbGciOiJFUzM4NCIsImtpZCI6IjE5TGZaYXRFZGc4M1lOYzVyMjNndU1KcXJuND0iLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsiY2VyYm9zLWp3dC10ZXN0cyJdLCJjdXN0b21BcnJheSI6WyJBIiwiQiIsIkMiXSwiY3VzdG9tSW50Ijo0MiwiY3VzdG9tTWFwIjp7IkEiOiJBQSIsIkIiOiJCQiIsIkMiOiJDQyJ9LCJjdXN0b21TdHJpbmciOiJmb29iYXIiLCJleHAiOjE5NDk5MzQwMzksImlzcyI6ImNlcmJvcy10ZXN0LXN1aXRlIn0.WN_tOScSpd_EI-P5EI1YlagxEgExSfBjAtcrgcF6lyWj1lGpR_GKx9goZEp2p_t5AVWXN_bjz_sMUmJdJa4cVd55Qm1miR-FKu6oNRHnSEWdMFmnArwPw-YDJWfylLFX"
    }
  }
}
Policy:
Copy code
- actions: ["defer"]
      effect: EFFECT_ALLOW
      roles: ["employee"]
      condition:
        match:
          all:
            of:
              - expr: '"cerbos-jwt-tests" in request.aux_data.jwt.aud'
              - expr: '"A" in request.aux_data.jwt.customArray'
j
eh the expr needs to be enclosed in single quotes? ok
d
These are YAML rules. Normally quotes aren’t needed, but since the beginning of the string is quoted in double quotes, the entire string requires single quotes.
j
is there something wrong with line 16?
the moment i add this resource policy into the google cloud storage bucket, i see this
Copy code
{
  "log.level": "error",
  "@timestamp": "2021-12-15T08:41:54.978Z",
  "log.logger": "cerbos.blob",
  "message": "Failed to check for updates",
  "bucket": "<gs://abac-policies>",
  "workDir": "/root/tmp/cerbos/work",
  "error": "failed to convert data to JSON: yaml: line 16: did not find expected key"
}
that error above is from the cerbos pod
c
I reckon it's because quotes are special characters in YAML. You have to use the multiline syntax like this:
Copy code
condition:
    match:
      expr: |-
        "read:businessassets" in request.auxdata.jwt.scope.split(" ")
j
{"log.level":"error","@timestamp":"2021-12-15T084954.978Z","log.logger":"cerbos.compiler","message":"Failed to recompile","id":{},"error":"1 compilation errors\nresourcePolicy businessassets.yaml Invalid expression in resource rule 'rule-002' (failed to compile
\"read:businessassets\" in request.auxdata.jwt.scope.split(\" \")
[undefined field 'auxdata'])"}
eh??
this is my entire policy
Copy code
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "1"
  importDerivedRoles:
    - special_roles
  resource: "businessassets"
  rules:
    - actions: ['read']
      effect: EFFECT_ALLOW
      derivedRoles:
        - hm_employee
    - actions: ['read']
      effect: EFFECT_ALLOW
      roles: ["valid_user"]
      condition:
        match:
          expr: |-
            "read:businessassets" in request.auxdata.jwt.scope.split(" ")
c
Sorry, typo. It should be
auxData
j
oh !
but in the payload it can be
auxdata
Copy code
{
  "log.level": "error",
  "@timestamp": "2021-12-15T08:52:04.991Z",
  "log.logger": "cerbos.compiler",
  "message": "Failed to recompile",
  "id": {},
  "error": "1 compilation errors:\nresourcePolicy_businessassets.yaml: Invalid expression in resource rule 'rule-002' (failed to compile `\"read:businessassets\" in request.auxData.jwt.scope.split(\" \")` [undefined field 'auxData'])"
}
by the way - one feedback; freshly created cerbos pod fails to start (crashloopbackoff) if there is an invalid YAML policy in the bucket storage. but if cerbos pod is already running, and i insert an invalid YAML policy, pod doesn't crash (it just spits out errors about that policy). would be better to make freshly created cerbos pod also startup THEN spit out errors about invalid policy files (rather than outright crashing) - and pod continues to stay alive.
c
We expect users to have run the
compile
command and verified the policy repo before starting the server. It's better to warn about an invalid repo on startup and let users fix it immediately.
j
ah, i have it installed in a k8s cluster.
so i should download the cerbos binary and run "cerbos compile <dir>" first as a best practice?
and only if there are no errors do i start up the pods?
c
Yeah, it's the GitOps workflow. Before you publish your polciies to your repo, you should verify that they are valid
j
ok thank you!
c
You don't have to do it with an init container or anything. I am just talking about the process for publishing the policies to the repo
j
understood. i will definitely take that into consideration. i didn't know about the "compile" command
anyway still facing issues with the auxData / auxdata thingy....
c
Let me try it out locally
j
I only have these configured right now:
Copy code
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "1"
  importDerivedRoles:
    - special_roles
  resource: "businessassets"
  rules:
    - name: allow_readbusinessassets_is_employee
      actions: ['read']
      effect: EFFECT_ALLOW
      derivedRoles:
        - hm_employee
    - name: allow_readbusinessassets_from_scope
      actions: ['read']
      effect: EFFECT_ALLOW
      roles: ["valid_user"]
      condition:
        match:
          expr: |-
            "read:businessassets" in request.auxData.jwt.scope.split(" ")
and
Copy code
apiVersion: "api.cerbos.dev/v1"
description: "Dynamic role to determine if this is a HM employee."
derivedRoles:
  name: special_roles
  definitions:
    - name: hm_employee
      parentRoles: ["valid_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.is_hm_employee == "TRUE"
my goal is this: 1. If is_hm_employee exists as a claim in JWT and is set to TRUE then user has access to resource of kind "businessassets"; else 2. If JWT's scope claim contains "read:businessassets" then user has access to resource of kind "businessassets"
c
About auxData, I was wrong. It should be
aux_data
. https://docs.cerbos.dev/cerbos/0.9.1/policies/conditions.html#auxdata
d
IMO it is
auxData
in the HTTP request and aux_data in the policy.
c
I know it's not very intuitive. It's a quirk in how CEL expressions get access to the aux data block.
I'll look to see if there's a way to make them consistent
e
aux_data works. Thanks @Charith (Cerbos). Jesum, to quickly test things, you can also use the playground. For simplicity I created an instance with your current two policies. You can save it from here. https://play.cerbos.dev/p/zllyXK8fKp61b9b1dclVIRVLkNbiidda
j
aaaah ok. got it. so auxData in http request and aux_data in policy! and thank you @Emre (Cerbos) i will give it a try
when defining a derivedRole policy, is there a way I can use a catch all condition?
Copy code
apiVersion: "api.cerbos.dev/v1"
description: "Dynamic role to determine if this is a HM employee."
derivedRoles:
  name: platform_roles
  definitions:
    - name: hm_employee
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.is_hm_employee == "TRUE"
    - name: explore
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.hm_account_status == "Explore"
    - name: expand
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.hm_account_status == "Expand"
    - name: experience
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.hm_account_status == "Experience"
like if you look at the derivedrole policy above, i would like a catch-all condition that is like an "else" statement where the default role is "explore"
e
Re: above playground. I am not 100% sure if you can include teh auxData block, so let me check and get back to you on that one.
c
You could remove the
condition
block from explore. Then it will always be active for anyone with the role naked_user
🆗 1
j
so if a user has is_hm_employee = TRUE and hm_account_status = "Experience", then which derivedrole will that user get?
because....
c
They will get both
j
Copy code
apiVersion: "api.cerbos.dev/v1"
description: "Dynamic role to determine if this is a HM employee."
derivedRoles:
  name: platform_roles
  definitions:
    - name: hm_employee
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.is_hm_employee == "TRUE"
    - name: explore
      parentRoles: ["naked_user"]
    - name: expand
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.hm_account_status == "Expand"
    - name: experience
      parentRoles: ["naked_user"]
      condition: 
        match:
          expr: request.aux_data.jwt.hm_account_status == "Experience"
if i define the policy this way, wouldn't a user always end up being "explore"? because expand and experience would not be evaluated?
c
No, they are evaluated. A user can have multiple derived roles.
j
i see. so it will be an array in the response.
so if i want to get rid of ambiguity, i have be as explicit as possible with the conditions
c
Yes
j
Copy code
apiVersion: "api.cerbos.dev/v1"
description: "Dynamic role to determine if this is a HM employee."
derivedRoles:
  name: platform_roles
  definitions:
    - name: hm_employee
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.is_hm_employee == "TRUE"
              - expr: request.aux_data_jwt.hm_account_status != "Expand"
              - expr: request.aux_data_jwt.hm_account_status != "Explore"
              - expr: request.aux_data_jwt.hm_account_status != "Experience"
    - name: explore
      parentRoles: ["naked_user"]
    - name: expand
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.hm_account_status == "Expand"
              - expr: request.aux_data_jwt.is_hm_employee != "TRUE"
    - name: experience
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.hm_account_status == "Experience"
              - expr: request.aux_data_jwt.is_hm_employee != "TRUE"
i suspect there's a more efficient way to specify the condition for the "hm_employee" derived role
the last 3 expressions there...can i do something like
expr: !(request.aux_data_jwt.hm_account_status in ['Expand', 'Explore', 'Experience')
nevermind let me look up some CEL examples 🙂
c
That should work. (I haven't tried it but it looks OK to me). https://docs.cerbos.dev/cerbos/latest/policies/conditions.html#_lists_and_maps
j
i'll give it a try
ok so this is what i have now...and i'm making some progress....
Copy code
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "1"
  importDerivedRoles:
    - platform_roles
  resource: "businessassets"
  rules:
    - name: allow_readbusinessassets_is_employee
      actions: ['read']
      effect: EFFECT_ALLOW
      derivedRoles:
        - hm_employee
    - name: allow_readbusinessassets_from_scope
      actions: ['read']
      effect: EFFECT_ALLOW
      roles: ["naked_user"]
      condition:
        match:
          expr: |-
            "read:businessassets" in request.aux_data.jwt.scope.split(" ")
Copy code
apiVersion: "api.cerbos.dev/v1"
description: "Dynamic role to determine if this is a HM employee."
derivedRoles:
  name: platform_roles
  definitions:
    - name: hm_employee
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.is_hm_employee == "TRUE"
              #- expr: !(request.aux_data.jwt.hm_account_status in ['Expand', 'Explore', 'Experience'])
    - name: explore
      parentRoles: ["naked_user"]
      condition: 
        match:
          any:
            of:
              - expr: request.aux_data.jwt.hm_account_status == "Explore"
              - expr: request.aux_data.jwt.is_hm_employee != "TRUE"
    - name: expand
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.hm_account_status == "Expand"
              - expr: request.aux_data.jwt.is_hm_employee != "TRUE"
    - name: experience
      parentRoles: ["naked_user"]
      condition: 
        match:
          all:
            of:
              - expr: request.aux_data.jwt.hm_account_status == "Experience"
              - expr: request.aux_data.jwt.is_hm_employee != "TRUE"
the token has expired so it's ok to post it here. 🙂
Copy code
{
  "requestId": "1",
  "resourceInstances": {
    "prod": {
      "actions": {
        "read": "EFFECT_DENY"
      }
    }
  },
  "meta": {
    "resourceInstances": {
      "prod": {
        "actions": {
          "read": {
            "matchedPolicy": "resource.businessassets.v1"
          }
        },
        "effectiveDerivedRoles": []
      }
    }
  }
}
so there's a matched policy, which is good. but the result is EFFECT_DENY
d
auxdata
j
in the json payload?
oh
d
yes
j
holy sh**
you're right. i get effect_allow now
FINALLY!!!!!!!!!!
d
🎉
j
a small victory. .... bows in great reverence to the Cerbos gurus here
thank you everyone
d
you’re welcome, Jesum
I’ve just checked. Both
auxData
and
aux_data
works in HTTP request.
the latter works everywhere - in policies and HTTP request
j
yeah but
auxdata
doesn't work in HTTP request
so i'll just stick with aux_data everywhere
👍 1