Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: shared-operator-workflow
uses: redhat-cop/github-workflows-operators/.github/workflows/pr-operator.yml@v1.1.4
with:
GO_VERSION: ~1.21
GO_VERSION: ~1.25
RUN_UNIT_TESTS: true
RUN_INTEGRATION_TESTS: false
RUN_HELMCHART_TEST: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
with:
PR_ACTOR: "andy.block@gmail.com"
GO_VERSION: ~1.21
GO_VERSION: ~1.25
RUN_UNIT_TESTS: true
RUN_INTEGRATION_TESTS: false
RUN_HELMCHART_TEST: false
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest

## Tool Versions
KUSTOMIZE_VERSION ?= v3.8.7
CONTROLLER_TOOLS_VERSION ?= v0.10.0
CONTROLLER_TOOLS_VERSION ?= v0.20.1

.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Groups contained within Azure Active Directory can be synchronized into OpenShif
| `baseGroups` | List of groups to start searching from instead of listing all groups in the directory | | No |
| `credentialsSecret` | Name of the secret containing authentication details (See below) | | Yes |
| `filter` | Graph API filter | | No |
| `clientFilter` | CEL expression for client-side filtering of groups (See below) | | No |
| `groups` | List of groups to filter against | | No |
| `userNameAttributes` | Fields on a user record to use as the User Name | `userPrincipalName` | No |
| `prune` | Prune Whether to prune groups that are no longer in Azure | `false` | No |
Expand All @@ -99,6 +100,71 @@ spec:
namespace: group-sync-operator
```

#### Client-Side Filtering

The `clientFilter` field allows you to apply additional filtering logic using [CEL (Common Expression Language)](https://github.com/google/cel-spec) expressions after groups are retrieved from Azure. This is useful when the Azure Graph API filter capabilities are insufficient for your requirements.

The CEL expression evaluates against a `group` object whose fields are derived from the Azure Group SDK object via reflection. Absent fields default to zero values (`""` for strings, `false` for booleans, `0` for integers) so no nil guards are needed. The full list of available fields is logged at operator startup and included in any filter error message.

Commonly used fields:

* `displayName` - The group's display name
* `mailNickname` - The group's mail nickname
* `mail` - The group's email address
* `id` - The unique identifier
* `description` - The group description
* `securityEnabled` - Whether the group is security-enabled (`bool`)
* `mailEnabled` - Whether the group is mail-enabled (`bool`)
* `groupTypes` - Array of group type strings (e.g. `["Unified"]`)
* `createdDateTime` - When the group was created
* `expirationDateTime` - When the group expires
* `visibility` - The group's visibility (`Public`, `Private`, etc.)
* `classification` - The group's sensitivity classification
* `membershipRule` - The dynamic membership rule expression
* `isAssignableToRole` - Whether the group can be assigned to an Azure AD role (`bool`)
* `onPremisesSyncEnabled` - Whether the group is synced from on-premises (`bool`)
* `onPremisesDomainName` - The on-premises domain name
* `onPremisesSamAccountName` - The on-premises SAM account name
* `securityIdentifier` - The group's security identifier (SID)

**Example: Filter groups where mailNickname matches displayName**

This is useful to distinguish manually-created groups from auto-generated ones:

```yaml
apiVersion: redhatcop.redhat.io/v1alpha1
kind: GroupSync
metadata:
name: azure-groupsync
spec:
providers:
- name: azure
azure:
credentialsSecret:
name: azure-group-sync
namespace: group-sync-operator
filter: "startsWith(displayName,'amacp-')"
clientFilter: "group.mailNickname == group.displayName"
```

**Additional CEL expression examples:**

```yaml
# Only security-enabled groups
clientFilter: "group.securityEnabled"

# Groups without a description (absent fields default to zero value: "" for strings, false for bool)
clientFilter: 'group.description == ""'

# Complex expression combining multiple conditions
clientFilter: "group.securityEnabled && !group.mailEnabled && group.mailNickname == group.displayName"

# Groups created after a specific date
clientFilter: "group.createdDateTime > timestamp('2024-01-01T00:00:00Z')"
```

**Performance Note:** The `filter` parameter uses server-side filtering (fast), while `clientFilter` applies after retrieving groups (slower for large result sets). For best performance, use `filter` to narrow down the result set, then use `clientFilter` for fine-grained control.

#### Authenticating to Azure

Authentication to Azure can be performed using Application Registration with access to query group information in Azure Active Directory.
Expand Down
11 changes: 11 additions & 0 deletions api/v1alpha1/groupsync_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,17 @@ type AzureProvider struct {
// +kubebuilder:validation:Optional
Filter string `json:"filter,omitempty"`

// ClientFilter is a CEL expression for client-side filtering of groups after retrieval from Azure.
// The expression evaluates against a 'group' object with fields derived from the Azure Group object.
// String fields absent from the API response default to "", bool fields to false, int fields to 0.
// Supported fields: displayName, mailNickname, id, securityEnabled, mailEnabled, description,
// groupTypes, createdDateTime, visibility, membershipRule, onPremisesSyncEnabled, mail.
// Example: 'group.mailNickname == group.displayName' filters to groups where these fields match.
// Example: 'group.securityEnabled && group.description == ""' filters to security groups without descriptions.
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Client Filter (CEL Expression)",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"}
// +kubebuilder:validation:Optional
ClientFilter string `json:"clientFilter,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the client filter is not specific to Azure. Would it potentially make sense to make available to all providers and if so, the logic should be moved to a common areas (such as syncer.go)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure to get this point. The filter is specific to the provider (since fields are different), should you implement several providers and extend the CEL filtering support you would have a filter per provider right?


// Insecure specifies whether to allow for unverified certificates to be used when communicating to Azure
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Ignore SSL Verification",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"}
// +kubebuilder:validation:Optional
Expand Down
46 changes: 45 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading