mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
cli: try to infer the bootstrap package name from the URL too (#11571)
#11570
This commit is contained in:
parent
4a1d45de17
commit
653bbec5f1
1
changes/11570-bp-url
Normal file
1
changes/11570-bp-url
Normal file
@ -0,0 +1 @@
|
||||
* MDM: try to infer the bootstrap package name from the URL on upload if a content-disposition header is not provided.
|
13
pkg/file/validation.go
Normal file
13
pkg/file/validation.go
Normal file
@ -0,0 +1,13 @@
|
||||
package file
|
||||
|
||||
import "strings"
|
||||
|
||||
var InvalidMacOSChars = []rune{':', '\\', '*', '?', '"', '<', '>', '|', 0}
|
||||
|
||||
func IsValidMacOSName(fileName string) bool {
|
||||
if fileName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.ContainsAny(fileName, string(InvalidMacOSChars))
|
||||
}
|
38
pkg/file/validation_test.go
Normal file
38
pkg/file/validation_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsValidMacOSName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
output bool
|
||||
}{
|
||||
{"valid", "filename.txt", true},
|
||||
{"spaces", "file name with spaces.txt", true},
|
||||
{"dashes", "file-name-with-dashes.txt", true},
|
||||
{"underscores", "file_underscored.txt", true},
|
||||
{"non-ASCII characters", "中文文件名.txt", true},
|
||||
|
||||
{"colon", "file:name.txt", false},
|
||||
{"backslash", "file\\name.txt", false},
|
||||
{"asterisk", "file*name.txt", false},
|
||||
{"question mark", "file?name.txt", false},
|
||||
{"double quote", "file\"name.txt", false},
|
||||
{"less than", "file<name.txt", false},
|
||||
{"greater than", "file>name.txt", false},
|
||||
{"pipe", "file|name.txt", false},
|
||||
{"null character", "file\x00name.txt", false},
|
||||
{"empty", "", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.output, IsValidMacOSName(tc.input))
|
||||
})
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/VividCortex/mysqlerr"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
@ -1662,6 +1663,12 @@ func (uploadBootstrapPackageRequest) DecodeRequest(ctx context.Context, r *http.
|
||||
}
|
||||
|
||||
decoded.Package = r.MultipartForm.File["package"][0]
|
||||
if !file.IsValidMacOSName(decoded.Package.Filename) {
|
||||
return nil, &fleet.BadRequestError{
|
||||
Message: "package name contains invalid characters",
|
||||
InternalErr: ctxerr.New(ctx, "package name contains invalid characters"),
|
||||
}
|
||||
}
|
||||
|
||||
// default is no team
|
||||
decoded.TeamID = 0
|
||||
|
@ -11,7 +11,9 @@ import (
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -146,8 +148,32 @@ func (c *Client) ValidateBootstrapPackageFromURL(url string) (*fleet.MDMAppleBoo
|
||||
return downloadRemoteMacosBootstrapPackage(url)
|
||||
}
|
||||
|
||||
func downloadRemoteMacosBootstrapPackage(url string) (*fleet.MDMAppleBootstrapPackage, error) {
|
||||
resp, err := http.Get(url) // nolint:gosec // we want this URL to be provided by the user. It will run on their machine.
|
||||
func extractFilenameFromPath(p string) string {
|
||||
u, err := url.Parse(p)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
invalid := map[string]struct{}{
|
||||
"": {},
|
||||
".": {},
|
||||
"/": {},
|
||||
}
|
||||
|
||||
b := path.Base(u.Path)
|
||||
if _, ok := invalid[b]; ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
if _, ok := invalid[path.Ext(b)]; ok {
|
||||
return b + ".pkg"
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func downloadRemoteMacosBootstrapPackage(pkgURL string) (*fleet.MDMAppleBootstrapPackage, error) {
|
||||
resp, err := http.Get(pkgURL) // nolint:gosec // we want this URL to be provided by the user. It will run on their machine.
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("downloading bootstrap package: %w", err)
|
||||
}
|
||||
@ -166,6 +192,13 @@ func downloadRemoteMacosBootstrapPackage(url string) (*fleet.MDMAppleBootstrapPa
|
||||
filename = params["filename"]
|
||||
}
|
||||
}
|
||||
|
||||
// if it fails, try to extract it from the URL
|
||||
if filename == "" {
|
||||
filename = extractFilenameFromPath(pkgURL)
|
||||
}
|
||||
|
||||
// if all else fails, use a default name
|
||||
if filename == "" {
|
||||
filename = "bootstrap-package.pkg"
|
||||
}
|
||||
|
@ -202,3 +202,26 @@ spec:
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractFilenameFromPath(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"http://example.com", ""},
|
||||
{"http://example.com/", ""},
|
||||
{"http://example.com?foo=bar", ""},
|
||||
{"http://example.com/foo.pkg", "foo.pkg"},
|
||||
{"http://example.com/foo.exe", "foo.exe"},
|
||||
{"http://example.com/foo.pkg?bar=baz", "foo.pkg"},
|
||||
{"http://example.com/foo.bar.pkg", "foo.bar.pkg"},
|
||||
{"http://example.com/foo", "foo.pkg"},
|
||||
{"http://example.com/foo/bar/baz", "baz.pkg"},
|
||||
{"http://example.com/foo?bar=baz", "foo.pkg"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := extractFilenameFromPath(c.in)
|
||||
require.Equalf(t, c.out, got, "for URL %s", c.in)
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/redis/redistest"
|
||||
@ -2608,6 +2609,14 @@ func (s *integrationMDMTestSuite) TestBootstrapPackage() {
|
||||
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: signedPkg}, http.StatusBadRequest, "package multipart field is required")
|
||||
// invalid
|
||||
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: invalidPkg, Name: "invalid.tar.gz"}, http.StatusBadRequest, "invalid file type")
|
||||
// invalid names
|
||||
for _, char := range file.InvalidMacOSChars {
|
||||
s.uploadBootstrapPackage(
|
||||
&fleet.MDMAppleBootstrapPackage{
|
||||
Bytes: signedPkg,
|
||||
Name: fmt.Sprintf("invalid_%c_name.pkg", char),
|
||||
}, http.StatusBadRequest, "")
|
||||
}
|
||||
// unsigned
|
||||
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: unsignedPkg, Name: "pkg.pkg"}, http.StatusBadRequest, "file is not signed")
|
||||
// wrong TOC
|
||||
|
Loading…
Reference in New Issue
Block a user