tutorial

How to Monitor Apache Syncope Identity Governance Health with Vigilmon

Apache Syncope provisioning task failures, ConnID connector pool exhaustion, and workflow engine stalls are silent until users report accounts that failed to provision or access rights that weren't revoked. Learn how to monitor Syncope identity governance availability, connector health, and provisioning job liveness with Vigilmon HTTP probes and heartbeat monitors.

Apache Syncope is an open-source Identity Governance and Administration (IGA) platform that manages user identities, roles, and entitlements across connected systems via the ConnID framework. Syncope orchestrates provisioning workflows across LDAP directories, HR systems, and SaaS applications — when a ConnID connector pool is exhausted, the provisioning engine silently queues tasks that never execute, users get provisioned hours or days late while their manager believes the account was created immediately; when a Quartz-based propagation task throws an exception mid-execution, Syncope marks the task as failed in its internal database but sends no external signal — operations teams only discover the failure when an audit finds unprovision tasks that never ran; when Syncope's embedded Activiti workflow engine encounters a corrupted process definition after an upgrade, approval workflows queue indefinitely and no approvals or rejections are ever processed. These are identity security incidents disguised as system behavior.

Vigilmon gives you external visibility into Syncope's identity governance layer through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.


Why Syncope Needs External Monitoring

Syncope's failure modes are subtle and high-impact:

  • ConnID connector pool exhaustion: each ConnID connector has a configured pool of connections to the target system; under high provisioning load, the pool fills and subsequent tasks block indefinitely — the Syncope UI shows tasks queued but not executing, with no external alert
  • Propagation task failure accumulation: Syncope's propagation engine retries failed tasks on a configurable schedule; if the retry schedule is misconfigured after an upgrade, failed tasks accumulate in the FAILURE state indefinitely while the system appears healthy from the outside
  • Workflow stall on Activiti engine: Syncope uses Activiti for approval workflows; a corrupt or incompatible process definition after an upgrade causes all new workflow instances to stall in the ACTIVE state — approvers see no pending approvals and no notifications are sent
  • REST API authentication failure: Syncope's REST API uses bearer token authentication; a misconfigured keymaster.conf.location after a redeployment can cause all API calls to return 401, disabling all external integrations while the Syncope UI (which uses a different auth path) continues to work
  • Reconciliation task stall: Syncope's reconciliation tasks compare the identity store with connected systems; a timeout on a slow LDAP connector causes reconciliation to stall mid-execution, holding a task lock that blocks subsequent scheduled reconciliation runs indefinitely
  • Attribute mapping failure: a schema migration that removes an attribute that is still referenced in a connector's attribute mapping causes propagation to throw MappingManagerException for every provisioning event — accounts are never provisioned but Syncope shows no error on the user record itself

External monitoring with Vigilmon adds:

  • Proactive alerting when Syncope's actuator or REST API endpoints report failures
  • Provisioning engine availability monitoring independent of the Syncope UI state
  • Heartbeat monitoring so you know when scheduled provisioning, reconciliation, or audit export jobs stop completing
  • Multi-region probe consensus that filters transient authentication token blips from genuine Syncope provisioning failures

Step 1: Build a Syncope Health Endpoint

Syncope 3.x ships with Spring Boot Actuator. For older versions, add a dedicated REST health endpoint.

Spring Boot Actuator (Syncope 3.x)

Syncope 3.x exposes /actuator/health by default. Enable detailed health output in core/src/main/resources/application.properties:

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

The /actuator/health response includes:

{
  "status": "UP",
  "components": {
    "db": { "status": "UP", "details": { "database": "H2", "validationQuery": "isValid()" } },
    "diskSpace": { "status": "UP" },
    "ping": { "status": "UP" }
  }
}

Custom Syncope Health Resource (Syncope 2.x / JAX-RS)

// rest/SyncopeHealthEndpoint.java
package com.yourorg.syncope.rest;

import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
import org.apache.syncope.common.lib.types.TaskType;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.*;

@Path("/health/syncope")
@Produces(MediaType.APPLICATION_JSON)
public class SyncopeHealthEndpoint {

    @Inject
    private TaskDAO taskDAO;

    @GET
    public Response health() {
        Map<String, Object> result = new LinkedHashMap<>();
        Map<String, String> checks = new LinkedHashMap<>();
        boolean anyFailed = false;

        // Check for accumulated propagation failures
        try {
            long failedTasks = taskDAO.count(TaskType.PROPAGATION)
                .stream()
                .filter(t -> "FAILURE".equals(t.getLatestExecStatus()))
                .count();
            if (failedTasks > 10) {
                checks.put("propagation_failures", "high:" + failedTasks);
                anyFailed = true;
            } else {
                checks.put("propagation_failures", "ok:" + failedTasks);
            }
        } catch (Exception e) {
            checks.put("propagation_failures", "down");
            anyFailed = true;
        }

        result.put("status", anyFailed ? "degraded" : "ok");
        result.put("checks", checks);

        return Response
            .status(anyFailed ? Response.Status.SERVICE_UNAVAILABLE : Response.Status.OK)
            .entity(result)
            .build();
    }
}

Node.js Sidecar

// health/syncope.js
const express = require('express');
const http = require('http');

const app = express();
const APP_HOST = process.env.APP_HOST || 'localhost';
const APP_PORT = parseInt(process.env.APP_PORT || '8080');
const HEALTH_PATH = process.env.SYNCOPE_HEALTH_PATH || '/syncope/rest/actuator/health';
const SYNCOPE_USER = process.env.SYNCOPE_ADMIN_USER || 'admin';
const SYNCOPE_PASS = process.env.SYNCOPE_ADMIN_PASS || '';

function probeSyncope() {
  return new Promise((resolve, reject) => {
    const auth = Buffer.from(`${SYNCOPE_USER}:${SYNCOPE_PASS}`).toString('base64');
    const req = http.get(
      {
        host: APP_HOST,
        port: APP_PORT,
        path: HEALTH_PATH,
        timeout: 5000,
        headers: { Authorization: `Basic ${auth}` },
      },
      res => {
        let body = '';
        res.on('data', chunk => { body += chunk; });
        res.on('end', () => resolve({ status: res.statusCode, body }));
      }
    );
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
  });
}

app.get('/health/syncope', async (req, res) => {
  try {
    const result = await probeSyncope();
    const up = result.status === 200 &&
      (result.body.includes('"status":"UP"') || result.body.includes('"status":"ok"'));

    if (!up) {
      return res.status(503).json({
        status: 'down',
        upstream_status: result.status,
        body: result.body.substring(0, 500),
      });
    }

    return res.status(200).json({ status: 'ok' });
  } catch (err) {
    return res.status(503).json({ status: 'down', error: err.message });
  }
});

app.listen(3018);

Python Health Sidecar

# health_syncope.py
import os, requests
from flask import Flask, jsonify

app = Flask(__name__)

APP_BASE = f"http://{os.environ.get('APP_HOST', 'localhost')}:{os.environ.get('APP_PORT', '8080')}"
HEALTH_PATH = os.environ.get('SYNCOPE_HEALTH_PATH', '/syncope/rest/actuator/health')
AUTH = (
    os.environ.get('SYNCOPE_ADMIN_USER', 'admin'),
    os.environ.get('SYNCOPE_ADMIN_PASS', '')
)

@app.route('/health/syncope')
def health():
    try:
        resp = requests.get(f'{APP_BASE}{HEALTH_PATH}', auth=AUTH, timeout=5)
        if resp.status_code != 200:
            return jsonify({'status': 'down', 'upstream_status': resp.status_code}), 503

        data = resp.json()
        status = data.get('status', '')
        if status not in ('UP', 'ok'):
            return jsonify({'status': 'degraded', 'detail': data}), 503

        return jsonify({'status': 'ok', 'components': data.get('components', {})})
    except Exception as e:
        return jsonify({'status': 'down', 'error': str(e)}), 503

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

Step 2: Configure Vigilmon HTTP Monitor for Syncope

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your Syncope health endpoint: https://syncope.your-org.example.com/syncope/rest/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: 5000ms
  6. Under Alert channels, assign your Slack or PagerDuty channel
  7. Save the monitor

Add a second monitor for the Syncope REST API availability:

  • URL: https://syncope.your-org.example.com/syncope/rest/users?page=1&size=1
  • Expected: 200
  • Interval: 2 minutes
  • Alert channel: P1 pager — REST API failure means all external provisioning integrations and SCIM consumers are blocked

For Syncope UI availability, add a third monitor:

  • URL: https://syncope.your-org.example.com/syncope-console/
  • Expected: 200
  • Interval: 5 minutes
  • Alert channel: Slack — UI failure blocks self-service and admin operations

Vigilmon's multi-region probe consensus filters transient JWT token refresh events from genuine Syncope REST API failures.


Step 3: Heartbeat Monitoring for Provisioning and Reconciliation Jobs

Health endpoint monitoring catches database and API availability failures, but not end-to-end provisioning and reconciliation problems. Syncope can report healthy via Actuator while scheduled reconciliation tasks have been silently stalling due to a ConnID connector timeout holding a task lock.

Vigilmon heartbeat monitors catch these: your Syncope provisioning and reconciliation jobs ping Vigilmon after each successful execution.

Set Up Heartbeat Monitors

For provisioning job liveness:

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: syncope-propagation-jobs
  3. Set the expected interval: 30 minutes
  4. Set the grace period: 60 minutes
  5. Save — copy the heartbeat URL

For reconciliation liveness:

  1. Create another Heartbeat monitor named syncope-reconciliation
  2. Set the expected interval: 4 hours
  3. Set the grace period: 5 hours
  4. Save — copy the heartbeat URL

Wire Heartbeats Via Syncope REST API Job Hooks

Syncope 3.x supports job delegates and task notification hooks. Add a heartbeat ping to a custom SchedTaskCommand:

// task/PropagationMonitorTask.java
package com.yourorg.syncope.task;

import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import java.net.http.*;
import java.net.URI;

@Component
public class PropagationMonitorTask implements SchedTaskJobDelegate {

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

    @Override
    public String execute(SchedTask task, boolean dryRun, JobExecutionContext context)
            throws JobExecutionException {
        try {
            // Check for stuck propagation tasks and resolve them
            checkAndResolvePropagationFailures();
            pingHeartbeat(heartbeatUrl);
            return "Propagation health check completed";
        } catch (Exception e) {
            throw new JobExecutionException("Propagation health check failed", e);
        }
    }

    private void checkAndResolvePropagationFailures() throws Exception {
        // Query Syncope REST API for failed tasks and alert
    }

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

For standard reconciliation tasks configured via the Syncope admin console, add the heartbeat URL as an environment variable and invoke it from a post-execution hook script:

#!/bin/bash
# post-reconciliation-hook.sh — runs after each reconciliation task completes
if [ -n "$VIGILMON_RECONCILIATION_HEARTBEAT_URL" ]; then
  curl -s "$VIGILMON_RECONCILIATION_HEARTBEAT_URL" > /dev/null 2>&1 || true
fi

Step 4: Alert Routing for Syncope Failures

Syncope failures range from a single connector pool being exhausted to the entire provisioning engine stalling. Route alerts by severity:

| Monitor | Alert Channel | Priority | |---|---|---| | HTTP: /actuator/health Syncope status | Slack + PagerDuty | P1 | | HTTP: REST API users endpoint | Slack + PagerDuty | P1 | | HTTP: Syncope console UI | Slack | P2 | | Heartbeat: propagation jobs | Slack + PagerDuty | P1 | | Heartbeat: reconciliation tasks | Slack + PagerDuty | P1 | | Heartbeat: audit export | Slack + email | P2 |

Set response time thresholds as early warning signals:

  • Alert at 2000ms for the Actuator health check (slow responses indicate database or connector pool pressure)
  • Alert at 3000ms for the REST API check (REST API slowdowns indicate provisioning engine backpressure)
  • Alert at 60 minutes gap in propagation job heartbeat (stuck propagation means accounts are not being created or updated)
  • Alert at 5 hours gap in reconciliation heartbeat during business hours

For production IGA deployments, set up a status page in Vigilmon showing provisioning engine availability and reconciliation job liveness — this lets identity operations engineers identify Syncope failures before they manifest as access requests that silently never complete or roles that are never revoked after an employee departure.


Summary

Syncope failures present as silent provisioning delays or access governance gaps before they escalate to full IGA platform outages — HR and IT teams see accounts that were never created or roles that were never revoked while Syncope's health actuator reports UP because the HTTP layer is healthy. External monitoring catches these before they become a security incident:

| Monitor Type | What It Covers | |---|---| | HTTP monitor on /actuator/health | Database, disk, and Syncope core availability | | HTTP monitor on REST API endpoint | Provisioning REST layer authentication and response | | HTTP monitor on console UI | Admin and self-service interface availability | | Heartbeat monitor (propagation jobs) | End-to-end provisioning task execution success | | Heartbeat monitor (reconciliation tasks) | Identity store reconciliation and sync job liveness |

Get started free at vigilmon.online — your first Syncope identity governance 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 →