mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-19 11:06: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.
117 lines
6.6 KiB
JSON
117 lines
6.6 KiB
JSON
{
|
|
"protocolVersion": "v1",
|
|
"fixtureSet": "redaction",
|
|
"fixture": "ruleset",
|
|
"description": "The frozen deterministic redaction contract. A support bundle manifest stores redactionVersion and rulesetHash so a reader can prove which rules produced a redacted document, and a RustFS agent reproduces the same decisions from canonicalForm alone.",
|
|
"redactionVersion": "rustfs.connect.redaction.v1",
|
|
"redactionVersionFormat": "^rustfs\\.connect\\.redaction\\.v[1-9][0-9]*$",
|
|
"redactionVersionNotes": [
|
|
"An opaque stable identifier, not a semantic version: compare it for equality, never order it.",
|
|
"A change to any line of canonicalForm changes rulesetHash and requires a new major."
|
|
],
|
|
"rulesetHash": "b37436d8e72515394a122d633865b1dc028d4ece349352a0a3a23f52ca4285f3",
|
|
"rulesetHashAlgorithm": "sha256",
|
|
"rulesetHashInput": "The canonicalForm lines below joined with U+000A and terminated with a final U+000A, encoded as UTF-8.",
|
|
"collectionDecision": {
|
|
"registry": "protocol/data-collection-fields.json",
|
|
"rule": "Stage one is an allow-list. A field id absent from the registry is removed before its value is read, so an unknown, newly invented, or L2/L3 field can never be collected no matter what it contains. The value rules below are stage two and never grant collection."
|
|
},
|
|
"placeholder": {
|
|
"token": "[REDACTED]",
|
|
"rule": "One constant token for every redaction, carrying no rule name, no offset, no length, and no digest of the removed value. Redaction is not reversible and the result records no hash of anything it removed."
|
|
},
|
|
"output": {
|
|
"canonicalJson": "Object keys are emitted in ascending byte order and an object that loses every entry is removed from its parent, so the same input and version always produce the same bytes.",
|
|
"counts": [
|
|
"droppedField: a field the allow-list refused, a key that is not a plain ASCII identifier, or an object left with no entries.",
|
|
"redactedValue: a value replaced by the placeholder.",
|
|
"redactedOversizeValue: a value replaced because it is longer than maxValueBytes and cannot be scanned within budget."
|
|
]
|
|
},
|
|
"coverage": {
|
|
"AWS_ACCESS_KEY_ID": "S3 and AWS access key ids.",
|
|
"AWS_SECRET_ACCESS_KEY": "A standalone 40-character S3 secret access key. Mixed case plus a digit is required so a single-case hex digest of the same length is not redacted.",
|
|
"BEARER_TOKEN": "HTTP bearer credentials, including a captured Authorization header.",
|
|
"CREDENTIAL_ASSIGNMENT": "API keys, registration tokens, session tokens, passphrases, and KMS secrets written as an assignment in a connection string, environment dump, or configuration snippet.",
|
|
"JWT": "JSON web tokens presented on their own.",
|
|
"PASSWORD_ASSIGNMENT": "A password written as an assignment, including inside a DSN.",
|
|
"PEM_PRIVATE_KEY": "Any PEM private key or private key block header, which covers device keys and KMS private material.",
|
|
"SESSION_ID_ASSIGNMENT": "Session and CSRF identifiers written as an assignment.",
|
|
"URL_CREDENTIALS": "Credentials in a URL or DSN authority. The whole value is replaced, so the host the credential belonged to is not published either."
|
|
},
|
|
"keyRuleNormalisation": "ASCII-lowercase the key and remove '_', '-', and '.', so secret_access_key, Secret-Access-Key, and secretAccessKey are the same key. A key that is not a plain ASCII identifier is dropped rather than normalised.",
|
|
"canonicalForm": [
|
|
"version\trustfs.connect.redaction.v1",
|
|
"placeholder\t[REDACTED]",
|
|
"budget\tmaxInputBytes\t262144",
|
|
"budget\tmaxDepth\t8",
|
|
"budget\tmaxNodes\t4096",
|
|
"budget\tmaxValueBytes\t4096",
|
|
"keyPattern\t/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/D",
|
|
"field\theartbeat.agentVersion\tL0",
|
|
"field\theartbeat.capabilities\tL0",
|
|
"field\theartbeat.clientTime\tL0",
|
|
"field\theartbeat.coarseNodeSummary\tL0",
|
|
"field\theartbeat.protocolVersion\tL0",
|
|
"field\theartbeat.sequence\tL0",
|
|
"field\tinventory.capacityTotalBytes\tL0",
|
|
"field\tinventory.capacityUsedBytes\tL0",
|
|
"field\tinventory.coarseFlags\tL0",
|
|
"field\tinventory.driveCount\tL0",
|
|
"field\tinventory.nodeCount\tL0",
|
|
"field\tinventory.osVersion\tL0",
|
|
"field\tinventory.rustfsVersion\tL0",
|
|
"field\toffline.capacityTotalBytes\tL0",
|
|
"field\toffline.capacityUsedBytes\tL0",
|
|
"field\toffline.coarseHealthFlags\tL0",
|
|
"field\toffline.cpuSummary\tL1",
|
|
"field\toffline.driveCount\tL0",
|
|
"field\toffline.filesystemSummary\tL1",
|
|
"field\toffline.kernelSummary\tL1",
|
|
"field\toffline.memorySummary\tL1",
|
|
"field\toffline.networkSummary\tL1",
|
|
"field\toffline.nodeCount\tL0",
|
|
"field\toffline.osSummary\tL1",
|
|
"field\toffline.rustfsVersion\tL0",
|
|
"keyRule\taccesskey",
|
|
"keyRule\taccesskeyid",
|
|
"keyRule\tapikey",
|
|
"keyRule\tapitoken",
|
|
"keyRule\tauthorization",
|
|
"keyRule\tbearertoken",
|
|
"keyRule\tcookie",
|
|
"keyRule\tcredential",
|
|
"keyRule\tcredentials",
|
|
"keyRule\tcsrftoken",
|
|
"keyRule\tkmskey",
|
|
"keyRule\tkmskeyid",
|
|
"keyRule\tkmsmasterkey",
|
|
"keyRule\tkmssecret",
|
|
"keyRule\tpassphrase",
|
|
"keyRule\tpasswd",
|
|
"keyRule\tpassword",
|
|
"keyRule\tprivatekey",
|
|
"keyRule\tpwd",
|
|
"keyRule\trefreshtoken",
|
|
"keyRule\tregistrationtoken",
|
|
"keyRule\tsecret",
|
|
"keyRule\tsecretaccesskey",
|
|
"keyRule\tsecretkey",
|
|
"keyRule\tsessioncookie",
|
|
"keyRule\tsessionid",
|
|
"keyRule\tsessiontoken",
|
|
"keyRule\tsigningkey",
|
|
"keyRule\ttoken",
|
|
"keyRule\txsrftoken",
|
|
"valueRule\tAWS_ACCESS_KEY_ID\t/\\b(?:A3T[A-Z0-9]{2}|ABIA|ACCA|AKIA|ASIA)[A-Z0-9]{16}\\b/",
|
|
"valueRule\tAWS_SECRET_ACCESS_KEY\t/(?<![A-Za-z0-9+\\/])(?=[A-Za-z0-9+\\/]{0,39}[a-z])(?=[A-Za-z0-9+\\/]{0,39}[A-Z])(?=[A-Za-z0-9+\\/]{0,39}[0-9])[A-Za-z0-9+\\/]{40}(?![A-Za-z0-9+\\/=])/",
|
|
"valueRule\tBEARER_TOKEN\t/(?i)\\bbearer\\s{1,8}[A-Za-z0-9\\-._~+\\/]{8,4096}={0,2}/",
|
|
"valueRule\tCREDENTIAL_ASSIGNMENT\t/(?i)\\b[a-z0-9_.-]{0,24}(?:access[_.-]?key(?:[_.-]?id)?|api[_.-]?key|credentials?|passphrase|secret(?:[_.-]?key)?|token)\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9\\-._~+\\/=]{8,4096}/",
|
|
"valueRule\tJWT\t/\\beyJ[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}/",
|
|
"valueRule\tPASSWORD_ASSIGNMENT\t/(?i)\\b(?:passwd|password|pwd)\\b\\s{0,8}[:=]\\s{0,8}\\S/",
|
|
"valueRule\tPEM_PRIVATE_KEY\t/-----BEGIN [A-Z0-9 ]{0,32}PRIVATE KEY(?: BLOCK)?-----/",
|
|
"valueRule\tSESSION_ID_ASSIGNMENT\t/(?i)\\b(?:csrf[_.-]?token|jsessionid|phpsessid|sess|session|sid|xsrf[_.-]?token)(?:[_.-]?id)?\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9%\\-._~+\\/]{12,4096}/",
|
|
"valueRule\tURL_CREDENTIALS\t/\\b[a-zA-Z][a-zA-Z0-9+.\\-]{0,31}:\\/\\/[^\\s\\/@:]{1,256}(?::[^\\s\\/@]{0,256})?@/"
|
|
]
|
|
}
|