Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡️ filtered resources: microsoft.users & microsoft.roles #5168

Merged
merged 1 commit into from
Feb 7, 2025

Conversation

afiune
Copy link
Contributor

@afiune afiune commented Feb 5, 2025

When writing policies that require fetching huge amount of data only to search or filter for specific resources, we fetch all resources and then we apply the filters using the builtin functions where() any() and more.

An example of a policy check that uses these patterns is:

// search for emergency accounts
microsoft.users.any(displayName == /emergency/)

// emrgIds holds the ids which match the above criteria
emrgID = microsoft.users.where(displayName == /emergency/).map(id)

// check if at least one of the accounts identified as such is attached to the "Global Administrator" role
microsoft.rolemanagement.roleDefinitions.where(displayName == "Global Administrator").all(assignments.any(principalId == emrgID))

To improve these resources, I am proposing a new pattern, similar to the one used at #5156, but with the difference that it doesn't override builtin functions, instead it leverages list resources which are natively supported in MQL with additional query parameters filter and search.

These query parameters will be used directly when executing API requests against Microsoft Graph API. These query parameters are documented at:

https://learn.microsoft.com/en-us/graph/filter-query-parameter?tabs=http#filter-using-lambda-operators

The above example can be rewritten using these two new filtered resources like:

// search for emergency accounts
microsoft.users(search: "displayName:emergency").any()

// emrgIds holds the ids which match the above criteria
emrgID = microsoft.users(search: "displayName:emergency").map(id)

// check if at least one of the accounts identified as such is attached to the "Global Administrator" role
microsoft.roles(filter: "displayName eq 'Global Administrator'").all(assignments.any(principalId == emrgID))

Additionally, since these query parameters are directly passed to Microsoft API's, we can write very complex filters for these two new resources.

A couple examples are:

microsoft.roles(filter: "isBuiltIn eq true and startswith(displayName, 'Global')")
microsoft.users(filter: "accountEnabled eq true AND userType eq 'Member'", search: "officeLocation:berlin")

Closes #5110

When writing policies that require fetching huge amount of data only to
search or filter for specific resources, we fetch all resources and then
we apply the filters using the builtin functions `where()` `any()` and
more.

An example of a policy check that uses these patterns is:
```
// search for emergency accounts
microsoft.users.any(displayName == /emergency/)

// emrgIds holds the ids which match the above criteria
emrgID = microsoft.users.where(displayName == /emergency/).map(id)

// check if at least one of the accounts identified as such is attached to the "Global Administrator" role
microsoft.rolemanagement.roleDefinitions.where(displayName == "Global Administrator").all(assignments.any(principalId == emrgID))
```

To improve these resources, I am proposing a new pattern, similar to the
one used at #5156, but with the
difference that it doesn't override builtin functions, instead it
leverages list resources which are natively supported in MQL with
additional query parameters `filter` and `search`.

These query parameters will be used directly when executing API requests
against Microsoft Graph API. These query parameters are documented at:

https://learn.microsoft.com/en-us/graph/filter-query-parameter?tabs=http#filter-using-lambda-operators

The above example can be rewritten using these two new filtered
resources like:

```
// search for emergency accounts
microsoft.users(search: "displayName:emergency").any()

// emrgIds holds the ids which match the above criteria
emrgID = microsoft.users(search: "displayName:emergency").map(id)

// check if at least one of the accounts identified as such is attached to the "Global Administrator" role
microsoft.roles(filter: "displayName eq 'Global Administrator'").all(assignments.any(principalId == emrgID))
```

Additionally, since these query parameters are directly passed to
Microsoft API's, we can write very complex filters for these two new
resources.

A couple examples are:
```
microsoft.roles(filter: "isBuiltIn eq true and startswith(displayName, 'Global')")
microsoft.users(filter: "accountEnabled eq true AND userType eq 'Member'", search: "officeLocation:berlin")
```

Closes #5110

Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
@afiune afiune requested review from chris-rock and arlimus February 5, 2025 11:50
Copy link
Contributor

github-actions bot commented Feb 5, 2025

Test Results

3 212 tests  ±0   3 208 ✅ ±0   1m 52s ⏱️ +12s
  385 suites ±0       4 💤 ±0 
   29 files   ±0       0 ❌ ±0 

Results for commit f6dc25c. ± Comparison against base commit 3799ad0.

@mm-weber
Copy link
Contributor

mm-weber commented Feb 7, 2025

I like it @afiune , for the flexibility and performance it has.

One thing was a bit odd for me:

cnspec> microsoft.users(search: "displayName:emergency").any()
[failed] microsoft.users.any()
  actual:   []

cnspec> microsoft.users(search: "displayName:emergency")
microsoft.users.list: []
cnspec> [] == empty
[ok] value: []

cnspec> microsoft.users(search: "displayName:emergency")
microsoft.users.list: []
cnspec> microsoft.users(search: "displayName:emergency") != empty
[ok] value: microsoft.users id = microsoft.users/search-displayName:emergency

It does not return false when the results are an empty list and you compare it to != empty

@afiune
Copy link
Contributor Author

afiune commented Feb 7, 2025

@mm-weber good observation, since this is a "list resource" you still need to use the any() or none() built in functions.

For example, the os provider has a similar "list resource" called ports:

https://github.com/mondoohq/cnquery/blob/main/providers/os/resources/os.lr#L1007-L1012

If I try what you're trying to do, I get:

cnquery> ports != empty
failed to compile: cannot find operator handler: any != ref

But I can do any and none:

cnquery> ports.any()
[ok] value: true
cnquery> ports.none()
[failed] ports.none()
  actual:   [
    0: port port=51134 address="192.168.86.36" protocol="tcp4" process.executable="Brave"
    ...
    112: port port=51284 address="192.168.86.36" protocol="tcp4" process.executable="cnquery"
  ]

You can do the comparison your doing if you access the implicit list param:

cnquery> ports.list != empty
[ok] value: [
  0: port id = port: udp4/*:0/*:0/unknown
  ...
  112: port id = port: tcp4/127.0.0.1:5433/:0/listen
]

@afiune afiune merged commit f248d9a into main Feb 7, 2025
17 checks passed
@afiune afiune deleted the afiune/slow-microsoft-queries branch February 7, 2025 21:16
@github-actions github-actions bot locked and limited conversation to collaborators Feb 7, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

improve slow microsoft query
3 participants