MM-16368 - Plugin Signing (#13017)
* [MM-18757] POST handler for `/plugins/marketplace` (#12372) * Implement installMarketplacePlugin * Add InstallMarketplacePlugin endpoint * Fix go.mod * merge with master * Fix go.mod * Fix plugin tests * Move get plugin to marketplace client * Fix stylistic concerns * Add trailing newline to the go.mod * [MM-16586] Add plugin signature settings (#12390) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Update development public key * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Remove init_public_keys string * GolangCI * Closing file handlers * Fixed test that was installing nps plugin * [MM-19798] Implement plugin signature verification (#12768) * MM-17149 - Extend config.json for marketplace settings (#11933) * MM-17149 - Extend config.json for marketplace settings * Renamed MarketplaceUrl, tracking default marketplace url * Added EnableMarketplace to the client config * Revert "Added EnableMarketplace to the client config" This reverts commit 0f982c4c661c2cd9bb96264e9a01a2363c40d9c5. * MM-17149 - Added EnableMarketplace to the client config (#11958) * Added EnableMarketplace to the client config * Moved EnableMarketplace setting out of limited client configuration * Add public key settings to the config.json * Rename PublicKeys to SignaturePublicKeyFiles * Change filepath.Split to Base * Remove additional prints * Force extention of a public key file * Remove config validation * Remove error on delete * Remove config cloning * Add error messages * Add plugin public key tests * Rename extension to PluginSignaturePublicKeyFileExtention * Remove EnforceVerification * Change []*PublicKeyDescription to []string * Change .asc extension to .plugin.asc * Change ordering of public methods * Change plugin key commands * Update examples in the plugin key commands * Remove forcing extention * Add verify signature in settings * Fix tabbing * Fix naming * Remove unused text * Remove unused text * Update command examples * Fix unit tests * Change errors.New to errors.Wrap * Fix verbose flag * Change .asc to .gpg * Fix } * Change AddPublicKey signature * Change public.key extension * Add plugin public key command tests * Update en.json * Bootstrap the public keys * Update en.json * Fix en.json * Fix en.json * Bootstrap hard-coded public key * Remove unused texts in en.json * Change file to name * Add license header * Implement plugin signature verification * Remove benburker openpgp * Update en.json * Update development public key * Add support of multiple signatures in filestore * Update en.json * Run go mod vendor * Fix style * Remove writeFile method * Remove .plugin.asc extension * Rename publiKey to mattermostPublicKey * Verify plugin with mattermost public key * Remove init_public_keys string * Add InstallPluginWithSignature method and Refactor * Add signature verification on claster notification * Remove armored signature headers * Add error strings * Fix en.json * Change signatureStorePath * Implement minor fixes * Refactor plugin install methods * Add installPlugin method to uploadPlugin * Update en.json * Refactor installPlugin * Limit number of signatures * Close signatures * Fix helper function * Fix fromReadCloseSeekerToReadSeeker * Cleaned up ReadCloseSeeker for signatures * Remove signature truncation on FS * GolangCI * Add tests for armored signatures and plugin uploads * Fix nil slice issue * Fix TestPluginSync * Fixed tests * Return io.ReadSeeker from downloadFromUrl * Add log for the found plugins in the file store * Remove logging plugin detection info * [MM-20134] Consume and store single-signature for each plugin (#13081) * Consume and store single-signature for each plugin * Fix en.json * Remove saveSignature method * Remove public key hash * PR Feedback * refactored config * PR feedback
Этот коммит содержится в:
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/plugin"
|
||||
"github.com/mattermost/mattermost-server/services/filesstore"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
@@ -61,16 +62,38 @@ const fileStorePluginFolder = "plugins"
|
||||
func (a *App) InstallPluginFromData(data model.PluginEventData) {
|
||||
mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id))
|
||||
|
||||
fileStorePath := a.getBundleStorePath(data.Id)
|
||||
reader, appErr := a.FileReader(fileStorePath)
|
||||
pluginSignaturePathMap, appErr := a.getPluginsFromFolder()
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to open plugin bundle from filestore.", mlog.String("path", fileStorePath), mlog.Err(appErr))
|
||||
mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
plugin, ok := pluginSignaturePathMap[data.Id]
|
||||
if !ok {
|
||||
mlog.Error("Failed to get plugin signature from filestore. Can't install plugin from data.", mlog.String("plugin id", data.Id))
|
||||
return
|
||||
}
|
||||
|
||||
reader, appErr := a.FileReader(plugin.path)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
manifest, appErr := a.installPluginLocally(reader, installPluginLocallyAlways)
|
||||
var signature filesstore.ReadCloseSeeker
|
||||
if *a.Config().PluginSettings.RequirePluginSignature {
|
||||
signature, appErr = a.FileReader(plugin.signaturePath)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
defer signature.Close()
|
||||
}
|
||||
|
||||
manifest, appErr := a.installPluginLocally(reader, signature, installPluginLocallyAlways)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to unpack plugin from filestore", mlog.Err(appErr), mlog.String("path", fileStorePath))
|
||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.notifyPluginEnabled(manifest); err != nil {
|
||||
@@ -94,6 +117,11 @@ func (a *App) RemovePluginFromData(data model.PluginEventData) {
|
||||
}
|
||||
}
|
||||
|
||||
// InstallPluginWithSignature verifies and installs plugin.
|
||||
func (a *App) InstallPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) {
|
||||
return a.installPlugin(pluginFile, signature, installPluginLocallyAlways)
|
||||
}
|
||||
|
||||
// InstallPlugin unpacks and installs a plugin but does not enable or activate it.
|
||||
func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) {
|
||||
installationStrategy := installPluginLocallyOnlyIfNew
|
||||
@@ -101,14 +129,24 @@ func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Mani
|
||||
installationStrategy = installPluginLocallyAlways
|
||||
}
|
||||
|
||||
manifest, appErr := a.installPluginLocally(pluginFile, installationStrategy)
|
||||
return a.installPlugin(pluginFile, nil, installationStrategy)
|
||||
}
|
||||
|
||||
func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||
manifest, appErr := a.installPluginLocally(pluginFile, signature, installationStrategy)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if signature != nil {
|
||||
signature.Seek(0, 0)
|
||||
if _, appErr = a.WriteFile(signature, a.getSignatureStorePath(manifest.Id)); appErr != nil {
|
||||
return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// Store bundle in the file store to allow access from other servers.
|
||||
pluginFile.Seek(0, 0)
|
||||
|
||||
if _, appErr := a.WriteFile(pluginFile, a.getBundleStorePath(manifest.Id)); appErr != nil {
|
||||
return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
@@ -142,11 +180,17 @@ const (
|
||||
installPluginLocallyAlways
|
||||
)
|
||||
|
||||
func (a *App) installPluginLocally(pluginFile io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||
func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||
pluginsEnvironment := a.GetPluginsEnvironment()
|
||||
if pluginsEnvironment == nil {
|
||||
return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
// verify signature
|
||||
if signature != nil {
|
||||
if err := a.VerifyPlugin(pluginFile, signature); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "plugintmp")
|
||||
if err != nil {
|
||||
@@ -154,6 +198,7 @@ func (a *App) installPluginLocally(pluginFile io.ReadSeeker, installationStrateg
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
pluginFile.Seek(0, 0)
|
||||
if err = utils.ExtractTarGz(pluginFile, tmpDir); err != nil {
|
||||
return nil, model.NewAppError("installPluginLocally", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
@@ -282,9 +327,12 @@ func (a *App) removePlugin(id string) *model.AppError {
|
||||
if !bundleExist {
|
||||
return nil
|
||||
}
|
||||
if err := a.RemoveFile(storePluginFileName); err != nil {
|
||||
if err = a.RemoveFile(storePluginFileName); err != nil {
|
||||
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
if err = a.removeSignature(id); err != nil {
|
||||
mlog.Error("Can't remove signature", mlog.Err(err))
|
||||
}
|
||||
|
||||
a.notifyClusterPluginEvent(
|
||||
model.CLUSTER_EVENT_REMOVE_PLUGIN,
|
||||
@@ -336,6 +384,26 @@ func (a *App) removePluginLocally(id string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) removeSignature(pluginId string) *model.AppError {
|
||||
filePath := a.getSignatureStorePath(pluginId)
|
||||
exists, err := a.FileExists(filePath)
|
||||
if err != nil {
|
||||
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
if !exists {
|
||||
mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginId))
|
||||
return nil
|
||||
}
|
||||
if err = a.RemoveFile(filePath); err != nil {
|
||||
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) getBundleStorePath(id string) string {
|
||||
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz", id))
|
||||
}
|
||||
|
||||
func (a *App) getSignatureStorePath(id string) string {
|
||||
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.sig", id))
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user