Skip to main content
Version: Next 🚧

Writing raw policies

Raw policies are policies that can evaluate arbitrary JSON documents. For more information about raw policies, please refer to the raw policies page.

Examples

The following examples should look familiar if you completed the validation page of this tutorial.

note

Remember to mark the policy as raw by using the policyType field in the metadata.yml configuration. Please refer to the metadata specification for more information.

Validation

Let's write a policy that accepts a request in the following format:

{
"request": {
"user": "alice",
"action": "delete",
"resource": "products"
}
}

and validates that:

  • user is in the list of valid users
  • action is in the list of valid actions
  • resource is in the list of valid resources

Start by scaffolding the policy by using the rust policy template.

First, we define the types that represent the payload of the request. We will declare a custom RawValidationRequest type that contains the Request and the Settings, instead of using the ValidationRequest type that is provided by the SDK:

/// RawValidationRequest represents the request that is sent to the validate function by the Policy Server.
#[derive(Deserialize)]
pub(crate) struct RawValidationRequest {
pub(crate) request: Request,
pub(crate) settings: Settings,
}

#[derive(Serialize, Deserialize)]
/// Request represents the payload of the request.
pub(crate) struct Request {
pub(crate) user: String,
pub(crate) resource: String,
pub(crate) action: String,
}

Then we need to define the Settings type and implement the Validatable trait:

/// Settings represents the settings of the policy.
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(default, rename_all = "camelCase")]
pub(crate) struct Settings {
pub(crate) valid_users: Vec<String>,
pub(crate) valid_actions: Vec<String>,
pub(crate) valid_resources: Vec<String>,
}

impl kubewarden::settings::Validatable for Settings {
fn validate(&self) -> Result<(), String> {
info!(LOG_DRAIN, "starting settings validation");

if self.valid_users.is_empty() {
return Err("validUsers cannot be empty".to_string());
}

if self.valid_actions.is_empty() {
return Err("validActions cannot be empty".to_string());
}

if self.valid_resources.is_empty() {
return Err("validResources cannot be empty".to_string());
}

Ok(())
}
}

Finally, we define the validate function:

fn validate(payload: &[u8]) -> CallResult {
let validation_request: RawValidationRequest =
if let Ok(validation_request) = serde_json::from_slice(payload) {
validation_request
} else {
return kubewarden::reject_request(
Some("cannot unmarshal request".to_string()),
None,
None,
None,
);
};

info!(LOG_DRAIN, "starting validation");

let request = validation_request.request;
let settings = validation_request.settings;

if settings.valid_users.contains(&request.user)
&& settings.valid_actions.contains(&request.action)
&& settings.valid_resources.contains(&request.resource)
{
info!(LOG_DRAIN, "accepting resource");
kubewarden::accept_request()
} else {
kubewarden::reject_request(
Some("this request is not accepted".to_string()),
None,
None,
None,
)
}
}

Mutation

Let's modify the previous example to mutate the request instead of rejecting it. In this case, the settings will contain the defaultUser, defaultAction and defaultRequest that will be used to mutate the request if the user, the action or the resource is not valid.

We need to update the Settings type with the new fields:

/// Settings represents the settings of the policy.
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(default, rename_all = "camelCase")]
pub(crate) struct Settings {
pub(crate) valid_users: Vec<String>,
pub(crate) valid_actions: Vec<String>,
pub(crate) valid_resources: Vec<String>,
pub(crate) default_user: String,
pub(crate) default_action: String,
pub(crate) default_resource: String,
}

impl kubewarden::settings::Validatable for Settings {
fn validate(&self) -> Result<(), String> {
info!(LOG_DRAIN, "starting settings validation");

if self.valid_users.is_empty() {
return Err("validUsers cannot be empty".to_string());
}

if self.valid_actions.is_empty() {
return Err("validActions cannot be empty".to_string());
}

if self.valid_resources.is_empty() {
return Err("validResources cannot be empty".to_string());
}

if self.default_user.is_empty() {
return Err("defaultUser cannot be empty".to_string());
}

if self.default_action.is_empty() {
return Err("defaultAction cannot be empty".to_string());
}

if self.default_resource.is_empty() {
return Err("defaultResource cannot be empty".to_string());
}

Ok(())
}
}

and the validate function to introduce the mutation:

fn validate(payload: &[u8]) -> CallResult {
let validation_request: RawValidationRequest =
if let Ok(validation_request) = serde_json::from_slice(payload) {
validation_request
} else {
return kubewarden::reject_request(
Some("cannot unmarshal request".to_string()),
None,
None,
None,
);
};

info!(LOG_DRAIN, "starting validation");

let request = validation_request.request;
let settings = validation_request.settings;

if settings.valid_users.contains(&request.user)
&& settings.valid_actions.contains(&request.action)
&& settings.valid_resources.contains(&request.resource)
{
info!(LOG_DRAIN, "accepting request");
return kubewarden::accept_request();
}

info!(LOG_DRAIN, "mutating request");
let mut request = request;

if !settings.valid_users.contains(&request.user) {
request.user = settings.default_user;
}

if !settings.valid_actions.contains(&request.action) {
request.action = settings.default_action;
}

if !settings.valid_resources.contains(&request.resource) {
request.resource = settings.default_resource;
}

let mutated_request = serde_json::to_value(request)?;
kubewarden::mutate_request(mutated_request)
}