mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-19 19:16:17 +00:00
3b5164032a
A RustFS cluster device needs a durable identity before it can exchange a one-time registration token for a certificate. This adds the device-side half of that exchange, which rustfs/connect already verifies. `connect::identity` builds the canonical registration transcript frozen by protocol/agent/v1/registration-proof.md, signs it as low-S ES256, and emits the PKCS#10 certificate request Connect consumes for its SubjectPublicKeyInfo. `connect::identity_store` seals the P-256 key at mode 0600 and publishes it through a no-clobber link, so a retry or a concurrent start returns the original identity rather than minting a second one, and a corrupt or widened key is refused rather than silently replaced. The protocol fixture set is copied here byte-identically because fixture-sets.json names this repository as the consumer copy; the tests verify it against its own manifests and cross-verify Connect-produced ECDSA proofs against transcripts rebuilt locally. Nothing starts a task or touches the S3 data path: an unenrolled deployment generates no key and holds no identity.
149 lines
6.4 KiB
JSON
149 lines
6.4 KiB
JSON
{
|
|
"protocolVersion": "v1",
|
|
"fixtureSet": "bundle",
|
|
"fixture": "error-codes",
|
|
"description": "Frozen ErrorInfo reasons for support bundle manifest validation, and the closed persisted code each one collapses into.",
|
|
"domain": "rustfs.connect",
|
|
"detailType": "type.googleapis.com/google.rpc.ErrorInfo",
|
|
"disclosureRules": [
|
|
"A rejection never reveals whether a bundle, key, or device belongs to another tenant.",
|
|
"A rejection never contains manifest bytes, entry paths, key material, or digests.",
|
|
"Only supportBundleRejectionCode is persisted. The protocol reason stays in the verifier."
|
|
],
|
|
"reasons": [
|
|
{
|
|
"reason": "UNSUPPORTED_PROTOCOL",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The protocolVersion is missing, malformed, or names an unsupported major version."
|
|
},
|
|
{
|
|
"reason": "UNSUPPORTED_FORMAT",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The formatVersion is not rustfs.connect.support.bundleManifest/1."
|
|
},
|
|
{
|
|
"reason": "SIGNATURE_MALFORMED",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The signature is not 64 octets of fixed-width r||s in unpadded base64url, or r or s is out of range."
|
|
},
|
|
{
|
|
"reason": "SIGNATURE_NOT_CANONICAL",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The signature verifies but its s exceeds half the group order."
|
|
},
|
|
{
|
|
"reason": "SIGNATURE_INVALID",
|
|
"httpStatus": 401,
|
|
"status": "UNAUTHENTICATED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "ECDSA verification over the received manifest octets failed. Tampering and re-serialisation both land here."
|
|
},
|
|
{
|
|
"reason": "DEVICE_KEY_UNKNOWN",
|
|
"httpStatus": 401,
|
|
"status": "UNAUTHENTICATED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "No enrolled device key matches deviceKeyId for this bundle."
|
|
},
|
|
{
|
|
"reason": "DEVICE_KEY_REVOKED",
|
|
"httpStatus": 401,
|
|
"status": "UNAUTHENTICATED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The device key is known but revoked. Revocation is not a validity window and is not retroactively forgiven."
|
|
},
|
|
{
|
|
"reason": "ORGANIZATION_MISMATCH",
|
|
"httpStatus": 403,
|
|
"status": "PERMISSION_DENIED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The manifest names a different organization than the bundle Connect authorised."
|
|
},
|
|
{
|
|
"reason": "CLUSTER_MISMATCH",
|
|
"httpStatus": 403,
|
|
"status": "PERMISSION_DENIED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The manifest names a different cluster than the bundle Connect authorised."
|
|
},
|
|
{
|
|
"reason": "DEVICE_MISMATCH",
|
|
"httpStatus": 403,
|
|
"status": "PERMISSION_DENIED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The manifest names a different device than the bundle Connect authorised."
|
|
},
|
|
{
|
|
"reason": "MANIFEST_NOT_YET_VALID",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "producedAt is further in the future than the skew tolerance."
|
|
},
|
|
{
|
|
"reason": "MANIFEST_EXPIRED",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "producedAt is older than the freshness window."
|
|
},
|
|
{
|
|
"reason": "CLASSIFICATION_NOT_PERMITTED",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "An entry declares a classification level that is not collected in this release."
|
|
},
|
|
{
|
|
"reason": "ENTRY_TYPE_UNKNOWN",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "An entry declares a type outside the closed set."
|
|
},
|
|
{
|
|
"reason": "REDACTION_VERSION_UNKNOWN",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "redactionVersion is not an identifier Connect implements. The identifier is opaque, so a newer looking one is never treated as a superset of an older one."
|
|
},
|
|
{
|
|
"reason": "REDACTION_RULESET_MISMATCH",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "redactionVersion is known but rulesetHash is not the hash of the rules that identifier names, so the identifier would stand for two different treatments."
|
|
},
|
|
{
|
|
"reason": "ENTRY_DIGEST_MISMATCH",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "HASH_MISMATCH",
|
|
"meaning": "An archive member does not hash to the digest its manifest entry declares."
|
|
},
|
|
{
|
|
"reason": "ENTRY_SIZE_MISMATCH",
|
|
"httpStatus": 400,
|
|
"status": "INVALID_ARGUMENT",
|
|
"supportBundleRejectionCode": "SIZE_MISMATCH",
|
|
"meaning": "An archive member is not the size its manifest entry declares."
|
|
},
|
|
{
|
|
"reason": "BUNDLE_REPLAYED",
|
|
"httpStatus": 409,
|
|
"status": "ABORTED",
|
|
"supportBundleRejectionCode": "MANIFEST_INVALID",
|
|
"meaning": "The manifest nonce was already accepted for this organization, cluster, and device."
|
|
}
|
|
]
|
|
}
|