File Integrity Monitoring & Node Hardening
Duration: ~30 minutes
Overview
Learn how to deploy and configure the File Integrity Operator to monitor file system integrity on OpenShift nodes, detect unauthorized changes, and implement node-level security hardening measures. This is a foundational capability for incident response and compliance monitoring.
Why it matters
File integrity monitoring detects unauthorized changes to critical system files, which could indicate compromise or misconfiguration. Combined with node hardening, this provides defense-in-depth for the control plane and worker nodes, meeting compliance requirements for system integrity monitoring.
A point-in-time CIS scan of kubelet and API-server file permissions is not enough. Auditors want a continuous change-detection story: baseline critical paths, alert on add/change/delete, and show who investigated. File Integrity Operator (AIDE on RHCOS) is that story on OpenShift.
| Standard | Control | What FIM plus node hardening shows |
|---|---|---|
PCI DSS 4.0 |
Req 11.5.2 (was 11.5 in 3.2.1) |
Change-detection on critical files, with alerting when someone tampers with them |
NIST SP 800-53 Rev. 5 |
SI-7 (Software, Firmware, and Information Integrity); CM-3 / CM-6 |
Integrity monitoring of software and config; unauthorized baseline drift is visible |
CIS Kubernetes / OpenShift ( |
Control plane 1.1.x and worker 4.1.x file-permission checks |
CIS proves mode/owner at scan time; FIM proves those files did not change afterward |
ISO/IEC 27001:2022 |
A.8.9 Configuration management; A.8.16 Monitoring activities |
Config is controlled and monitored, not only documented |
SOC 2 TSC |
CC7.1 / CC7.2 detection and monitoring; CC8.1 change management |
Anomalies on node binaries/config are detected; changes are not silent |
HIPAA |
164.312(c)(1) Integrity |
ePHI-supporting systems can show unauthorized alteration of system files was monitored |
FedRAMP inherits SI-7 from 800-53. Pair FIM with 101-10 (CIS evidence) and 201-07 (tailoring) so the scan and the integrity feed tell the same story.
File Integrity Operator is one integrity step: AIDE on the node (kubelet configs, host binaries, /etc on RHCOS). It does not see what a process inside a pod writes. That file activity is RHACS Collector: runtime events and policies on unexpected writes, dropped binaries, and package-manager execution in the container. You need both; neither replaces the other.
| Control | Watches | What a hit means |
|---|---|---|
File Integrity Operator (this lab) |
Host filesystem on selected nodes |
A critical node file was added, changed, or removed versus the AIDE baseline |
RHACS runtime (Collector) |
Process and file activity inside pods |
A workload executed or wrote something the policy did not expect (package manager, crypto miner dropper, write to a sensitive path) |
What does it solve
-
Undetected file system tampering on the node
-
Silent file activity inside pods if you stop at FIO and never turn on RHACS runtime
-
PCI / NIST / CIS / ISO / SOC 2 integrity-monitoring evidence with no operator story
-
Node-level security gaps
-
Unauthorized configuration changes
-
Security baseline drift
Your Mission
Turn on File Integrity Operator for worker nodes so a silent binary or config change on the host is a report, not a surprise during incident response. Treat that as the node layer only—RHACS still has to watch file activity in the workload.
Click each step only if you need a hint.
Install File Integrity Operator
The operator is cluster-scoped in effect (AIDE on nodes) but OLM-installs into openshift-file-integrity. The namespace needs privileged PSA so the operator and AIDE pods can run. This is the documented CLI install — no OperatorHub click-path.
oc apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: openshift-file-integrity
labels:
openshift.io/cluster-monitoring: "true"
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: file-integrity-operator
namespace: openshift-file-integrity
spec:
targetNamespaces:
- openshift-file-integrity
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: file-integrity-operator
namespace: openshift-file-integrity
spec:
channel: stable
installPlanApproval: Automatic
name: file-integrity-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
| Namespace, OperatorGroup, and Subscription created (or already configured). Image pull can take a few minutes. |
Wait until File Integrity Operator is Succeeded
Do not create a FileIntegrity object until the CSV is Succeeded.
echo "Waiting for File Integrity Operator CSV..."
for i in $(seq 1 36); do
PHASE=$(oc get csv -n openshift-file-integrity -o jsonpath='{.items[0].status.phase}' 2>/dev/null || true)
echo " attempt $i: ${PHASE:-none}"
[ "$PHASE" = "Succeeded" ] && break
sleep 5
done
oc get csv -n openshift-file-integrity
oc get pods -n openshift-file-integrity
CSV PHASE is Succeeded and the operator pod is Running. If the CSV never appears, check oc get packagemanifest file-integrity-operator -n openshift-marketplace.
|
Install File Integrity console plugin
Optional UI: Compute → File Integrity in the admin console. This is a separate community operator in its own namespace. It reads File Integrity CRs with your token; it does not need to live next to the operator. Skip this step if community-operators is not on the cluster.
The install form defaults the plugin to Disabled. The next step enables it with oc.
PLUGIN_CHANNEL=$(oc get packagemanifest file-integrity-console-plugin -n openshift-marketplace -o jsonpath='{.status.defaultChannel}' 2>/dev/null || true)
if [ -z "$PLUGIN_CHANNEL" ]; then
echo "Packagemanifest file-integrity-console-plugin not found — skip (community catalog missing)."
else
echo "Using plugin channel: $PLUGIN_CHANNEL"
oc apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: file-integrity-console-plugin
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: file-integrity-console-plugin
namespace: file-integrity-console-plugin
spec:
targetNamespaces:
- file-integrity-console-plugin
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: file-integrity-console-plugin
namespace: file-integrity-console-plugin
spec:
channel: ${PLUGIN_CHANNEL}
installPlanApproval: Automatic
name: file-integrity-console-plugin
source: community-operators
sourceNamespace: openshift-marketplace
EOF
fi
subscription.operators.coreos.com/file-integrity-console-plugin created (or configured). Channel comes from the packagemanifest so it matches this OpenShift version.
|
Wait for the plugin and enable it on the console
Community plugins are not trusted by default. Enabling adds file-integrity-console-plugin to the cluster Console CR. The console restarts once; refresh the browser afterward.
echo "Waiting for console plugin CSV..."
for i in $(seq 1 36); do
PHASE=$(oc get csv -n file-integrity-console-plugin -o jsonpath='{.items[0].status.phase}' 2>/dev/null || true)
echo " attempt $i: ${PHASE:-none}"
[ "$PHASE" = "Succeeded" ] && break
[ -z "$PHASE" ] && [ "$i" -ge 3 ] && echo "No CSV yet — skip enable if you skipped the plugin install." && break
sleep 5
done
oc get csv -n file-integrity-console-plugin 2>/dev/null || true
oc get pods -n file-integrity-console-plugin 2>/dev/null || true
PLUGIN=file-integrity-console-plugin
ENABLED=$(oc get consoles.operator.openshift.io cluster -o jsonpath='{.spec.plugins[*]}' 2>/dev/null || true)
if echo " $ENABLED " | grep -q " $PLUGIN "; then
echo "Console plugin already enabled: $ENABLED"
elif [ -z "$ENABLED" ]; then
oc patch consoles.operator.openshift.io cluster --type=json \
-p "[{\"op\":\"add\",\"path\":\"/spec/plugins\",\"value\":[\"$PLUGIN\"]}]"
else
oc patch consoles.operator.openshift.io cluster --type=json \
-p "[{\"op\":\"add\",\"path\":\"/spec/plugins/-\",\"value\":\"$PLUGIN\"}]"
fi
oc get consoles.operator.openshift.io cluster -o jsonpath='{.spec.plugins}{"\n"}'
oc get consoleplugin "$PLUGIN" 2>/dev/null || echo "ConsolePlugin CR appears after the plugin pod starts."
spec.plugins includes file-integrity-console-plugin. OpenShift Console → Compute → File Integrity after a browser refresh.
|
Confirm operator pods in openshift-file-integrity
AIDE daemonset pods appear after you create the FileIntegrity object in the next steps. Right now you should see the operator itself.
oc get pods -n openshift-file-integrity
Operator pod(s) Running. No resources found means the Subscription has not reconciled yet — re-run the wait step.
|
Get FileIntegrity objects cluster-wide
See whether a scan object already exists before you create one.
oc get fileintegrity -A
An empty list is fine. You will create worker-file-integrity next.
|
Apply FileIntegrity worker-file-integrity
Select worker nodes. Leave debug off unless you are troubleshooting the operator itself.
oc apply -f - <<'EOF'
apiVersion: fileintegrity.openshift.io/v1alpha1
kind: FileIntegrity
metadata:
name: worker-file-integrity
namespace: openshift-file-integrity
spec:
nodeSelector:
node-role.kubernetes.io/worker: ""
config:
name: my-file-integrity-config
namespace: openshift-file-integrity
debug: false
EOF
fileintegrity.fileintegrity.openshift.io/worker-file-integrity created (or configured).
|
Get FileIntegrity in openshift-file-integrity
Confirm the object is Accepted / Active before you hunt for reports.
oc get fileintegrity -n openshift-file-integrity
worker-file-integrity is listed. Status may take a minute to settle.
|
Get FileIntegrityReports
Reports appear after AIDE has a baseline. Wait if the list is empty.
oc get fileintegrityreports -n openshift-file-integrity
| One report per scanned node is typical. |
Read FileIntegrityReport results
Look at the results stanza for added, changed, or removed files.
oc get fileintegrityreports -n openshift-file-integrity -o yaml | grep -A 10 "results"
| An empty change list means the baseline is clean. A hit is a host-file change to investigate. |
Debrief
File Integrity Operator is a camera for node-critical paths—catch unexpected file and config changes before they become incidents. It is not the whole integrity story. A container that writes a miner binary under /tmp never touches the RHCOS AIDE baseline; that shows up as RHACS file/process activity, not a FileIntegrityReport.
What breaks without this:
-
Silent binary or config drift on nodes → malware or misconfig with no early signal
-
PCI DSS 11.5.2 / NIST SI-7 / CIS 1.1 & 4.1 ask for FIM with no operator story
-
FIO only → host is watched, workload file drops are invisible
|
Run File Integrity Operator on the nodes you care about (clear AIDE paths, alert into the same channel as other platform events, pair with node-hardening baselines). Add RHACS runtime policies for pod file and process activity—acs-06 is the first enforcement lab. Compliance evidence for 11.5.2 / SI-7 is stronger when you can show both the node baseline and the workload runtime feed. |
|
Keep FIO scoped to critical host system/bin/config files; overhead is usually light and the scan interval is configurable. Do not try to replace ACS Collector with a tighter AIDE config, or replace FIO with a runtime policy that only sees the container overlay. They answer different auditors' questions. |
