apereo/cas · error

Located [ ] S3 object(s) from bucket [ ]

Error message

Located [{}] S3 object(s) from bucket [{}]

What it means

AmazonS3SamlIdPMetadataLocator.fetchInternal() lists objects in the configured S3 bucket expecting exactly the IdP signing/encryption metadata objects, but found more than one match. It logs a warning, deletes every matching object from the bucket, and throws IllegalArgumentException, because multiple candidate objects make it ambiguous which key material is authoritative.

Solutions

  1. Inspect the bucket/prefix and delete duplicate objects so exactly one signing and one encryption object remain, then retry.
  2. Upload metadata via the CAS-supported single-writer path and ensure old objects are replaced (same key) rather than added under new keys.
  3. Narrow the configured prefix/filter so the locator only matches the intended metadata objects.
  4. Note the locator already deletes the found duplicates before throwing — verify the bucket after this error, as the next attempt may succeed with an empty-then-repopulated state.
  5. Restrict IAM write access to the metadata bucket to prevent uncoordinated uploads from other systems.

Example fix

// before: uploading with timestamped keys -> multiple objects
// s3: key = cas-idp-signing-2024-01-01.pem, cas-idp-signing-2026-01-01.pem
// after: stable keys, overwrite in place
// s3: key = cas-idp-signing.pem (PutObject overwrites, one object total)
Defensive patterns

Strategy: fallback

Validate before calling

// Before locating metadata, assert the bucket holds exactly the expected objects
var list = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucket).prefix(prefix).build());
if (list.contents().size() > 2) { // signing + encryption expected
    throw new IllegalStateException("Bucket " + bucket + "/" + prefix + " has "
        + list.contents().size() + " objects; clean duplicates before proceeding");
}

Try / catch

try {
    return s3Locator.fetchInternal(...);
} catch (IllegalArgumentException e) {
    // locator already deleted duplicates; re-fetch once from the now-clean bucket
    return s3Locator.fetchInternal(...);
}

Prevention

When it happens

Trigger: Calling fetchInternal() (via locate/fetch of the AWS S3 metadata locator) when the ListObjectsV2 response for the bucket/prefix returns 2+ objects — e.g. duplicate uploads of signing/encryption keys or leftover objects from prior writes under the same prefix.

Common situations: Manual re-upload of metadata objects without deleting old ones; concurrent writers (multiple CAS nodes or scripts) each pushing key objects; a bucket prefix broad enough to catch unrelated objects; failed prior runs leaving orphans, then a retry adding another copy; bucket reuse across environments (staging objects left in prod bucket).

Related errors


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

Appendix: source

Thrown at support/cas-server-support-saml-idp-metadata-aws-s3/src/main/java/org/apereo/cas/support/saml/idp/metadata/AmazonS3SamlIdPMetadataLocator.java:60

    @Override
    public SamlIdPMetadataDocument fetchInternal(final Optional<SamlRegisteredService> registeredService) throws Exception {
        val bucketToUse = AmazonS3SamlIdPMetadataUtils.determineBucketNameFor(registeredService, this.bucketName, s3Client);
        LOGGER.debug("Locating S3 object(s) from bucket [{}]...", bucketToUse);
        if (s3Client.listBuckets(ListBucketsRequest.builder().build())
            .buckets().stream().noneMatch(b -> b.name().equalsIgnoreCase(bucketToUse))) {
            LOGGER.debug("S3 bucket [{}] does not exist", bucketToUse);
            return null;
        }

        val result = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketToUse).build());
        val objects = result.contents();
        LOGGER.debug("Located [{}] S3 object(s) from bucket [{}]", objects.size(), bucketToUse);

        if (objects.isEmpty()) {
            return null;
        }
        if (objects.size() > 1) {
            LOGGER.warn("Located [{}] S3 object(s) from bucket [{}]", objects.size(), bucketToUse);
            objects.forEach(obj -> {
                LOGGER.debug("Deleting object [{}] from bucket [{}]", obj.key(), bucketToUse);
                val deleteRequest = DeleteObjectRequest.builder().bucket(bucketToUse).key(obj.key()).build();
                s3Client.deleteObject(deleteRequest);
            });
            throw new IllegalArgumentException("Multiple S3 objects where found in bucket " + bucketToUse);
        }

        val firstMetadataObject = objects.getFirst();
        LOGGER.debug("Fetching object [{}] from bucket [{}]", firstMetadataObject.key(), bucketToUse);
        val metadataEntry = s3Client.getObject(GetObjectRequest.builder().bucket(bucketToUse).key(firstMetadataObject.key()).build());
        return AmazonS3SamlIdPMetadataUtils.readMetadataDocumentFromBucket(metadataEntry, bucketToUse);
    }
}

View on GitHub (pinned to e7288fc434)