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
- Inspect the bucket/prefix and delete duplicate objects so exactly one signing and one encryption object remain, then retry.
- Upload metadata via the CAS-supported single-writer path and ensure old objects are replaced (same key) rather than added under new keys.
- Narrow the configured prefix/filter so the locator only matches the intended metadata objects.
- 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.
- 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
- Use fixed, well-known object keys (e.g. cas-idp-signing.pem) so uploads overwrite instead of accumulating.
- Restrict write IAM permissions on the metadata bucket to the CAS service account only.
- Don't reuse one bucket across environments; separate staging and production buckets/prefixes.
- Alert/monitor on object counts in the metadata bucket exceeding expected (2 for signing+encryption).
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
- Multiple S3 objects where found in bucket
- Unable to accept the ID token with an invalid [sub] claim
- No assertion consumer service could be found for entity
- Endpoint for is not available or does not define a binding…
- Endpoint for does not define a binding or location for…
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)