greentic-secrets-runner 0.4.14

Host bridge for environment-backed secrets with tenant policy enforcement.
Documentation
use std::env;
use std::fs::File;
use std::io::{Read, Seek};
use std::path::Path;

use greentic_types::{PROVIDER_EXTENSION_ID, PackManifest, decode_pack_manifest};
use serde_yaml::Value;
use zip::ZipArchive;

fn main() {
    if let Err(err) = run() {
        eprintln!("{err}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), String> {
    let args: Vec<String> = env::args().skip(1).collect();
    if args.is_empty() {
        return Err("Usage: validate-gtpack-extension <pack.gtpack> [pack2.gtpack ...]".into());
    }

    for pack in args {
        validate_pack(&pack)?;
    }

    Ok(())
}

fn validate_pack(pack: &str) -> Result<(), String> {
    let path = Path::new(pack);
    let file = File::open(path).map_err(|err| format!("[ERROR] open {}: {err}", pack))?;
    let mut archive =
        ZipArchive::new(file).map_err(|err| format!("[ERROR] {}: open zip: {err}", pack))?;
    if let Some(bytes) = read_member_bytes(&mut archive, "manifest.cbor") {
        let manifest = decode_pack_manifest(&bytes)
            .map_err(|err| format!("[ERROR] {}: decode manifest.cbor: {err}", pack))?;
        validate_manifest(pack, &manifest)?;
    } else if let Some(bytes) = read_member_bytes(&mut archive, "gtpack.yaml") {
        eprintln!(
            "[WARN] {}: manifest.cbor missing; falling back to gtpack.yaml validation",
            pack
        );
        validate_yaml(pack, &bytes)?;
    } else {
        return Err(format!(
            "[ERROR] {}: missing manifest.cbor and gtpack.yaml",
            pack
        ));
    }

    println!("{pack}: provider extension ok");
    Ok(())
}

fn read_member_bytes<R: Read + Seek>(
    archive: &mut ZipArchive<R>,
    name_suffix: &str,
) -> Option<Vec<u8>> {
    for idx in 0..archive.len() {
        let mut file = archive.by_index(idx).ok()?;
        if file.is_dir() {
            continue;
        }
        if file.name().ends_with(name_suffix) {
            let mut buf = Vec::new();
            if file.read_to_end(&mut buf).is_ok() {
                return Some(buf);
            }
        }
    }
    None
}

fn validate_manifest(pack: &str, manifest: &PackManifest) -> Result<(), String> {
    let extensions = manifest
        .extensions
        .as_ref()
        .ok_or_else(|| format!("[ERROR] {}: missing extensions map", pack))?;
    let provider = extensions.get(PROVIDER_EXTENSION_ID).ok_or_else(|| {
        format!(
            "[ERROR] {}: missing canonical provider extension key {}",
            pack, PROVIDER_EXTENSION_ID
        )
    })?;
    if provider.kind != PROVIDER_EXTENSION_ID {
        return Err(format!(
            "[ERROR] {}: provider extension kind {} != {}",
            pack, provider.kind, PROVIDER_EXTENSION_ID
        ));
    }
    if provider.version != "1.0.0" {
        return Err(format!(
            "[ERROR] {}: provider extension version {} != 1.0.0",
            pack, provider.version
        ));
    }
    Ok(())
}

fn validate_yaml(pack: &str, yaml_bytes: &[u8]) -> Result<(), String> {
    let doc: Value = serde_yaml::from_slice(yaml_bytes)
        .map_err(|err| format!("[ERROR] {}: parse gtpack.yaml: {err}", pack))?;
    let extensions = doc.get("extensions").ok_or_else(|| {
        format!(
            "[ERROR] {}: missing extensions map while reading gtpack.yaml",
            pack
        )
    })?;
    let provider = extensions.get(PROVIDER_EXTENSION_ID).ok_or_else(|| {
        format!(
            "[ERROR] {}: missing canonical provider extension key {} in gtpack.yaml",
            pack, PROVIDER_EXTENSION_ID
        )
    })?;
    let kind = provider
        .get("kind")
        .and_then(Value::as_str)
        .ok_or_else(|| {
            format!(
                "[ERROR] {}: missing provider extension kind in gtpack.yaml",
                pack
            )
        })?;
    if kind != PROVIDER_EXTENSION_ID {
        return Err(format!(
            "[ERROR] {}: provider extension kind {} != {} in gtpack.yaml",
            pack, kind, PROVIDER_EXTENSION_ID
        ));
    }
    let version = provider
        .get("version")
        .and_then(Value::as_str)
        .unwrap_or_default();
    if version != "1.0.0" {
        return Err(format!(
            "[ERROR] {}: provider extension version {} != 1.0.0 in gtpack.yaml",
            pack, version
        ));
    }

    Ok(())
}