Every credential provider says whether it still holds a consumer
holds() for postgres, mssql, mongodb, minio, lavinmq, mosquitto, mailu and gitea, so the harness makes again a login the backend lost (hq issue 120). Each checks the mesh's password as the consumer presents it, or compares it read-only, and returns false only when the backend says the credential is absent or wrong; an unreachable backend throws.
This commit is contained in:
+17
-4
@@ -125,6 +125,18 @@ export class MinioClient {
|
||||
throw new Error(`minio bucketExists ${bucket}: ${status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a consumer's access key, with exactly this secret, reaches its bucket: a HEAD of the
|
||||
* bucket signed as the consumer, the way it signs. Read-only. `false` when the key is unknown, the
|
||||
* secret wrong, access denied or the bucket gone; any other answer rejects (novox/hq issue 120).
|
||||
*/
|
||||
async canReachAs(bucket: string, accessKey: string, secretKey: string): Promise<boolean> {
|
||||
const { status } = await this.request("HEAD", `/${bucket}`, {}, { accessKey, secretKey });
|
||||
if (status === 200) return true;
|
||||
if (status === 403 || status === 404) return false;
|
||||
throw new Error(`minio HEAD ${bucket} as ${accessKey}: ${status}`);
|
||||
}
|
||||
|
||||
async createBucket(bucket: string): Promise<void> {
|
||||
const { status, text } = await this.request("PUT", `/${bucket}`);
|
||||
// 200 created; 409 BucketAlreadyOwnedByYou — idempotent, a re-provision must not fail.
|
||||
@@ -251,6 +263,7 @@ export class MinioClient {
|
||||
method: string,
|
||||
path: string,
|
||||
query: Record<string, string> = {},
|
||||
as: { accessKey: string; secretKey: string } = { accessKey: this.rootUser, secretKey: this.rootPassword },
|
||||
): Promise<{ status: number; headers: Headers; text: string }> {
|
||||
const { amzDate, dateStamp } = this.stamp();
|
||||
const host = new URL(this.baseUrl).host;
|
||||
@@ -262,8 +275,8 @@ export class MinioClient {
|
||||
const canonicalRequest = [method, encodedPath, canonicalQuery, canonicalHeaders, signedHeaders, payloadHash].join("\n");
|
||||
const scope = `${dateStamp}/${this.region}/s3/aws4_request`;
|
||||
const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256hex(canonicalRequest)].join("\n");
|
||||
const signature = hmac(this.signingKey(dateStamp), stringToSign).toString("hex");
|
||||
const authorization = `AWS4-HMAC-SHA256 Credential=${this.rootUser}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
const signature = hmac(this.signingKey(dateStamp, as.secretKey), stringToSign).toString("hex");
|
||||
const authorization = `AWS4-HMAC-SHA256 Credential=${as.accessKey}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
|
||||
const url = `${this.baseUrl}${encodedPath}${canonicalQuery ? `?${canonicalQuery}` : ""}`;
|
||||
const res = await fetch(url, {
|
||||
@@ -275,8 +288,8 @@ export class MinioClient {
|
||||
return { status: res.status, headers: res.headers, text };
|
||||
}
|
||||
|
||||
private signingKey(dateStamp: string): Buffer {
|
||||
const kDate = hmac(`AWS4${this.rootPassword}`, dateStamp);
|
||||
private signingKey(dateStamp: string, secretKey: string = this.rootPassword): Buffer {
|
||||
const kDate = hmac(`AWS4${secretKey}`, dateStamp);
|
||||
const kRegion = hmac(kDate, this.region);
|
||||
const kService = hmac(kRegion, "s3");
|
||||
return hmac(kService, "aws4_request");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@novox/mesh-sdk": "^0.1.0"
|
||||
"@novox/mesh-sdk": "^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
|
||||
@@ -53,6 +53,12 @@ runProvisioner("s3-bucket", {
|
||||
|
||||
await announce("module.minio.bucket.removed", { bucket, accessKey: p.as });
|
||||
},
|
||||
|
||||
// Asked every minute by the harness: whether the backend still holds this consumer exactly as
|
||||
// the mesh gave it, so a login lost behind the provisioner's back is made again (novox/hq issue 120).
|
||||
async holds(p: Provision): Promise<boolean> {
|
||||
return minio.canReachAs(bucketFor(p.as), p.as, p.password);
|
||||
},
|
||||
});
|
||||
|
||||
/** Emit best-effort: a broker hiccup is logged and dropped, never allowed to throw back and fail a
|
||||
|
||||
Reference in New Issue
Block a user