tutorial

How to Monitor Apache Fortress Identity Management Health with Vigilmon

Apache Fortress LDAP connectivity failures, role assignment propagation delays, and password policy enforcement outages are invisible until users report access failures. Learn how to monitor Fortress identity management availability, LDAP backend health, and scheduled access review job liveness with Vigilmon HTTP probes and heartbeat monitors.

Apache Fortress is an open-source Role-Based Access Control (RBAC) and Identity Management system built on LDAP. It enforces role hierarchies, session constraints, temporal policies, and delegated administration across enterprise applications. When Fortress's LDAP backend becomes temporarily unavailable due to a network partition, every authentication and authorization call fails with LDAPException — applications protected by Fortress throw 403 or 500 errors to all users while the Fortress service itself reports no outage because it is stateless and the failure is in the LDAP connection layer; when an LDAP connection pool configuration is wrong after a Fortress upgrade, the pool may hold expired connections that pass a health ping but fail on first actual use — the first user login after the pool warms up succeeds while subsequent logins intermittently fail; when Fortress's DelAdminMgr delegation constraints are misconfigured in LDAP after a policy import, privilege escalation rules silently stop enforcing — users accumulate roles they should not have while audit logs show successful role assignments. These are identity security incidents disguised as normal system operation.

Vigilmon gives you external visibility into Fortress's identity management health through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.


Why Fortress Needs External Monitoring

Fortress's failure modes are concentrated in the LDAP integration and policy enforcement layers:

  • LDAP connection pool exhaustion: Fortress uses an LDAP connection pool (UnboundID SDK or Apache LDAP API); under high concurrent authentication load, all connections are checked out and new requests block — Fortress's REST API responds slowly or times out while the LDAP server itself is healthy
  • Stale connection pool: LDAP connections have server-side idle timeouts; when Fortress's pool holds connections longer than the LDAP server's idle timeout, the first use of a stale connection throws LDAPConnectionLostException and Fortress retries or fails the authentication — users see intermittent login failures that are hard to reproduce
  • Policy import silent failure: Fortress provides a FortressAntTask and CLI tool for importing LDAP policy objects; when a schema constraint violation occurs during import, the tool may exit with partial import — some roles or permission objects are missing, causing authorization checks to fail for specific entitlements while others work correctly
  • Temporal constraint enforcement failure: Fortress supports day-of-week and time-of-day role activation constraints; when the Fortress service process clock drifts from the LDAP server clock due to NTP misconfiguration, temporal constraints enforce against the wrong time — users gain or lose access at incorrect hours
  • DelAdminMgr constraint bypass: a corrupted ftARC (administrative role constraint) LDAP attribute after a policy migration causes delegated admin role boundaries to stop enforcing — delegated admins can assign roles outside their allowed scope without error
  • Password policy enforcement stall: Fortress integrates with LDAP's pwdPolicy object for lockout, history, and expiry enforcement; when the pwdPolicy attribute is missing from a user entry after a schema migration, password policy is silently not enforced for that user

External monitoring with Vigilmon adds:

  • Proactive alerting when Fortress REST API or LDAP backend endpoints fail
  • LDAP connectivity monitoring independent of the Fortress service layer
  • Heartbeat monitoring so you know when scheduled access reviews and password audit jobs stop completing
  • Multi-region probe consensus that filters transient LDAP connection pool refresh events from genuine backend failures

Step 1: Build a Fortress Health Endpoint

Fortress's REST API (fortress-rest) is deployed as a WAR on Tomcat or a Spring Boot application. Add a health endpoint that exercises the LDAP connection.

Spring Boot Fortress REST Application

// health/FortressHealthIndicator.java
package com.yourorg.fortress.health;

import org.apache.directory.fortress.core.AccessMgr;
import org.apache.directory.fortress.core.AccessMgrFactory;
import org.apache.directory.fortress.core.SecurityException;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class FortressHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        try {
            AccessMgr accessMgr = AccessMgrFactory.createInstance();
            // Lightweight LDAP connectivity probe — checks a known session context
            // without performing actual authentication
            accessMgr.isUserInRole(
                new org.apache.directory.fortress.core.model.Session(),
                new org.apache.directory.fortress.core.model.UserRole()
            );
            return Health.up()
                .withDetail("fortress", "ldap_connected")
                .build();
        } catch (SecurityException e) {
            // SecurityException from a valid call means LDAP is reachable
            // but the probe params are invalid — that's expected and healthy
            if (e.getErrorId() != 0) {
                return Health.up()
                    .withDetail("fortress", "ldap_reachable")
                    .withDetail("probe_error_id", e.getErrorId())
                    .build();
            }
            return Health.down(e)
                .withDetail("fortress", "ldap_unavailable")
                .build();
        } catch (Exception e) {
            return Health.down(e)
                .withDetail("fortress", "connection_failed")
                .build();
        }
    }
}

Enable Actuator health exposure in application.properties:

management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=health,info

Dedicated LDAP Probe Endpoint (WAR Deployment)

For Fortress deployed as a WAR without Spring Boot Actuator:

// servlet/FortressHealthServlet.java
package com.yourorg.fortress.servlet;

import com.unboundid.ldap.sdk.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

@WebServlet("/health/fortress")
public class FortressHealthServlet extends HttpServlet {

    private final String ldapHost = System.getenv().getOrDefault("LDAP_HOST", "localhost");
    private final int ldapPort = Integer.parseInt(
        System.getenv().getOrDefault("LDAP_PORT", "389")
    );
    private final String bindDn = System.getenv().getOrDefault("LDAP_BIND_DN", "");
    private final String bindPw = System.getenv().getOrDefault("LDAP_BIND_PW", "");

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setContentType("application/json");
        try (LDAPConnection conn = new LDAPConnection(ldapHost, ldapPort)) {
            conn.bind(bindDn, bindPw);
            SearchResult result = conn.search(
                "dc=example,dc=com", SearchScope.BASE, "(objectClass=*)", "+"
            );
            resp.setStatus(200);
            resp.getWriter().write("{\"status\":\"ok\",\"ldap\":\"connected\",\"entries\":"
                + result.getEntryCount() + "}");
        } catch (Exception e) {
            resp.setStatus(503);
            resp.getWriter().write("{\"status\":\"down\",\"error\":\""
                + e.getMessage().replace("\"", "'") + "\"}");
        }
    }
}

Node.js Sidecar

// health/fortress.js
const express = require('express');
const ldap = require('ldapjs');

const app = express();
const LDAP_URL = process.env.LDAP_URL || 'ldap://localhost:389';
const LDAP_BIND_DN = process.env.LDAP_BIND_DN || '';
const LDAP_BIND_PW = process.env.LDAP_BIND_PW || '';
const LDAP_BASE = process.env.LDAP_BASE || 'dc=example,dc=com';

function probeLdap() {
  return new Promise((resolve, reject) => {
    const client = ldap.createClient({ url: LDAP_URL, connectTimeout: 5000 });
    client.on('error', err => { client.destroy(); reject(err); });
    client.bind(LDAP_BIND_DN, LDAP_BIND_PW, err => {
      if (err) { client.destroy(); return reject(err); }
      client.search(LDAP_BASE, { scope: 'base', filter: '(objectClass=*)' }, (err, res) => {
        if (err) { client.destroy(); return reject(err); }
        res.on('searchEntry', () => {});
        res.on('error', e => { client.destroy(); reject(e); });
        res.on('end', () => { client.unbind(); resolve(true); });
      });
    });
  });
}

app.get('/health/fortress', async (req, res) => {
  try {
    await probeLdap();
    return res.status(200).json({ status: 'ok', ldap: 'connected' });
  } catch (err) {
    return res.status(503).json({ status: 'down', error: err.message });
  }
});

app.listen(3021);

Python Sidecar

# health_fortress.py
import os
from ldap3 import Server, Connection, ALL
from flask import Flask, jsonify

app = Flask(__name__)

LDAP_HOST = os.environ.get('LDAP_HOST', 'localhost')
LDAP_PORT = int(os.environ.get('LDAP_PORT', '389'))
LDAP_BIND_DN = os.environ.get('LDAP_BIND_DN', '')
LDAP_BIND_PW = os.environ.get('LDAP_BIND_PW', '')
LDAP_BASE = os.environ.get('LDAP_BASE', 'dc=example,dc=com')

@app.route('/health/fortress')
def health():
    try:
        server = Server(LDAP_HOST, port=LDAP_PORT, get_info=ALL, connect_timeout=5)
        conn = Connection(server, LDAP_BIND_DN, LDAP_BIND_PW, auto_bind=True)
        conn.search(LDAP_BASE, '(objectClass=*)', search_scope='BASE')
        conn.unbind()
        return jsonify({'status': 'ok', 'ldap': 'connected'})
    except Exception as e:
        return jsonify({'status': 'down', 'error': str(e)}), 503

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3021)

Step 2: Configure Vigilmon HTTP Monitor for Fortress

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your Fortress health endpoint: https://fortress.your-org.example.com/actuator/health
  4. Set the check interval to 1 minute
  5. Under Expected response, configure:
    • Status code: 200
    • Response body contains: "status":"UP"
    • Response time threshold: 3000ms
  6. Under Alert channels, assign your Slack or PagerDuty channel
  7. Save the monitor

Add monitors for LDAP connectivity and the Fortress REST API:

| Endpoint | What it catches | |---|---| | /actuator/health | Spring Boot, DB, custom LDAP indicator | | /health/fortress (custom) | Raw LDAP bind and search probe | | /rest/role/read (Fortress REST) | Fortress RBAC REST API availability | | /rest/perms/search (Fortress REST) | Permission store query availability |

For the Fortress REST API endpoints that require authentication, configure Vigilmon's HTTP Basic Auth header or set up a lightweight public ping endpoint that bypasses auth and performs an LDAP probe internally.

Vigilmon's multi-region probing filters transient LDAP connection pool reconnet events from genuine LDAP server failures.


Step 3: Heartbeat Monitoring for Access Review and Audit Jobs

HTTP health checks verify LDAP connectivity and Fortress API availability but not whether scheduled access review, password audit, or role cleanup jobs are completing. Fortress deployments typically run:

  • Periodic access reviews: batch jobs that query all user role assignments via ReviewMgr and export reports for compliance audits
  • Password policy audit: periodic checks that pwdPolicy attributes are present on all user entries and that locked accounts are reported
  • Orphaned role cleanup: batch jobs that remove role assignments from deprovisioned users
  • Role hierarchy consistency checks: periodic scans that verify role hierarchy integrity in LDAP after policy imports

Vigilmon heartbeat monitors catch when these jobs stop running.

Set Up Heartbeat Monitors

For access review job liveness:

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: fortress-access-review
  3. Set the expected interval: 25 hours
  4. Set the grace period: 27 hours
  5. Save — copy the heartbeat URL

For password audit job liveness:

  1. Create another Heartbeat monitor named fortress-password-audit
  2. Set the expected interval: 25 hours
  3. Set the grace period: 27 hours
  4. Save — copy the heartbeat URL

Wire Heartbeats Into Scheduled Jobs

// job/FortressAccessReviewJob.java
package com.yourorg.fortress.job;

import org.apache.directory.fortress.core.*;
import org.apache.directory.fortress.core.model.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.*;
import java.util.List;

@Service
public class FortressAccessReviewJob {

    private final String heartbeatUrl = System.getenv("VIGILMON_ACCESS_REVIEW_HEARTBEAT_URL");
    private final HttpClient http = HttpClient.newHttpClient();

    @Scheduled(cron = "0 0 1 * * *") // daily at 1 AM
    public void runAccessReview() {
        try {
            ReviewMgr reviewMgr = ReviewMgrFactory.createInstance();

            // Pull all users and their assigned roles
            List<User> users = reviewMgr.findUsers(new User());
            for (User user : users) {
                List<UserRole> roles = reviewMgr.assignedRoles(user);
                // Export to audit report (write to file, DB, or send to SIEM)
                exportRoleAssignments(user, roles);
            }

            pingHeartbeat();
        } catch (Exception e) {
            throw new RuntimeException("Access review job failed", e);
        }
    }

    private void exportRoleAssignments(User user, List<UserRole> roles) {
        // Write to audit report store
    }

    private void pingHeartbeat() {
        if (heartbeatUrl == null || heartbeatUrl.isBlank()) return;
        try {
            http.sendAsync(
                HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
                HttpResponse.BodyHandlers.discarding()
            );
        } catch (Exception ignored) {}
    }
}

For shell-based Fortress CLI audit jobs:

#!/bin/bash
# fortress-audit.sh
set -e

java -jar /opt/fortress/fortress-core.jar \
    -Dfort.cfg=/opt/fortress/config/fortress.properties \
    -cp /opt/fortress/lib/* \
    com.yourorg.fortress.audit.PasswordPolicyAudit

if [ -n "$VIGILMON_PASSWORD_AUDIT_HEARTBEAT_URL" ]; then
  curl -s "$VIGILMON_PASSWORD_AUDIT_HEARTBEAT_URL" > /dev/null 2>&1 || true
fi

Step 4: Alert Routing for Fortress Failures

Fortress failures carry identity security implications — an LDAP outage disables authentication for all protected applications, while a silent policy enforcement failure can leave privilege escalation paths open. Route alerts by security impact:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /actuator/health Fortress | Slack + PagerDuty | P1 | | HTTP: LDAP probe endpoint | Slack + PagerDuty | P1 | | HTTP: Fortress REST API /rest/role/read | Slack + PagerDuty | P1 | | Heartbeat: access review job | Slack + PagerDuty (security team) | P1 | | Heartbeat: password policy audit | Slack + email | P2 | | Heartbeat: orphaned role cleanup | Slack + email | P2 |

Set response time thresholds as early warning signals:

  • Alert at 2000ms for the LDAP probe (slow LDAP responses indicate connection pool pressure or LDAP server load)
  • Alert at 2500ms for the Fortress REST API health check (slow responses indicate LDAP pool saturation affecting all authorization checks)
  • Alert at 27 hours gap in access review heartbeat (compliance audit window missed)
  • Alert at 5 minutes gap in LDAP connectivity probe (LDAP down means all Fortress-protected authentication is failing)

For production Fortress deployments protecting enterprise applications, set up a status page in Vigilmon showing identity management availability — this allows application teams and security operations to see Fortress and LDAP health status independently, rather than filing support tickets every time a login failure may be caused by LDAP connectivity.


Summary

Fortress failures span three distinct risk categories: availability failures (LDAP down stops all authentication), silent security failures (policy enforcement bypassed), and compliance failures (audit and review jobs not completing). External monitoring catches availability failures immediately and surfaces compliance job gaps before audit cycles:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /actuator/health | Fortress service, LDAP connectivity, disk space | | HTTP monitor on LDAP probe | Raw LDAP bind and search availability | | HTTP monitor on REST API endpoint | Fortress RBAC query and role assignment availability | | Heartbeat monitor (access review) | Scheduled role assignment audit job liveness | | Heartbeat monitor (password policy audit) | Password policy enforcement verification job liveness |

Get started free at vigilmon.online — your first Fortress identity management monitor is running in under two minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →