diff --git a/client/command/armory/armory.go b/client/command/armory/armory.go index 14d55069f4..12d7a9f0e5 100644 --- a/client/command/armory/armory.go +++ b/client/command/armory/armory.go @@ -87,7 +87,7 @@ type pkgCacheEntry struct { Pkg ArmoryPackage Sig minisign.Signature Alias *alias.AliasManifest - Extension *extensions.ExtensionManifest + Extension extensions.MultiManifest LastErr error } @@ -141,7 +141,7 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []stri if cacheEntry.Pkg.IsAlias { aliases = append(aliases, cacheEntry.Alias) } else { - exts = append(exts, cacheEntry.Extension) + exts = append(exts, cacheEntry.Extension...) } } return true @@ -183,7 +183,7 @@ func packagesInCache() ([]*alias.AliasManifest, []*extensions.ExtensionManifest) if cacheEntry.Pkg.IsAlias { aliases = append(aliases, cacheEntry.Alias) } else { - exts = append(exts, cacheEntry.Extension) + exts = append(exts, cacheEntry.Extension...) } } return true @@ -533,7 +533,7 @@ func fetchPackageSignature(wg *sync.WaitGroup, armoryConfig *assets.ArmoryConfig if armoryPkg.IsAlias { pkgCacheEntry.Alias, err = alias.ParseAliasManifest(manifestData) } else { - pkgCacheEntry.Extension, err = extensions.ParseExtensionManifest(manifestData) + pkgCacheEntry.Extension, err = extensions.ParseMultiManifest(manifestData) } if err != nil { pkgCacheEntry.LastErr = fmt.Errorf("failed to parse trusted manifest in pkg signature: %s", err) diff --git a/client/command/armory/install.go b/client/command/armory/install.go index 730b40ed34..5ba332ce1b 100644 --- a/client/command/armory/install.go +++ b/client/command/armory/install.go @@ -222,24 +222,25 @@ func resolveExtensionPackageDependencies(name string, deps map[string]struct{}, if entry == nil { return } + for _, multiExt := range entry.Extension { + if multiExt.DependsOn == "" { + continue // Avoid adding empty dependency + } - if entry.Extension.DependsOn == "" { - return // Avoid adding empty dependency - } - - if entry.Extension.DependsOn == name { - return // Avoid infinite loop of something that depends on itself - } - // We also need to look out for circular dependencies, so if we've already - // seen this dependency, we stop resolving - if _, ok := deps[entry.Extension.DependsOn]; ok { - return // Already resolved - } - if maxDepDepth < len(deps) { - return + if multiExt.DependsOn == name { + continue // Avoid infinite loop of something that depends on itself + } + // We also need to look out for circular dependencies, so if we've already + // seen this dependency, we stop resolving + if _, ok := deps[multiExt.DependsOn]; ok { + continue // Already resolved + } + if maxDepDepth < len(deps) { + continue + } + deps[multiExt.DependsOn] = struct{}{} + resolveExtensionPackageDependencies(multiExt.DependsOn, deps, clientConfig, con) } - deps[entry.Extension.DependsOn] = struct{}{} - resolveExtensionPackageDependencies(entry.Extension.DependsOn, deps, clientConfig, con) } func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, con *console.SliverConsoleClient) error { @@ -301,7 +302,7 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c if installPath == nil { return errors.New("failed to install extension") } - extCmd, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName)) + manyfest, err := extensions.LoadExtensionManifest(filepath.Join(*installPath, extensions.ManifestFileName)) if err != nil { return err } @@ -311,6 +312,8 @@ func installExtensionPackageByName(name string, clientConfig ArmoryHTTPConfig, c // if extensions.CmdExists(extCmd.Name, sliverMenu.Command) { // con.App.Commands().Remove(extCmd.Name) // } - extensions.ExtensionRegisterCommand(extCmd, sliverMenu.Command, con) + for _, extCmd := range manyfest { + extensions.ExtensionRegisterCommand(extCmd, sliverMenu.Command, con) + } return nil } diff --git a/client/command/armory/update.go b/client/command/armory/update.go index 76676355e9..8114ea680c 100644 --- a/client/command/armory/update.go +++ b/client/command/armory/update.go @@ -97,15 +97,17 @@ func checkForExtensionUpdates(clientConfig ArmoryHTTPConfig, con *console.Sliver if err != nil { continue } - localManifest, err := extensions.ParseExtensionManifest(data) + manyfest, err := extensions.ParseMultiManifest(data) if err != nil { continue } - for _, latestExt := range cachedExtensions { - // Right now we don't try to enforce any kind of versioning, it is assumed if the version from - // the armory differs at all from the local version, the extension is out of date. - if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version { - results = append(results, localManifest.CommandName) + for _, localManifest := range manyfest { + for _, latestExt := range cachedExtensions { + // Right now we don't try to enforce any kind of versioning, it is assumed if the version from + // the armory differs at all from the local version, the extension is out of date. + if latestExt.CommandName == localManifest.CommandName && latestExt.Version != localManifest.Version { + results = append(results, localManifest.CommandName) + } } } } diff --git a/client/command/extensions/extensions_test.go b/client/command/extensions/extensions_test.go index 56325b47bd..2ab60bac7f 100644 --- a/client/command/extensions/extensions_test.go +++ b/client/command/extensions/extensions_test.go @@ -53,75 +53,6 @@ const ( } ] }` -) - -func TestParseExtensionManifest(t *testing.T) { - extManifest, err := ParseExtensionManifest([]byte(sample1)) - if err != nil { - t.Fatalf("Error parsing extension manifest: %s", err) - } - if extManifest.Name != "test1" { - t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name) - } - if extManifest.CommandName != "test1" { - t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName) - } - if extManifest.Version != "1.0.0" { - t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version) - } - if extManifest.ExtensionAuthor != "test" { - t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor) - } - if extManifest.OriginalAuthor != "test" { - t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor) - } - if extManifest.RepoURL != "https://example.com/" { - t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL) - } - if extManifest.Help != "some help" { - t.Errorf("Expected help 'some help', got '%s'", extManifest.Help) - } - if len(extManifest.Files) != 1 { - t.Errorf("Expected 1 file, got %d", len(extManifest.Files)) - } - if extManifest.Files[0].OS != "windows" { - t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS) - } - if extManifest.Files[0].Arch != "amd64" { - t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch) - } - if extManifest.Files[0].Path != "/foo/test1.dll" { - t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path) - } - - extManifest2, err := ParseExtensionManifest([]byte(sample2)) - if err != nil { - t.Fatalf("Error parsing extension manifest (2): %s", err) - } - if extManifest2.Name != "test2" { - t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name) - } - if extManifest2.CommandName != "test2" { - t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName) - } - if extManifest2.Help != "some help" { - t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help) - } - if len(extManifest2.Files) != 1 { - t.Errorf("Expected 1 file, got %d", len(extManifest2.Files)) - } - if extManifest2.Files[0].OS != "windows" { - t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS) - } - if extManifest2.Files[0].Arch != "amd64" { - t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch) - } - if extManifest2.Files[0].Path != "/foo/test1.dll" { - t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path) - } -} - -const ( sample3 = `{ "name": "test3", "command_name": "test3", @@ -138,10 +69,209 @@ const ( } ] }` + sample4 = `[{ + "name": "testmultisingle", + "command_name": "testmultisingle", + "version": "1.0.0", + "extension_author": "test", + "original_author": "test", + "repo_url": "https://example.com/", + "help": "some help", + "files": [ + { + "os": "windows", + "arch": "amd64", + "path": "foo/test1.dll" + } + ] + }]` + sample5 = `[{ + "name": "testmulti", + "command_name": "testmulti", + "version": "1.0.0", + "extension_author": "test", + "original_author": "test", + "repo_url": "https://example.com/", + "help": "some help", + "files": [ + { + "os": "windows", + "arch": "amd64", + "path": "foo/test1.dll" + } + ] + },{ + "name": "testmulti2", + "command_name": "testmulti2", + "version": "1.0.0", + "extension_author": "test", + "original_author": "test", + "repo_url": "https://example.com/", + "help": "some help", + "files": [ + { + "os": "windows", + "arch": "amd64", + "path": "foo/test1.dll" + } + ] + }]` ) +func TestParseExtensionManifest(t *testing.T) { + mextManifest, err := ParseMultiManifest([]byte(sample1)) + for _, extManifest := range mextManifest { //should only be a single manfiest here, so should pass + if err != nil { + t.Fatalf("Error parsing extension manifest: %s", err) + } + if extManifest.Name != "test1" { + t.Errorf("Expected extension name 'test1', got '%s'", extManifest.Name) + } + if extManifest.CommandName != "test1" { + t.Errorf("Expected extension command name 'test1', got '%s'", extManifest.CommandName) + } + if extManifest.Version != "1.0.0" { + t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version) + } + if extManifest.ExtensionAuthor != "test" { + t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor) + } + if extManifest.OriginalAuthor != "test" { + t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor) + } + if extManifest.RepoURL != "https://example.com/" { + t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL) + } + if extManifest.Help != "some help" { + t.Errorf("Expected help 'some help', got '%s'", extManifest.Help) + } + if len(extManifest.Files) != 1 { + t.Errorf("Expected 1 file, got %d", len(extManifest.Files)) + } + if extManifest.Files[0].OS != "windows" { + t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS) + } + if extManifest.Files[0].Arch != "amd64" { + t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch) + } + if extManifest.Files[0].Path != "/foo/test1.dll" { + t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path) + } + } + + mextManifest2, err := ParseMultiManifest([]byte(sample2)) //should only be a single manfiest here, so should pass + for _, extManifest2 := range mextManifest2 { + if err != nil { + t.Fatalf("Error parsing extension manifest (2): %s", err) + } + if extManifest2.Name != "test2" { + t.Errorf("Expected extension name 'test2', got '%s'", extManifest2.Name) + } + if extManifest2.CommandName != "test2" { + t.Errorf("Expected extension command name 'test2', got '%s'", extManifest2.CommandName) + } + if extManifest2.Help != "some help" { + t.Errorf("Expected help 'some help', got '%s'", extManifest2.Help) + } + if len(extManifest2.Files) != 1 { + t.Errorf("Expected 1 file, got %d", len(extManifest2.Files)) + } + if extManifest2.Files[0].OS != "windows" { + t.Errorf("Expected OS 'windows', got '%s'", extManifest2.Files[0].OS) + } + if extManifest2.Files[0].Arch != "amd64" { + t.Errorf("Expected Arch 'amd64', got '%s'", extManifest2.Files[0].Arch) + } + if extManifest2.Files[0].Path != "/foo/test1.dll" { + t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest2.Files[0].Path) + } + } +} + +func TestParseMultipleManifests(t *testing.T) { + mextManifest, err := ParseMultiManifest([]byte(sample4)) //single manifest in a slice + for _, extManifest := range mextManifest { + if err != nil { + t.Fatalf("Error parsing extension manifest: %s", err) + } + if extManifest.Name != "testmultisingle" { + t.Errorf("Expected extension name 'testmultisingle', got '%s'", extManifest.Name) + } + if extManifest.CommandName != "testmultisingle" { + t.Errorf("Expected extension command name 'testmultisingle', got '%s'", extManifest.CommandName) + } + if extManifest.Version != "1.0.0" { + t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version) + } + if extManifest.ExtensionAuthor != "test" { + t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor) + } + if extManifest.OriginalAuthor != "test" { + t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor) + } + if extManifest.RepoURL != "https://example.com/" { + t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL) + } + if extManifest.Help != "some help" { + t.Errorf("Expected help 'some help', got '%s'", extManifest.Help) + } + if len(extManifest.Files) != 1 { + t.Errorf("Expected 1 file, got %d", len(extManifest.Files)) + } + if extManifest.Files[0].OS != "windows" { + t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS) + } + if extManifest.Files[0].Arch != "amd64" { + t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch) + } + if extManifest.Files[0].Path != "/foo/test1.dll" { + t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path) + } + } + + mextManifest2, err := ParseMultiManifest([]byte(sample5)) //single manifest in a slice + for _, extManifest := range mextManifest2 { + if err != nil { + t.Fatalf("Error parsing extension manifest: %s", err) + } + if extManifest.Name != "testmulti" && extManifest.Name != "testmulti2" { + t.Errorf("Expected extension name 'testmulti or testmulti2', got '%s'", extManifest.Name) + } + if extManifest.CommandName != "testmulti" && extManifest.CommandName != "testmulti2" { + t.Errorf("Expected extension command name 'testmulti or testmulti2', got '%s'", extManifest.CommandName) + } + if extManifest.Version != "1.0.0" { + t.Errorf("Expected extension version '1.0.0', got '%s'", extManifest.Version) + } + if extManifest.ExtensionAuthor != "test" { + t.Errorf("Expected extension author 'test', got '%s'", extManifest.ExtensionAuthor) + } + if extManifest.OriginalAuthor != "test" { + t.Errorf("Expected original author 'test', got '%s'", extManifest.OriginalAuthor) + } + if extManifest.RepoURL != "https://example.com/" { + t.Errorf("Expected repo URL 'https://example.com/', got '%s'", extManifest.RepoURL) + } + if extManifest.Help != "some help" { + t.Errorf("Expected help 'some help', got '%s'", extManifest.Help) + } + if len(extManifest.Files) != 1 { + t.Errorf("Expected 1 file, got %d", len(extManifest.Files)) + } + if extManifest.Files[0].OS != "windows" { + t.Errorf("Expected OS 'windows', got '%s'", extManifest.Files[0].OS) + } + if extManifest.Files[0].Arch != "amd64" { + t.Errorf("Expected Arch 'amd64', got '%s'", extManifest.Files[0].Arch) + } + if extManifest.Files[0].Path != "/foo/test1.dll" { + t.Errorf("Expected path '/foo/test1.dll', got '%s'", extManifest.Files[0].Path) + } + } +} + func TestParseExtensionManifestErrors(t *testing.T) { - sample3, err := ParseExtensionManifest([]byte(sample3)) + sample3, err := parseExtensionManifest([]byte(sample3)) if err != nil { t.Fatalf("Failed to parse initial sample3: %s", err) } @@ -149,7 +279,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { missingName := (*sample3) missingName.Name = "" data, _ := json.Marshal(missingName) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing name error, got none") } @@ -157,7 +287,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { missingCmdName := (*sample3) missingCmdName.CommandName = "" data, _ = json.Marshal(missingCmdName) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing command name error, got none") } @@ -165,7 +295,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { missingHelp := (*sample3) missingHelp.Help = "" data, _ = json.Marshal(missingHelp) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing help error, got none") } @@ -173,7 +303,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { missingFiles := (*sample3) missingFiles.Files = []*extensionFile{} data, _ = json.Marshal(missingFiles) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing files error, got none") } @@ -187,7 +317,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { }, } data, _ = json.Marshal(missingFileOS) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing files.os error, got none") } @@ -201,7 +331,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { }, } data, _ = json.Marshal(missingFileArch) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing files.arch error, got none") } @@ -215,7 +345,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { }, } data, _ = json.Marshal(missingFilePath) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing files.path error, got none") } @@ -236,7 +366,7 @@ func TestParseExtensionManifestErrors(t *testing.T) { }, } data, _ = json.Marshal(missingFilePath2) - _, err = ParseExtensionManifest(data) + _, err = ParseMultiManifest(data) if err == nil { t.Fatalf("Expected missing files.path error, got none") } diff --git a/client/command/extensions/install.go b/client/command/extensions/install.go index 66e240f549..d11143b504 100644 --- a/client/command/extensions/install.go +++ b/client/command/extensions/install.go @@ -54,45 +54,53 @@ func installFromDir(extLocalPath string, con *console.SliverConsoleClient) { con.PrintErrorf("Error reading %s: %s", ManifestFileName, err) return } - manifest, err := ParseExtensionManifest(manifestData) + manyfest, err := ParseMultiManifest(manifestData) if err != nil { con.PrintErrorf("Error parsing %s: %s", ManifestFileName, err) return } - installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName)) - if _, err := os.Stat(installPath); !os.IsNotExist(err) { - con.PrintInfof("Extension '%s' already exists", manifest.CommandName) - confirm := false - prompt := &survey.Confirm{Message: "Overwrite current install?"} - survey.AskOne(prompt, &confirm) - if !confirm { - return + for _, manifest := range manyfest { + installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName)) + if _, err := os.Stat(installPath); !os.IsNotExist(err) { + con.PrintInfof("Extension '%s' already exists", manifest.CommandName) + confirm := false + prompt := &survey.Confirm{Message: "Overwrite current install?"} + survey.AskOne(prompt, &confirm) + if !confirm { + return + } + forceRemoveAll(installPath) } - forceRemoveAll(installPath) - } - con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version) - err = os.MkdirAll(installPath, 0o700) - if err != nil { - con.PrintErrorf("Error creating extension directory: %s\n", err) - return - } - err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600) - if err != nil { - con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err) - forceRemoveAll(installPath) - return - } + con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version) + err = os.MkdirAll(installPath, 0o700) + if err != nil { + con.PrintErrorf("Error creating extension directory: %s\n", err) + return + } + err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600) + if err != nil { + con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err) + forceRemoveAll(installPath) + return + } - for _, manifestFile := range manifest.Files { - if manifestFile.Path != "" { - src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path)) - dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path)) - err := util.CopyFile(src, dst) - if err != nil { - con.PrintErrorf("Error copying file '%s' -> '%s': %s\n", src, dst, err) - forceRemoveAll(installPath) - return + for _, manifestFile := range manifest.Files { + if manifestFile.Path != "" { + src := filepath.Join(extLocalPath, util.ResolvePath(manifestFile.Path)) + dst := filepath.Join(installPath, util.ResolvePath(manifestFile.Path)) + err = os.MkdirAll(filepath.Dir(dst), 0o700) //required for extensions with multiple dirs between the .o file and the manifest + if err != nil { + con.PrintErrorf("\nError creating extension directory: %s\n", err) + forceRemoveAll(installPath) + return + } + err := util.CopyFile(src, dst) + if err != nil { + con.PrintErrorf("Error copying file '%s' -> '%s': %s\n", src, dst, err) + forceRemoveAll(installPath) + return + } } } } @@ -105,49 +113,52 @@ func InstallFromFilePath(extLocalPath string, autoOverwrite bool, con *console.S con.PrintErrorf("Failed to read %s from '%s': %s\n", ManifestFileName, extLocalPath, err) return nil } - manifest, err := ParseExtensionManifest(manifestData) + manyfest, err := ParseMultiManifest(manifestData) if err != nil { con.PrintErrorf("Failed to parse %s: %s\n", ManifestFileName, err) return nil } - installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName)) - if _, err := os.Stat(installPath); !os.IsNotExist(err) { - if !autoOverwrite { - con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName) - confirm := false - prompt := &survey.Confirm{Message: "Overwrite current install?"} - survey.AskOne(prompt, &confirm) - if !confirm { - return nil + for _, manifest := range manyfest { + installPath := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manifest.CommandName)) + if _, err := os.Stat(installPath); !os.IsNotExist(err) { + if !autoOverwrite { + con.PrintInfof("Extension '%s' already exists\n", manifest.CommandName) + confirm := false + prompt := &survey.Confirm{Message: "Overwrite current install?"} + survey.AskOne(prompt, &confirm) + if !confirm { + return nil + } } + forceRemoveAll(installPath) } - forceRemoveAll(installPath) - } - con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version) - err = os.MkdirAll(installPath, 0o700) - if err != nil { - con.PrintErrorf("Failed to create extension directory: %s\n", err) - return nil - } - err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600) - if err != nil { - con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err) - forceRemoveAll(installPath) - return nil - } - for _, manifestFile := range manifest.Files { - if manifestFile.Path != "" { - err = installArtifact(extLocalPath, installPath, manifestFile.Path, con) - if err != nil { - con.PrintErrorf("Failed to install file: %s\n", err) - forceRemoveAll(installPath) - return nil + con.PrintInfof("Installing extension '%s' (%s) ... ", manifest.CommandName, manifest.Version) + err = os.MkdirAll(installPath, 0o700) + if err != nil { + con.PrintErrorf("Failed to create extension directory: %s\n", err) + return nil + } + err = os.WriteFile(filepath.Join(installPath, ManifestFileName), manifestData, 0o600) + if err != nil { + con.PrintErrorf("Failed to write %s: %s\n", ManifestFileName, err) + forceRemoveAll(installPath) + return nil + } + for _, manifestFile := range manifest.Files { + if manifestFile.Path != "" { + err = installArtifact(extLocalPath, installPath, manifestFile.Path, con) + if err != nil { + con.PrintErrorf("Failed to install file: %s\n", err) + forceRemoveAll(installPath) + return nil + } } } } con.Printf("done!\n") - return &installPath + first := filepath.Join(assets.GetExtensionsDir(), filepath.Base(manyfest[0].CommandName)) + return &first //this is probably a bad idea - but the only thing that seems to ref this is something that wants all the manifests, and all manifest data is stored in each installed dir, so... } func installArtifact(extGzFilePath string, installPath string, artifactPath string, con *console.SliverConsoleClient) error { diff --git a/client/command/extensions/load.go b/client/command/extensions/load.go index 72b7d2fd29..a296fbb0f6 100644 --- a/client/command/extensions/load.go +++ b/client/command/extensions/load.go @@ -30,6 +30,7 @@ import ( "strconv" "strings" + "github.com/AlecAivazis/survey/v2" appConsole "github.com/reeflective/console" "github.com/rsteube/carapace" "github.com/spf13/cobra" @@ -73,6 +74,8 @@ type ExtensionManifest struct { RootPath string `json:"-"` } +type MultiManifest []*ExtensionManifest + type extensionFile struct { OS string `json:"os"` Arch string `json:"arch"` @@ -109,69 +112,104 @@ func (e *ExtensionManifest) getFileForTarget(cmdName string, targetOS string, ta func ExtensionLoadCmd(cmd *cobra.Command, con *console.SliverConsoleClient, args []string) { dirPath := args[0] // dirPath := ctx.Args.String("dir-path") - extCmd, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName)) + manyfest, err := LoadExtensionManifest(filepath.Join(dirPath, ManifestFileName)) if err != nil { return } // do not add if the command already exists sliverMenu := con.App.Menu("implant") - if CmdExists(extCmd.CommandName, sliverMenu.Command) { - con.PrintErrorf("%s command already exists\n", extCmd.CommandName) - return + for _, extCmd := range manyfest { + if CmdExists(extCmd.CommandName, sliverMenu.Command) { + con.PrintErrorf("%s command already exists\n", extCmd.CommandName) + confirm := false + prompt := &survey.Confirm{Message: "Overwrite current command?"} + survey.AskOne(prompt, &confirm) + if !confirm { + return + } + } + ExtensionRegisterCommand(extCmd, cmd.Root(), con) + con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help) } - ExtensionRegisterCommand(extCmd, cmd.Root(), con) - con.PrintInfof("Added %s command: %s\n", extCmd.CommandName, extCmd.Help) } // LoadExtensionManifest - Parse extension files -func LoadExtensionManifest(manifestPath string) (*ExtensionManifest, error) { +func LoadExtensionManifest(manifestPath string) (MultiManifest, error) { data, err := os.ReadFile(manifestPath) if err != nil { return nil, err } - extManifest, err := ParseExtensionManifest(data) + manyfest, err := ParseMultiManifest(data) if err != nil { return nil, err } - extManifest.RootPath = filepath.Dir(manifestPath) - loadedExtensions[extManifest.CommandName] = extManifest - return extManifest, nil + for _, extManifest := range manyfest { + extManifest.RootPath = filepath.Dir(manifestPath) + loadedExtensions[extManifest.CommandName] = extManifest + } + return manyfest, nil +} + +// ParseMultiManifest - Parse multiple extension manifests from buffer +func ParseMultiManifest(data []byte) (MultiManifest, error) { + var man MultiManifest + err := json.Unmarshal(data, &man) + if err != nil { + //maybe it's a single manifest + manifest, err := parseExtensionManifest(data) + if err != nil { + //it's not lol + return nil, err + } + man = MultiManifest{manifest} + } + for _, ext := range man { + err := validManifest(ext) + if err != nil { + return nil, err + } + } + return man, nil } -// ParseExtensionManifest - Parse extension manifest from buffer -func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) { +// parseExtensionManifest - Parse extension manifest from buffer (legacy, only parses one) +func parseExtensionManifest(data []byte) (*ExtensionManifest, error) { extManifest := &ExtensionManifest{} err := json.Unmarshal(data, &extManifest) if err != nil { return nil, err } + return extManifest, validManifest(extManifest) +} + +func validManifest(extManifest *ExtensionManifest) error { if extManifest.Name == "" { - return nil, errors.New("missing `name` field in extension manifest") + return errors.New("missing `name` field in extension manifest") } if extManifest.CommandName == "" { - return nil, errors.New("missing `command_name` field in extension manifest") + return errors.New("missing `command_name` field in extension manifest") } if len(extManifest.Files) == 0 { - return nil, errors.New("missing `files` field in extension manifest") + return errors.New("missing `files` field in extension manifest") } for _, extFiles := range extManifest.Files { if extFiles.OS == "" { - return nil, errors.New("missing `files.os` field in extension manifest") + return errors.New("missing `files.os` field in extension manifest") } if extFiles.Arch == "" { - return nil, errors.New("missing `files.arch` field in extension manifest") + return errors.New("missing `files.arch` field in extension manifest") } extFiles.Path = util.ResolvePath(extFiles.Path) if extFiles.Path == "" || extFiles.Path == "/" { - return nil, errors.New("missing `files.path` field in extension manifest") + return errors.New("missing `files.path` field in extension manifest") } extFiles.OS = strings.ToLower(extFiles.OS) extFiles.Arch = strings.ToLower(extFiles.Arch) } if extManifest.Help == "" { - return nil, errors.New("missing `help` field in extension manifest") + return errors.New("missing `help` field in extension manifest") } - return extManifest, nil + return nil } // ExtensionRegisterCommand - Register a new extension command diff --git a/client/command/sliver.go b/client/command/sliver.go index e6a48235d0..11e36501ea 100644 --- a/client/command/sliver.go +++ b/client/command/sliver.go @@ -94,13 +94,15 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands { // Load Extensions extensionManifests := assets.GetInstalledExtensionManifests() for _, manifest := range extensionManifests { - ext, err := extensions.LoadExtensionManifest(manifest) + mext, err := extensions.LoadExtensionManifest(manifest) // Absorb error in case there's no extensions manifest if err != nil { con.PrintErrorf("Failed to load extension: %s", err) continue } - extensions.ExtensionRegisterCommand(ext, sliver, con) + for _, ext := range mext { + extensions.ExtensionRegisterCommand(ext, sliver, con) + } } // [ Reconfig ] ---------------------------------------------------------------