apereo/cas · warning

Entity descriptor in the metadata has expired at

Error message

Entity descriptor in the metadata has expired at [{}]

What it means

An EntityDescriptor was found in the metadata provider, but its validUntil attribute is in the past relative to UTC now. CAS treats the descriptor as expired and refuses to build a metadata adaptor, returning Optional.empty() with a warning, because serving expired SAML metadata would break signing/encryption key trust.

Solutions

  1. Obtain fresh metadata from the SP (re-download the URL or regenerate the file) with a new validUntil and update the service's metadataLocation.
  2. Configure the metadata resolver to refresh automatically from the SP's metadata URL instead of a static snapshot (use the SP's metadata feed URL).
  3. If validUntil is intentionally short and trusted internally, republish metadata with a longer/absent validUntil.
  4. Check the CAS server clock (NTP) to rule out skew causing premature expiry.
  5. Clear the cached metadata resolver entry so the next lookup fetches renewed metadata.

Example fix

// before: static expired file
// metadataLocation: file:/etc/cas/sp-metadata.xml   (validUntil=2024-01-01)
// after: auto-refreshing feed
// metadataLocation: https://sp.example.org/saml/metadata (regenerated with current validUntil)
Defensive patterns

Strategy: validation

Validate before calling

var descriptor = resolver.resolveSingle(new CriteriaSet(new EntityIdCriterion(entityID)));
if (descriptor != null && descriptor.getValidUntil() != null
    && descriptor.getValidUntil().isBefore(Instant.now())) {
    throw new IllegalStateException("Entity " + entityID + " metadata expired at " + descriptor.getValidUntil()
        + " — refresh metadataLocation before proceeding");
}

Type guard

boolean entityDescriptorCurrent(EntityDescriptor d) {
    return d == null || d.getValidUntil() == null || d.getValidUntil().isAfter(Instant.now());
}

Try / catch

try {
    return serviceResolver.get(registeredService, entityID);
} catch (Exception e) {
    LoggingUtils.error(LOGGER, e);
    return triggerMetadataRefresh(registeredService); // re-fetch/re-cache metadata
}

Prevention

When it happens

Trigger: Calling get(entityID, ...) when entityDescriptor.getValidUntil() != null and validUntil.isBefore(now UTC) — i.e., the SP metadata document's cacheDuration/validUntil window has lapsed and the metadata source has not been refreshed with a newer document.

Common situations: SP metadata published with a short validity window (e.g. 7 days) but hosted as a static file never regenerated; metadata URL cached by CAS beyond its expiry because cache directives allow stale copies; idle test/dev environments where metadata expired while unattended; clock skew if server clock is ahead (rare but possible).

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/09eaf44eabb563ee. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-saml-idp-core/src/main/java/org/apereo/cas/support/saml/services/idp/metadata/SamlRegisteredServiceMetadataAdaptor.java:117

                () -> "Metadata resolution resulted in a null metadata resolver entry for entity id %s".formatted(entityID));
            
            Assert.isTrue(cachedResult.isResolved(), "Metadata resolution resulted in an unknown metadata resolver entry for entity id %s".formatted(entityID));
            val cachedMetadataResolver = cachedResult.getMetadataResolver();
            LOGGER.debug("Resolved metadata chain from [{}] using [{}]. Filtering the chain by entity ID [{}]",
                registeredService.getMetadataLocation(), cachedMetadataResolver.getId(), entityID);

            val entityDescriptor = cachedMetadataResolver.resolveSingle(criteriaSet);
            if (entityDescriptor == null) {
                LOGGER.warn("Cannot find entity [{}] in metadata provider for criteria [{}]", entityID, criteriaSet);
                return Optional.empty();
            }
            LOGGER.trace("Located entity descriptor in metadata for [{}]", entityID);

            if (entityDescriptor.getValidUntil() != null) {
                val expired = entityDescriptor.getValidUntil()
                    .isBefore(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
                if (expired) {
                    LOGGER.warn("Entity descriptor in the metadata has expired at [{}]", entityDescriptor.getValidUntil());
                    return Optional.empty();
                }
            }
            return getAdaptor(entityID, cachedMetadataResolver, entityDescriptor);
        } catch (final Exception e) {
            LoggingUtils.error(LOGGER, e);
        }
        return Optional.empty();
    }

    private static Optional<SamlRegisteredServiceMetadataAdaptor> getAdaptor(
        final String entityID,
        final MetadataResolver chainingMetadataResolver,
        final EntityDescriptor entityDescriptor) {
        val ssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS);
        if (ssoDescriptor != null) {
            LOGGER.debug("Located SP SSODescriptor in metadata for [{}]. Metadata is valid until [{}]", entityID,
                ObjectUtils.getIfNull(ssoDescriptor.getValidUntil(), "forever"));

View on GitHub (pinned to e7288fc434)