Commit f39a8d63 by Marcus Olsson Committed by GitHub

Docs: Plugin signing docs (#28671)

* WIP

* Update plugin signing docs

* Fix review comments
parent 676d393e
......@@ -46,7 +46,7 @@ For plugins prior to Grafana 7.0, all options are considered _Display options_.
While backend plugins were available as an experimental feature in previous versions of Grafana, the support has been greatly improved for Grafana 7. Backend plugins for Grafana 7.0 are backwards-compatible and will continue to work. However, the old backend plugin system has been deprecated, and we recommend that you use the new SDK for backend plugins.
Since Grafana 7.0 introduced [signing of backend plugins]({{< relref "../../plugins/plugin-signature-verification.md" >}}), community plugins won’t load by default if they’re unsigned.
Since Grafana 7.0 introduced [signing of backend plugins]({{< relref "../../plugins/plugin-signatures.md" >}}), community plugins won’t load by default if they’re unsigned.
To learn more, refer to [Backend plugins]({{< relref "backend" >}}).
......@@ -165,4 +165,4 @@ For more information, refer to [Data frames](https://grafana.com/docs/grafana/la
## Troubleshoot plugin migration
With Grafana 7.0, backend plugins can now be cryptographically signed to verify their origin. By default, Grafana will ignore unsigned plugins. For more information, refer to [Allow unsigned plugins]({{< relref "../../plugins/plugin-signature-verification.md#allow-unsigned-plugins" >}}).
With Grafana 7.0, backend plugins can now be cryptographically signed to verify their origin. By default, Grafana will ignore unsigned plugins. For more information, refer to [Allow unsigned plugins]({{< relref "../../plugins/plugin-signatures.md#allow-unsigned-plugins" >}}).
+++
title = "Package a plugin"
type = "docs"
aliases = ["/docs/grafana/latest/developers/plugins/share-a-plugin/"]
+++
# Package a plugin
You've just built your first plugin, and now you want to share it with the world. In this guide, you'll learn how to package and share your plugin with others.
For Grafana to be able to load a plugin, it first needs to be built. When you build a plugin from source, a `dist` directory is created that contains the production build, or _plugin assets_, for your plugin.
When the Grafana server starts, it recursively looks in the plugin directory for any directory that contains a `plugin.json` file and tries to load the plugin assets in the same directory.
There are three steps needed to package a plugin:
- Building the plugin
- Signing the plugin
- Archiving the plugin
1. Build the plugin
```
yarn install --pure-lockfile
yarn build
```
1. (Optional) If your data source plugin has a backend plugin, build it as well.
```
mage
```
1. [Sign the plugin]({{< relref "sign-a-plugin.md" >}}).
1. Create a ZIP archive of the `dist` directory.
```
mv dist/ myorg-simple-panel
zip myorg-simple-panel-1.0.0.zip myorg-simple-panel -r
```
## Publish your plugin on Grafana.com
The best way to share your plugin with the world is to publish it on [Grafana Plugins](https://grafana.com/plugins). By having your plugin published on Grafana.com, more users will be able to discover your plugin.
To publish a plugin to [Grafana Plugins](https://grafana.com/grafana/plugins), create a pull request to the [Grafana Plugin Repository](https://github.com/grafana/grafana-plugin-repository). Please note that both the source code and the packaged plugin archive need to be publicly available.
+++
title = "Share a plugin"
type = "docs"
+++
# Share a plugin
You've just built your first plugin, and now you want to share it with the world. In this guide, you'll learn how to package and share your plugin with others.
When you build a plugin from source, a `dist` directory is created that contains the production build, or _plugin assets_, for your plugin.
When loading your plugin, Grafana only cares about the plugin assets. Specifically, when the Grafana server starts, it attempts to discover and load plugins like this:
1. Look for a `plugin.json` file in any of the subdirectories in the plugin directory.
1. If a `plugin.json` was found, try to load the plugin assets from a `dist` directory in the same directory as the `plugin.json` file.
1. If there's no `dist` directory, try to load the plugin assets from the same directory as the `plugin.json` file.
Now that you know what Grafana needs to load your plugin, let's see how you can share the plugin with other users.
The best way to share your plugin with the world is to publish it on [Grafana Plugins](https://grafana.com/plugins). However, if you're not ready to make your plugin public just yet, you can still share your plugin by hosting the plugin yourself.
## Publish your plugin on Grafana.com
To publish a plugin to [Grafana Plugins](https://grafana.com/grafana/plugins), your plugin first needs to be publicly available in a commit on [GitHub](https://github.com).
The commit you submit needs to either:
- Contain a `dist` directory with the plugin assets
- Contain the plugin assets in the root directory
We strongly recommend that you don't check in the plugin assets to the main branch. Instead, use the following steps to create a release branch that contains the plugin assets.
1. Create a release branch.
```
git checkout -b release-0.1.x
```
1. Build the plugin assets.
```
yarn build
```
1. Add the `dist` directory. The `-f` flag adds the directory even if it's ignored by `.gitignore`.
```
git add -f dist
```
1. Create the release commit.
```
git commit -m "Release v0.1.0"
```
1. Create a release tag. You can also [create the release on GitHub](https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository). If you do, then you can skip this step and the next one.
```
git tag -a v0.1.0 -m "Create release tag v0.1.0"
```
1. Push to GitHub. `follow-tags` tells Git to push the release tag along with our release branch.
```
git push --set-upstream origin release-0.1.x --follow-tags
```
The next step is to submit the URL to your repository, and the release commit, to the [Grafana Plugin Repository](https://github.com/grafana/grafana-plugin-repository).
## Host the plugin yourself
If you want to share your plugin by hosting it yourself, then we recommend that you package it by adding the plugin assets to a .zip archive. You can then make the archive available by hosting it yourself.
How you package the plugin depends on whether you want to include the source code or not.
### Package the plugin with source code
If you want to distribute the source code along with your plugin assets, then you can archive the entire plugin directory.
To create a .zip archive that contains the plugin assets and source code, run the following commands in your terminal:
```
cd my-plugin/
yarn build
zip my-plugin-0.2.0.src.zip . -r -x "node_modules/*" -x ".git/*"
```
### Package the plugin without source code
If you don't want to distribute the plugin with the source code, then you can archive the `dist` directory.
To create a .zip archive that only contains the bare minimum to load the plugin, run the following commands in your terminal:
```
cd my-plugin/
yarn build
cd dist/
zip my-plugin-0.2.0.nosrc.zip . -r
```
### Package and host your plugin using GitHub
If you host your plugin on GitHub, then you can share the plugin using the following URL:
```
https://github.com/GITHUB_USERNAME/GITHUB_REPO_NAME/archive/<VERSION>.zip
```
For example, you can download the [Worldmap Panel](https://github.com/grafana/worldmap-panel) using the following URL:
[https://github.com/grafana/worldmap-panel/archive/v0.3.2.zip](https://github.com/grafana/worldmap-panel/archive/v0.3.2.zip)
### Install a packaged plugin
After the user has downloaded the archive containing the plugin assets, they can install it by extracting the archive into their plugin directory.
```
unzip my-plugin-0.2.0.zip -d YOUR_PLUGIN_DIR/my-plugin
```
......@@ -5,6 +5,97 @@ type = "docs"
# Sign a plugin
Signing a plugin allows Grafana to verify the authenticity of the plugin with [signature verification]({{< relref "../../plugins/plugin-signature-verification.md" >}}). This gives users a way to make sure plugins haven't been tampered with. All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed.
Signing a plugin allows Grafana to verify the authenticity of the plugin with [signature verification]({{< relref "../../plugins/plugin-signatures.md" >}}). This gives users a way to make sure plugins haven't been tampered with. All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed.
We're looking into providing a process for allowing community plugins to be signed in an upcoming version of Grafana.
> **Important:** Future versions of Grafana will require all plugins to be signed.
## Sign your plugin using Grafana Toolkit
The easiest way to sign your plugin is by using the [Grafana Toolkit](https://www.npmjs.com/package/@grafana/toolkit).
You can sign your plugin as a _public_ or a _private_ plugin. In both cases, you need to [create an account on Grafana.com](https://grafana.com/signup) and generate an API key with the `PluginPublisher` role. By creating an account, you can verify that you own the plugin that you want to sign.
### Sign a public plugin
Plugins signed under the community or commercial signature level are considered _public plugins_. Public plugins are published on [Grafana Plugin](https://grafana.com/plugins). For more information about installing public plugins, refer to [Install Grafana plugins]({{< relref "../../plugins/installation.md" >}}).
1. Request a plugin signature level by sending an email to [plugins@grafana.com](mailto:plugins@grafana.com).
1. Sign the plugin with the API key you just created. Grafana Toolkit creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin.
```
export GRAFANA_API_KEY=<YOUR_API_KEY>
npx @grafana/toolkit plugin:sign
```
### Sign a private plugin
If you're developing plugins for internal use only and don't want to make it public, you can sign it under a Private [signature level](#plugin-signature-levels).
1. Sign the plugin with the API key you just created. Grafana Toolkit creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin.
The `rootUrls` flag accepts a comma-separated list of URLs for which the plugin can be used. The URLs need to match the [root_url]({{< relref "../../administration/configuration.md#root_url" >}}) setting.
```
export GRAFANA_API_KEY=<YOUR_API_KEY>
npx @grafana/toolkit plugin:sign --rootUrls https://example.com/grafana
```
## Plugin signature levels
To sign a plugin, you need to decide the _signature level_ you want to sign it under. The signature level of your plugin determines how you can distribute it.
You can sign your plugin under three different _signature levels_.
|**Plugin Level**|**Paid Subscription Required?**|**Description**|
|---|---|---|
|Private|No;<br>Free of charge|<p>You can create and sign a Private Plugin for any technology at no charge.</p><p>Private Plugins are for use on your own Grafana. They may not be distributed to the Grafana community, and are not published in the Grafana catalog.</p>|
|Community|No;<br>Free of charge|<p>You can create, sign and distribute plugins at no charge, provided that all dependent technologies are open source and not for profit.</p><p>Community Plugins are published in the official Grafana catalog, and are available to the Grafana community.</p>|
|Commercial|Yes;<br>Commercial Plugin Subscription required|<p>You can create, sign and distribute plugins with dependent technologies that are closed source or commercially backed, by entering into a Commercial Plugin Subscription with Grafana Labs.</p><p>Commercial Plugins are published on the official Grafana catalog, and are available to the Grafana community.</p>|
For instructions on how to sign a plugin under the Community and Commercial signature level, refer to [Sign a public plugin](#sign-a-public-plugin).
For instructions on how to sign a plugin under the Private signature level, refer to [Sign a private plugin](#sign-a-private-plugin).
## Plugin manifest
For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, _MANIFEST.txt_. The signed manifest file contains two sections:
- **Signed message -** The signed message contains plugin metadata and plugin files with their respective checksums (SHA256).
- **Digital signature -** The digital signature is created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature have been encrypted using expected private key.
**Example manifest file:**
```txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "community",
"signedByOrg": "myorgid",
"signedByOrgName": "My Org",
"plugin": "myorgid-simple-panel",
"version": "1.0.0",
"time": 1602753404133,
"keyId": "7e4d0c6a708866e7",
"files": {
"LICENSE": "12ab7a0961275f5ce7a428e662279cf49bab887d12b2ff7bfde738346178c28c",
"module.js.LICENSE.txt": "0d8f66cd4afb566cb5b7e1540c68f43b939d3eba12ace290f18abc4f4cb53ed0",
"module.js.map": "8a4ede5b5847dec1c6c30008d07bef8a049408d2b1e862841e30357f82e0fa19",
"plugin.json": "13be5f2fd55bee787c5413b5ba6a1fae2dfe8d2df6c867dadc4657b98f821f90",
"README.md": "2d90145b28f22348d4f50a81695e888c68ebd4f8baec731fdf2d79c8b187a27f",
"module.js": "b4b6945bbf3332b08e5e1cb214a5b85c82557b292577eb58c8eb1703bc8e4577"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqEEARMKAAYFAl+IE3wACgkQfk0ManCIZudpdwIHTCqjVzfm7DechTa7BTbd
+dNIQtwh8Tv2Q9HksgN6c6M9nbQTP0xNHwxSxHOI8EL3euz/OagzWoiIWulG
7AQo7FYCCQGucaLPPK3tsWaeFqVKy+JtQhrJJui23DAZLSYQYZlKQ+nFqc9x
T6scfmuhWC/TOcm83EVoCzIV3R5dOTKHqkjIUg==
=GdNq
-----END PGP SIGNATURE-----
```
......@@ -438,8 +438,8 @@
link: /plugins/
- name: Install plugins
link: /plugins/installation/
- name: Plugin signature verification
link: /plugins/plugin-signature-verification/
- name: Plugin signatures
link: /plugins/plugin-signatures/
- name: HTTP APIs
link: /http_api/
children:
......@@ -537,8 +537,8 @@
name: Plugin protocol
- link: /developers/plugins/backend/grafana-plugin-sdk-for-go/
name: Grafana plugin SDK for Go
- name: Share a plugin
link: /developers/plugins/share-a-plugin/
- name: Package a plugin
link: /developers/plugins/package-a-plugin/
- name: Error handling
link: /developers/plugins/error-handling/
- name: Plugin migration guide
......
......@@ -44,7 +44,7 @@ Use app plugins when you want to create an custom out-of-the-box monitoring expe
## Learn more
- [Install plugins]({{< relref "./installation.md" >}})
- [Plugin signature verification]({{< relref "./plugin-signature-verification.md" >}})
- [Plugin signatures]({{< relref "./plugin-signatures.md" >}})
- Browse the available [Plugins](https://grafana.com/grafana/plugins)
<!-- BEGIN Optimal Workshop Intercept Snippet --><div id='owInviteSnippet' style='position:fixed;right:20px;bottom:20px;width:280px;padding:20px;margin:0;border-radius:6px;background:#1857B8;color:#F7F8FA;text-align:left;z-index:2200000000;opacity:0;transition:opacity 500ms;-webkit-transition:opacity 500ms;display:none;'><div id='owInviteMessage' style='padding:0;margin:0 0 20px 0;font-size:16px;'>Got a spare two and a half minutes to help us improve the docs?</div><a id='owInviteOk' href='https://Grafana.optimalworkshop.com/questions/grafana-docs?tag=docs&utm_medium=intercept' onclick='this.parentNode.style.display="none";' target='_blank' style='color:#F7FAFF;font-size:16px;font-weight:bold;text-decoration:underline;'>Yes, I&#x27;ll help</a><a id='owInviteCancel' href='javascript:void(0)' onclick='this.parentNode.style.display="none";' style='color:#F7F8FA;font-size:14px;text-decoration:underline;float:right;'>Close</a></div><script>var owOnload=function(){if(-1==document.cookie.indexOf('ow-intercept-quiz-4ior230e')){var o=new XMLHttpRequest;o.onloadend=function(){try{var o=document.getElementById('owInviteSnippet');var date=new Date();date.setMonth(date.getMonth()+1);this.response&&JSON.parse(this.response).active===!0&&(document.cookie='ow-intercept-quiz-4ior230e=Done;path=/;expires='+date.toUTCString()+';',setTimeout(function(){o.style.display='block',o.style.opacity=1},2e3))}catch(e){}},o.open('POST','https://app.optimalworkshop.com/survey_status/questions/4ior230e/active'),o.send()}};if(window.addEventListener){window.addEventListener('load',function(){owOnload();});}else if(window.attachEvent){window.attachEvent('onload',function(){owOnload();});}</script><!-- END Optimal Workshop snippet -->
......@@ -25,3 +25,11 @@ Grafana Cloud handles the plugin installation automatically.
Follow the instructions on the Install tab. You can either install the plugin with a Grafana CLI command or by downloading and uncompress a .zip file into the Grafana plugins directory. We recommend using Grafana CLI in most instances. The .zip option is available if your Grafana server does not have access to the internet.
For more information about Grafana CLI plugin commands, refer to [Plugin commands]({{< relref "../administration/cli.md#plugins-commands" >}}).
### Install a packaged plugin
After the user has downloaded the archive containing the plugin assets, they can install it by extracting the archive into their plugin directory.
```
unzip my-plugin-0.2.0.zip -d YOUR_PLUGIN_DIR/my-plugin
```
+++
title = "Plugin signature verification"
type = "docs"
+++
# Plugin signature verification
Plugin signature verification (signing) is a security measure to make sure plugins haven't been tampered with. Upon loading, Grafana checks to see if a plugin is signed or unsigned when inspecting and verifying its digital signature.
## How it works
For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, _MANIFEST.txt_. The signed manifest file contains two sections:
- **Signed message -** The signed message contains plugin metadata and plugin files with their respective checksums (SHA256).
- **Digital signature -** The digital signature is created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature have been encrypted using expected private key.
### Signed manifest example file
```txt
// MANIFEST.txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"plugin": "grafana-test-plugin",
"version": "1.0.0",
"files": {
"LICENSE": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
"README.md": "5bfefcdce6eafce3388d1fb200f3b10954cfeac6c7a45fd7dec42687e01ac75d",
"module.js": "3c07596a6a4796c65ef10ba2bc0805e7f3bc9e4e8fc9970a1307b97e29db1c0a",
"module.js.LICENSE.txt": "fdbc28c10f3d21976b4bc16464ad7c630538c0c3101347b5fd44af9066f7022b",
"module.js.map": "c3ac1e8aa98d83c54fd13e43b65e1cf4182a924d2eb23a2f1a6fe40b7785a1bb",
"plugin.json": "cf26a3afb7c10cd9ae40b5296d04172b5dac927d69a51082e6d085b34341ccc3"
},
"time": 1589558058070,
"keyId": "7e4d0c6a708866e7"
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqAEARMKAAYFAl6+uyoACgkQfk0ManCIZuc0+QIHdWC0dp7GRRFu3Hgk9tnl
FJnPwM6Y2tTdq7AkpVTTAb3RTFadA8dRmLfajxgHxmDf5yUv9M2M6sa1eTSG
8kJtOlwCB096dXOKsH1IOGQMCY+/xM2081FqbMTvWgN81xrxMoxftQn8z6VC
2nA2Rmt1VStppFVCCUXaq6Y4sFGHQF/yq5oi
=vqUQ
-----END PGP SIGNATURE-----
```
## Plugin signatures
When Grafana starts, it discovers plugins to load. For each discovered plugin it verifies the authenticity of it, and then decides whether to load it or not based on the state of the plugin signature:
| Plugin signature | Description |
| ---------------- | ----------- |
| internal | Core plugin built into Grafana. |
| invalid | Digital signature of _MANIFEST.txt_ file is not valid. |
| modified | Manifest plugin id or version have been changed or files checksums doesn't match. |
| unsigned | Plugin don't have a _MANIFEST.txt_ file. |
| valid | If any of the above descriptions is false. |
The plugin signature state can be inspected for each plugin in the plugins listing page (Configuration -> Plugins).
## Backend plugins
If a [backend plugin]({{< relref "../developers/plugins/backend/_index.md" >}}) is not signed, then Grafana will not load or start it. If you try to load a backend plugin with an invalid signature, then Grafana writes an error message to the server log:
```bash
EROR[06-01|16:45:59] Failed to load plugin error=plugin <plugin id> is unsigned
```
> **Note:** All Grafana Labs authored backend plugins, including Enterprise plugins, are signed.
## Allow unsigned plugins
While you can allow unsigned plugins using a configuration setting, we strongly advise you not to. For more information on how to allow unsigned backend plugin, refer to [Configuration]({{< relref "../administration/configuration.md#allow-loading-unsigned-plugins" >}}). Allowing unsigned plugins will not skip verifying the authenticity of a plugin if plugin has a _MANIFEST.txt_ file.
If you run an unsigned backend plugin, then Grafana writes a warning message to the server log:
```bash
WARN[06-01|16:45:59] Running an unsigned backend plugin pluginID=<plugin id>
```
If you're developing plugins and run Grafana from source, the development mode is enabled by default and also allow you to run unsigned backend plugins.
+++
title = "Plugin signatures"
type = "docs"
aliases = ["/docs/plugins/plugin-signature-verification"]
+++
# Plugin signatures
Plugin signature verification (signing) is a security measure to make sure plugins haven't been tampered with. Upon loading, Grafana checks to see if a plugin is signed or unsigned when inspecting and verifying its digital signature.
At startup, Grafana verifies the signatures of every plugin in the plugin directory. You can see the result of this verification for each plugin by navigating to **Configuration** -> **Plugins**.
> **Note:** If you're a plugin developer and want to know how to sign your plugin, refer to [Sign a plugin]({{< relref "../developers/plugins/sign-a-plugin.md" >}}).
| Signature status | Description |
| ---------------- | ----------- |
| Core | Core plugin built into Grafana. |
| Invalid signature | The plugin has a invalid signature. |
| Modified signature | The plugin has changed since it was signed. This may indicate malicious intent. |
| Unsigned | The plugin is not signed. |
| Signed | The plugin signature was successfully verified. |
## Plugin signature levels
All plugins is signed under a _signature level_. The signature level determines how the plugin can be distributed.
|**Plugin Level**|**Description**|
|---|---|
|Private|<p>Private plugins are for use on your own Grafana. They may not be distributed to the Grafana community, and are not published in the Grafana catalog.</p>|
|Community|<p>Community plugins have dependent technologies that are open source and not for profit.</p><p>Community plugins are published in the official Grafana catalog, and are available to the Grafana community.</p>|
|Commercial|<p>Commercial plugins have dependent technologies that are closed source or commercially backed.</p><p>Commercial Plugins are published on the official Grafana catalog, and are available to the Grafana community.</p>|
## Backend plugins
If a [backend plugin]({{< relref "../developers/plugins/backend/_index.md" >}}) is unsigned, then Grafana won't load or start it. If you try to load a backend plugin with an missing or invalid signature, then Grafana writes an error message to the server log:
```bash
EROR[06-01|16:45:59] Failed to load plugin error=plugin <plugin id> is unsigned
```
> **Note:** All Grafana Labs authored backend plugins, including Enterprise plugins, are signed.
## Allow unsigned plugins
We strongly recommend that you don't run unsigned plugins in your Grafana installation. If you're aware of the risks and you still want to load an unsigned plugin, refer to [Configuration]({{< relref "../administration/configuration.md#allow-loading-unsigned-plugins" >}}).
If you've allowed loading of an unsigned backend plugin, then Grafana writes a warning message to the server log:
```bash
WARN[06-01|16:45:59] Running an unsigned backend plugin pluginID=<plugin id>
```
> **Note:** If you're developing a plugin, then you can enable development mode to allow all unsigned plugins.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment