mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
feat(targets): require plugin artifact attestations (#3383)
This commit is contained in:
@@ -55,6 +55,8 @@ pub fn example_external_webhook_plugin() -> ExampleInstallableTargetPlugin {
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
},
|
||||
|
||||
@@ -169,6 +169,7 @@ pub struct TargetPluginInstallPolicy {
|
||||
pub allowed_download_hosts: Vec<String>,
|
||||
pub require_https: bool,
|
||||
pub require_signature: bool,
|
||||
pub require_provenance: bool,
|
||||
}
|
||||
|
||||
impl Default for TargetPluginInstallPolicy {
|
||||
@@ -177,7 +178,8 @@ impl Default for TargetPluginInstallPolicy {
|
||||
allowed_providers: vec!["rustfs".to_string(), "rustfs-labs".to_string()],
|
||||
allowed_download_hosts: vec!["plugins.example.test".to_string()],
|
||||
require_https: true,
|
||||
require_signature: false,
|
||||
require_signature: true,
|
||||
require_provenance: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,12 +203,6 @@ pub fn validate_external_plugin_installation(
|
||||
));
|
||||
}
|
||||
|
||||
if policy.require_signature {
|
||||
return Err(
|
||||
"signature verification is required by install policy but manifests do not expose signatures yet".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let distribution = distribution.ok_or_else(|| "external plugin is missing distribution metadata".to_string())?;
|
||||
if distribution.artifacts.is_empty() {
|
||||
return Err("external plugin distribution has no artifacts".to_string());
|
||||
@@ -236,6 +232,33 @@ pub fn validate_external_plugin_installation(
|
||||
artifact.artifact_id, artifact.digest_sha256
|
||||
));
|
||||
}
|
||||
if policy.require_signature && artifact.signature_uri.is_empty() {
|
||||
return Err(format!("artifact {} must declare a signature uri", artifact.artifact_id));
|
||||
}
|
||||
if policy.require_provenance && artifact.provenance_uri.is_empty() {
|
||||
return Err(format!("artifact {} must declare a provenance uri", artifact.artifact_id));
|
||||
}
|
||||
validate_artifact_uri("signature", artifact.artifact_id, artifact.signature_uri, policy)?;
|
||||
validate_artifact_uri("provenance", artifact.artifact_id, artifact.provenance_uri, policy)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_artifact_uri(label: &str, artifact_id: &str, uri: &str, policy: &TargetPluginInstallPolicy) -> Result<(), String> {
|
||||
if uri.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let parsed_uri = Url::parse(uri).map_err(|err| format!("invalid artifact {label} uri {uri}: {err}"))?;
|
||||
if policy.require_https && parsed_uri.scheme() != "https" {
|
||||
return Err(format!("artifact {artifact_id} must use https {label} uri, got {uri}"));
|
||||
}
|
||||
let host = parsed_uri
|
||||
.host_str()
|
||||
.ok_or_else(|| format!("artifact {artifact_id} {label} uri has no host"))?;
|
||||
if !policy.allowed_download_hosts.iter().any(|allowed| allowed == host) {
|
||||
return Err(format!("artifact {artifact_id} {label} host {host} is not allowed"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -370,6 +393,8 @@ mod tests {
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
};
|
||||
@@ -413,6 +438,8 @@ mod tests {
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
@@ -421,4 +448,82 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_external_installation_rejects_missing_artifact_signature() {
|
||||
let manifest = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
display_name: "Webhook Sidecar",
|
||||
provider: "rustfs-labs",
|
||||
version: "1.0.0",
|
||||
target_type: "webhook",
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
&TargetPluginExternalRuntimeContract {
|
||||
protocol_version: crate::SIDECAR_RUNTIME_PROTOCOL_VERSION,
|
||||
transport: TargetPluginRuntimeTransport::Grpc,
|
||||
},
|
||||
Some(TargetPluginDistributionManifest {
|
||||
artifacts: &[TargetPluginArtifactManifest {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
&policy,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.as_ref().map_err(String::as_str),
|
||||
Err("artifact sidecar-linux-amd64 must declare a signature uri")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_external_installation_rejects_missing_artifact_provenance() {
|
||||
let manifest = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
display_name: "Webhook Sidecar",
|
||||
provider: "rustfs-labs",
|
||||
version: "1.0.0",
|
||||
target_type: "webhook",
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
&TargetPluginExternalRuntimeContract {
|
||||
protocol_version: crate::SIDECAR_RUNTIME_PROTOCOL_VERSION,
|
||||
transport: TargetPluginRuntimeTransport::Grpc,
|
||||
},
|
||||
Some(TargetPluginDistributionManifest {
|
||||
artifacts: &[TargetPluginArtifactManifest {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.sig",
|
||||
provenance_uri: "",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
&policy,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.as_ref().map_err(String::as_str),
|
||||
Err("artifact sidecar-linux-amd64 must declare a provenance uri")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ pub struct TargetPluginArtifactManifest {
|
||||
pub target_triple: &'static str,
|
||||
pub download_uri: &'static str,
|
||||
pub digest_sha256: &'static str,
|
||||
pub signature_uri: &'static str,
|
||||
pub provenance_uri: &'static str,
|
||||
pub size_bytes: u64,
|
||||
}
|
||||
|
||||
@@ -297,6 +299,8 @@ mod tests {
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-plugin.tar.zst",
|
||||
digest_sha256: "0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-plugin.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-plugin.tar.zst.intoto.jsonl",
|
||||
size_bytes: 4096,
|
||||
}],
|
||||
},
|
||||
@@ -313,6 +317,8 @@ mod tests {
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-plugin.tar.zst",
|
||||
digest_sha256: "0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-plugin.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-plugin.tar.zst.intoto.jsonl",
|
||||
size_bytes: 4096,
|
||||
}],
|
||||
})
|
||||
|
||||
@@ -227,6 +227,8 @@ pub(crate) struct PluginArtifactContract {
|
||||
pub target_triple: String,
|
||||
pub download_uri: String,
|
||||
pub digest_sha256: String,
|
||||
pub signature_uri: String,
|
||||
pub provenance_uri: String,
|
||||
pub size_bytes: u64,
|
||||
}
|
||||
|
||||
@@ -237,6 +239,8 @@ impl From<TargetPluginArtifactManifest> for PluginArtifactContract {
|
||||
target_triple: value.target_triple.to_string(),
|
||||
download_uri: value.download_uri.to_string(),
|
||||
digest_sha256: value.digest_sha256.to_string(),
|
||||
signature_uri: value.signature_uri.to_string(),
|
||||
provenance_uri: value.provenance_uri.to_string(),
|
||||
size_bytes: value.size_bytes,
|
||||
}
|
||||
}
|
||||
@@ -555,6 +559,8 @@ mod tests {
|
||||
target_triple: "x86_64-unknown-linux-gnu".to_string(),
|
||||
download_uri: "https://plugins.example.test/webhook.tar.zst".to_string(),
|
||||
digest_sha256: "0123456789abcdef".to_string(),
|
||||
signature_uri: "https://plugins.example.test/webhook.tar.zst.sig".to_string(),
|
||||
provenance_uri: "https://plugins.example.test/webhook.tar.zst.intoto.jsonl".to_string(),
|
||||
size_bytes: 4096,
|
||||
}],
|
||||
}),
|
||||
@@ -572,6 +578,14 @@ mod tests {
|
||||
"https://plugins.example.test/webhook.tar.zst"
|
||||
);
|
||||
assert_eq!(value["distribution"]["artifacts"][0]["digest_sha256"], "0123456789abcdef");
|
||||
assert_eq!(
|
||||
value["distribution"]["artifacts"][0]["signature_uri"],
|
||||
"https://plugins.example.test/webhook.tar.zst.sig"
|
||||
);
|
||||
assert_eq!(
|
||||
value["distribution"]["artifacts"][0]["provenance_uri"],
|
||||
"https://plugins.example.test/webhook.tar.zst.intoto.jsonl"
|
||||
);
|
||||
assert_eq!(value["distribution"]["artifacts"][0]["size_bytes"], 4096);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user