本文档采用自动化机器翻译技术翻译。 尽管我们力求提供准确的译文,但不对翻译内容的完整性、准确性或可靠性作出任何保证。 若出现任何内容不一致情况,请以原始 英文 版本为准,且原始英文版本为权威文本。

这是尚未发布的文档。 Admission Controller 1.34-dev.

编写验证逻辑

是时候编写实际的验证代码了。 它定义在 src/lib.rs 文件中。 在这个文件中,您可以找到一个名为 validate 的函数。

这是脚手架提供的函数:

fn validate(payload: &[u8]) -> CallResult {
    let validation_request: ValidationRequest<Settings> = ValidationRequest::new(payload)?; (1)

    info!(LOG_DRAIN, "starting validation");
    if validation_request.request.kind.kind != apicore::Pod::KIND {
        warn!(LOG_DRAIN, "Policy validates Pods only. Accepting resource"; "kind" => &validation_request.request.kind.kind);
        return kubewarden::accept_request();
    }
    // TODO: you can unmarshal any Kubernetes API type you are interested in (2)
    match serde_json::from_value::<apicore::Pod>(validation_request.request.object) {
        Ok(pod) => {
            // TODO: your logic goes here (3)
            if pod.metadata.name == Some("invalid-pod-name".to_string()) {
                let pod_name = pod.metadata.name.unwrap();
                info!(
                    LOG_DRAIN,
                    "rejecting pod";
                    "pod_name" => &pod_name
                );
                kubewarden::reject_request(
                    Some(format!("pod name {} is not accepted", &pod_name)),
                    None,
                    None,
                    None,
                )
            } else {
                info!(LOG_DRAIN, "accepting resource");
                kubewarden::accept_request()
            }
        }
        Err(_) => {
            // TODO: handle as you wish
            // We were forwarded a request we cannot unmarshal or
            // understand, just accept it
            warn!(LOG_DRAIN, "cannot unmarshal resource: this policy does not know how to evaluate this resource; accept it");
            kubewarden::accept_request() (4)
        }
    }
}

逐步分析代码清单:

  • 在标记为 ➀ 的行中。将传入的 payload 解析为 ValidationRequest<Setting> 对象。 这会自动用用户提供的参数填充 ValidationRequest 中的 Settings 实例。

  • 在标记为 ➁ 的行中。将嵌入请求中的 Kubernetes 原始 JSON 对象转换为 Pod 结构 的实例。

  • 在标记为 ➂ 的行中。请求中有一个 Pod 对象,代码只批准其中 metadata.name 不等于硬编码值 invalid-pod-name 的请求。

  • 在标记为 ➃ 的行中。请求中不包含 Pod 对象,因此策略接受该请求。

如您所见,代码已经在执行类似于您想要实现的验证。 您只需去除硬编码值,并使用用户通过策略设置提供的值。

您可以通过在 src/lib.rs 中替换脚手架 validate 函数来实现这一点,使用这个函数:

fn validate(payload: &[u8]) -> CallResult {
    let validation_request: ValidationRequest<Settings> = ValidationRequest::new(payload)?;

    info!(LOG_DRAIN, "starting validation");
    if validation_request.request.kind.kind != apicore::Pod::KIND {
        warn!(LOG_DRAIN, "Policy validates Pods only. Accepting resource"; "kind" => &validation_request.request.kind.kind);
        return kubewarden::accept_request();
    }

    match serde_json::from_value::<apicore::Pod>(validation_request.request.object) {
        Ok(pod) => {
            let pod_name = pod.metadata.name.unwrap_or_default();
            if validation_request
                .settings
                .invalid_names
                .contains(&pod_name)
            {
                kubewarden::reject_request(
                    Some(format!("pod name {:?} is not accepted", pod_name)),
                    None,
                    None,
                    None,
                )
            } else {
                kubewarden::accept_request()
            }
        }
        Err(_) => {
            // We were forwarded a request we cannot unmarshal or
            // understand, just accept it
            kubewarden::accept_request()
        }
    }
}

单元测试

最后,您可以创建单元测试来检查验证代码是否按预期工作。

lib.rs 文件底部已经定义了测试,正如您所看到的,Admission Controller 的 Rust SDK 也提供了测试助手。

此外,脚手架项目已经在 test_data 目录中提供了默认的 测试夹具。您将使用这些记录的准入请求来编写单元测试。

src/lib.rs 末尾的测试部分内容更改为如下所示:

#[cfg(test)]
mod tests {
    use super::*;

    use kubewarden_policy_sdk::test::Testcase;
    use std::collections::HashSet;

    #[test]
    fn accept_pod_with_valid_name() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("bad_name1"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/pod_creation.json";
        let tc = Testcase {
            name: String::from("Pod creation with valid name"),
            fixture_file: String::from(request_file),
            expected_validation_result: true,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }

    #[test]
    fn reject_pod_with_invalid_name() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("nginx"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/pod_creation.json";
        let tc = Testcase {
            name: String::from("Pod creation with invalid name"),
            fixture_file: String::from(request_file),
            expected_validation_result: false,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }

    #[test]
    fn accept_request_with_non_pod_resource() -> Result<(), ()> {
        let mut invalid_names = HashSet::new();
        invalid_names.insert(String::from("prod"));
        let settings = Settings { invalid_names };

        let request_file = "test_data/ingress_creation.json";
        let tc = Testcase {
            name: String::from("Ingress creation"),
            fixture_file: String::from(request_file),
            expected_validation_result: true,
            settings,
        };

        let res = tc.eval(validate).unwrap();
        assert!(
            res.mutated_object.is_none(),
            "Something mutated with test case: {}",
            tc.name,
        );

        Ok(())
    }
}

您现在在 lib.rs 中定义了三个单元测试:

  • accept_pod_with_valid_name:接受具有有效名称的 Pod

  • reject_pod_with_invalid_name:拒绝具有无效名称的 Pod

  • accept_request_with_non_pod_resource:接受没有 Pod 作为对象的请求

您可以再次运行单元测试:

$ cargo test
   Compiling demo v0.1.0 (/home/flavio/hacking/kubernetes/kubewarden/demo)
    Finished test [unoptimized + debuginfo] target(s) in 3.45s
     Running target/debug/deps/demo-24670dd6a538fd72

running 5 tests
test settings::tests::accept_settings_with_a_list_of_invalid_names ... ok
test settings::tests::reject_settings_without_a_list_of_invalid_names ... ok
test tests::accept_request_with_non_pod_resource ... ok
test tests::accept_pod_with_valid_name ... ok
test tests::reject_pod_with_invalid_name ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

如果您需要编写一个简单的验证策略,这就是所需的全部。