From e949fe06edd40f2e1c471c6434bbb81a72405d53 Mon Sep 17 00:00:00 2001 From: Tillmann <112912081+tillmann-crabnebula@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:05:35 +0900 Subject: [PATCH 1/8] add initial security learning tutorial --- src/components/ShowSolution.astro | 70 +++++ .../Security/using-plugin-permissions.mdx | 292 ++++++++++++++++++ 2 files changed, 362 insertions(+) create mode 100644 src/components/ShowSolution.astro create mode 100644 src/content/docs/learn/Security/using-plugin-permissions.mdx diff --git a/src/components/ShowSolution.astro b/src/components/ShowSolution.astro new file mode 100644 index 0000000000..ae7fe59fe9 --- /dev/null +++ b/src/components/ShowSolution.astro @@ -0,0 +1,70 @@ +--- +let { text } = Astro.props; +text = text ?? "Show solution"; +--- + + + + + + + + + + + \ No newline at end of file diff --git a/src/content/docs/learn/Security/using-plugin-permissions.mdx b/src/content/docs/learn/Security/using-plugin-permissions.mdx new file mode 100644 index 0000000000..37e48bf5c5 --- /dev/null +++ b/src/content/docs/learn/Security/using-plugin-permissions.mdx @@ -0,0 +1,292 @@ +--- +title: Using Plugin Permissions +sidebar: + badge: + text: WIP +--- + +import { Steps } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro' +import Cta from '@fragments/cta.mdx'; + +The goal of this excercise is to get a better understanding on how +plugin permissions can be enabled or disabled, where they are described +and how to use default permissions of plugins. + +At the end you will have the ability to find and use permissions of +arbitrary plugins and understand how to custom tailor existing permissions. +You will have an example Tauri application where a plugin and plugin specific +permissions are used. + + + +1. ### Create Tauri Application + + Create your Tauri application. + In our example we will facilitate [`create-tauri-app`](https://github.com/tauri-apps/create-tauri-app): + + + + We will proceed in this step-by-step explanation with `pnpm` but you can choose another + package manager and replace it in the commands accordingly. + + + + ``` + pnpm create tauri-app + ``` + + ``` + ✔ Project name · plugin-permission-demo + ✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun) + ✔ Choose your package manager · pnpm + ✔ Choose your UI template · Vanilla + ✔ Choose your UI flavor · TypeScript + + Template created! To get started run: + cd plugin-permission-demo + pnpm install + pnpm tauri dev + ``` + + +2. ### Add the `file-system` Plugin to Your Application + + To search for existing plugins you can use multiple resources. + + The most straight forward way would be to check out if your plugin is already + in the [Plugins](/plugin/) section of the documentation and therefore part of Tauri's + maintained plugin set. + The Filesystem plugin is part of the Tauri plugin workspace and you can add it to + your project by following the [instructions](/plugin/file-system/#setup). + + If the plugin is part of the community effort you can most likely find it + on [crates.io](https://crates.io/search?q=tauri-plugin-) when searching for `tauri-plugin-`. + + + If it is an existing plugin from our workspace you can use the automated way: + + ``` + pnpm tauri add fs + ``` + + If you have found it on [crates.io](https://crates.io/crates/tauri-plugin-fs) + you need to manually add it as a dependency and modify the Tauri builder + to initialize the plugin: + + ``` + cargo add tauri-plugin-fs + ``` + + Modify `lib.rs` to initialize the plugin: + + ```rust title="src-tauri/src/lib.rs" ins={4} + #[cfg_attr(mobile, tauri::mobile_entry_point)] + fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } + ``` + + +3. ### Understand the Default Permissions of the `fs` Plugin + + Each plugin has a `default` permission set, which contains + all permissions and scopes to use the plugin out of the box + with a reasonable minimal feature set. + + In the case of official maintained plugins you can find a + rendered description in the documentation + (eg. [fs default](/plugin/file-system/#default-permissions)). + + In case you are figuring this out for a community plugin you + need to check out the source code of the plugin. + This should be defined in `your-plugin/permissions/default.toml`. + + + ``` + "$schema" = "schemas/schema.json" + + [default] + description = """ + # Tauri `fs` default permissions + + This configuration file defines the default permissions granted + to the filesystem. + + ### Granted Permissions + + This default permission set enables all read-related commands and + allows access to the `$APP` folder and sub directories created in it. + The location of the `$APP` folder depends on the operating system, + where the application is run. + + In general the `$APP` folder needs to be manually created + by the application at runtime, before accessing files or folders + in it is possible. + + ### Denied Permissions + + This default permission set prevents access to critical components + of the Tauri application by default. + On Windows the webview data folder access is denied. + + """ + permissions = ["read-all", "scope-app-recursive", "deny-default"] + + ``` + + + 4. ### Find the Right Permissions + + This step is all about finding the permissions you need to + for your commands to be exposed to the frontend with the minimal + access to your system. + + The `fs` plugin has autogenerated permissions which will disable + or enable individual commands and allow or disable global scopes. + + These can be found in the [documentation](/plugin/file-system/#command-permissions) + or in the source code of the plugin (`fs/permissions/autogenerated`). + + Let us assume we want to enable writing to a text file `test.txt` + located in the users `$HOME` folder. + + For this we would search in the autogenerated permissions for a + permission to enable writing to text files like `allow-write-text-file` + and then for a scope which would allow us to access the `$HOME/test.txt` + file. + + We need to add these to our `capabilities` section in our + `src-tauri/tauri.conf.json` or in a file in the `src-tauri/capabilities/` folder. + By default there is already a capability in `src-tauri/capabilities/main.json` we + can modify. + + + + ```json title="src-tauri/capabilities/default.json" del={18} ins={19} + { + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "image:default", + "resources:default", + "menu:default", + "tray:default", + "shell:allow-open", + "fs:default", + "fs:allow-write-text-file", + ] + } + ``` + + + + Since there are only autogenerated scopes in the `fs` plugin to + access the full `$HOME` folder, we need to configure our own scope. + This scope should be only enabled for the `write-text-file` command + and should only expose our `test.txt` file. + + + ```json title="src-tauri/capabilities/default.json" del={18} ins={19-22} + { + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "image:default", + "resources:default", + "menu:default", + "tray:default", + "shell:allow-open", + "fs:allow-write-text-file", + { + "identifier": "fs:allow-write-text-file", + "allow": [{ "path": "$HOME/test.txt" }] + }, + ] + } + ``` + + 5. ### Test Permissions in Practice + + After we have added the necessary permission we want to + confirm that our application can access the file and write + it's content. + + + We can use this snippet in our application to write to the file: + + ```ts title="src/main.ts" + import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + + let greetInputEl: HTMLInputElement | null; + + async function write(message: string) { + await writeTextFile('test.txt', message, { baseDir: BaseDirectory.Home }); + } + + window.addEventListener("DOMContentLoaded", () => { + greetInputEl = document.querySelector("#greet-input"); + document.querySelector("#greet-form")?.addEventListener("submit", (e) => { + e.preventDefault(); + if (!greetInputEl ) + return; + + write(greetInputEl.value == "" ? "No input provided": greetInputEl.value); + + }); + }); + + ``` + + Replacing the `src/main.ts` with this snippet means we do not need to modify the default `index.html`, + when using the plain Vanilla+Typescript app. + Entering any input into the input field of the running app will be + written to the file on submit. + + Let's test now in practice: + + ``` + pnpm run tauri dev + ``` + + After writing into the input and clicking "Submit", + we can check via our terminal emulator or by manually opening the + file in your home folder. + + ``` + cat $HOME/test.txt + ``` + + You should be presented with your input and finished learning about using permissions from plugins in Tauri applications. + 🥳 + + + If you encountered this error: + + ```sh + [Error] Unhandled Promise Rejection: fs.write_text_file not allowed. Permissions associated with this command: fs:allow-app-write, fs:allow-app-write-recursive, fs:allow-appcache-write, fs:allow-appcache-write-recursive, fs:allow-appconf... + (anonymous function) (main.ts:5) + ``` + Then you very likely did not properly follow the [previous instructions](#find-the-right-permissions). + + + From f803020945003e2ac8b54c056232378092f9d0d5 Mon Sep 17 00:00:00 2001 From: Tillmann <112912081+tillmann-crabnebula@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:11:15 +0900 Subject: [PATCH 2/8] Iinitial guide to plugin command with permissions --- .../Security/writing-plugin-permissions.mdx | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 src/content/docs/learn/Security/writing-plugin-permissions.mdx diff --git a/src/content/docs/learn/Security/writing-plugin-permissions.mdx b/src/content/docs/learn/Security/writing-plugin-permissions.mdx new file mode 100644 index 0000000000..18feadebe8 --- /dev/null +++ b/src/content/docs/learn/Security/writing-plugin-permissions.mdx @@ -0,0 +1,311 @@ +--- +title: Writing Plugin Permissions +sidebar: + badge: + text: WIP +--- + +import { Steps } from '@astrojs/starlight/components'; +import ShowSolution from '@components/ShowSolution.astro' +import Cta from '@fragments/cta.mdx'; + +The goal of this excercise is to get a better understanding on how +plugin permissions can be created when writing your own plugin. + +At the end you will have the ability to create simple permissions for +your plugins. +You will have an example Tauri plugin where permissions are partially autogenerated +and hand crafted. + + + +1. ### Create a Tauri Plugin + + In our example we will facilitate the Tauri [`cli`]() + to bootstrap a Tauri plugin source code structure. + Make sure you have installed all [Prerequisites]() + and verify you have the Tauri CLI in the correct version + by running `cargo tauri info`. + + The output should indicate the `tauri-cli` version is `2.x`. + We will proceed in this step-by-step explanation with `pnpm` but you can choose another + package manager and replace it in the commands accordingly. + + Once you have a recent version installed you can go + ahead and create the plugin using the Tauri CLI. + + + ```sh + mkdir -p tauri-learning + cd tauri-learning + cargo tauri plugin new test + cd tauri-plugin-test + pnpm install + pnpm build + cargo build + ``` + + +2. ### Create a New Command + + To showcase something practical and simple let us assume + our command writes user input to a file in our temporary folder while + adding some custom header to the file. + + Let's name our command `write_custom_file`, implement it in `src/commands.rs` + and add it to our plugin builder to be exposed to the frontend. + + Tauri's core utils will autogenerate `allow` and `deny` permissions for this + command, so we do not need to care about this. + + + + The command implementation: + + ```rust title="src/commands.rs" ins={15-22} ins=", Manager" + use tauri::{command, AppHandle, Runtime, State, Window, Manager}; + + use crate::{MyState, Result}; + + #[command] + pub(crate) async fn execute( + _app: AppHandle, + _window: Window, + state: State<'_, MyState>, + ) -> Result { + state.0.lock().unwrap().insert("key".into(), "value".into()); + Ok("success".to_string()) + } + + #[command] + pub(crate) async fn write_custom_file( + user_input: String, + app: AppHandle, + ) -> Result { + std::fs::write(app.path().temp_dir().unwrap(), user_input)?; + Ok("success".to_string()) + } + + ``` + + Auto-Generate inbuilt permissions for your new command: + + ```rust title="src/build.rs" ins="\"write_custom_file\"" + const COMMANDS: &[&str] = &["ping", "execute", "write_custom_file"]; + ``` + + These inbuilt permissions will be automatically generated by the Tauri build + system and will be visible in the `permissions/autogenerated/commands` folder. + By default an `enable-` and `deny-` permission will + be created. + + +3. ### Expose the New Command + + The previous step was to write the actual command implementation. + Next we want to expose it to the frontend so it can be consumed. + + + + Configure the Tauri builder to generate the invoke handler to pass frontend + IPC requests to the newly implemented command: + + ```rust title="src/lib.rs" ins="commands::write_custom_file," + pub fn init() -> TauriPlugin { + Builder::new("test") + .invoke_handler(tauri::generate_handler![ + commands::execute, + commands::write_custom_file, + ]) + .setup(|app, api| { + #[cfg(mobile)] + let test = mobile::init(app, api)?; + #[cfg(desktop)] + let test = desktop::init(app, api)?; + app.manage(test); + + // manage state so it is accessible by the commands + app.manage(MyState::default()); + Ok(()) + }) + .build() + } + ``` + + Expose the new command in the frontend module: + + :::note + + This step is essential for the example application to sucessfully + import the frontend module but as soon as the command handler is generated + the command can be manually invoked from the frontend. + + ::: + + ```ts title="guest-js/index.ts" ins={6-9} + import { invoke } from '@tauri-apps/api/core' + + export async function execute() { + await invoke('plugin:test|execute') + } + + export async function writeCustomFile(user_input: string): Promise { + return await invoke('plugin:test|write_custom_file',{userInput: user_input}); + } + ``` + + :::tip + The invoke parameter needs to be CamelCase. In this example it is `userInput` instead of `user_input`. + ::: + + Make sure your package is built: + + ``` + pnpm build + ``` + + + +4. ### Define Default Plugin Permissions + + As our plugin should expose the `write_custom_file` command by default + we should add this to our `default.toml` permission. + + + Create a new default permission file if not already existing: + ``` + touch permissions/default.toml + ``` + + Add this to our default permission set to allow the new command + we just exposed. + + ```toml title="permissions/default.toml" + "$schema" = "schemas/schema.json" + + [default] + description = """ + This is the default permission set of our test plugin. + It enables the `write_custom_file` command to be exposed. + """ + permissions = ["allow-write-custom-file"] + ``` + + +5. ### Invoke Test Command from Example Application + + The created plugin directory structure contains an `example/tauri-app` folder, + which has a ready to use Tauri application to test out the plugin. + + Since we added a new command we need to slightly modify the frontend to + invoke our new command instead. + + + ```svelte del={11-13,42-45} ins={14-16,45-49} + + +
+

Welcome to Tauri!

+ + + +

+ Click on the Tauri, Vite, and Svelte logos to learn more. +

+ +
+ +
+ +
+ +
{@html response}
+
+
+ +
{@html response}
+
+ + +
+ + + ``` + + Running this and trying to invoke the "Write" button will result in: + + ``` + test.execute not allowed. Permissions associated with this command: test:allow-execute + ``` + + Since this error already contains which permission is associated + we now know that have not added our default permission to the capability of the test + application. + + ```json title="src-tauri/capabilities/main.json" ins={15} + { + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-capability", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "resources:default", + "menu:default", + "tray:default", + "window:allow-set-title", + "test:default" + ] + } + ``` + + Afterwards you should be greeted with this: + + ``` + success + ``` + + And you should find a `test.txt` file in your temporary folder containing a message + from our new implemented plugin command. + 🥳 + +
+ +
From 962b96be35d4b40d628a03a67d5f96f334207972 Mon Sep 17 00:00:00 2001 From: Tillmann <112912081+tillmann-crabnebula@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:33:33 +0900 Subject: [PATCH 3/8] type and minor fixes --- .../Security/writing-plugin-permissions.mdx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/content/docs/learn/Security/writing-plugin-permissions.mdx b/src/content/docs/learn/Security/writing-plugin-permissions.mdx index 18feadebe8..efc84df798 100644 --- a/src/content/docs/learn/Security/writing-plugin-permissions.mdx +++ b/src/content/docs/learn/Security/writing-plugin-permissions.mdx @@ -132,15 +132,12 @@ and hand crafted. } ``` - Expose the new command in the frontend module: - - :::note + Expose the new command in the frontend module. This step is essential for the example application to sucessfully - import the frontend module but as soon as the command handler is generated - the command can be manually invoked from the frontend. - - ::: + import the frontend module. This is for convenience and has + no security impact, as the command handler is already generated + and the command can be manually invoked from the frontend. ```ts title="guest-js/index.ts" ins={6-9} import { invoke } from '@tauri-apps/api/core' @@ -194,14 +191,14 @@ and hand crafted. 5. ### Invoke Test Command from Example Application - The created plugin directory structure contains an `example/tauri-app` folder, + The created plugin directory structure contains an `examples/tauri-app` folder, which has a ready to use Tauri application to test out the plugin. Since we added a new command we need to slightly modify the frontend to invoke our new command instead. - ```svelte del={11-13,42-45} ins={14-16,45-49} + ```svelte title="src/App.svelte" del={11-13,42-45} ins={14-16,45-49}