mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +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/VividCortex/mysqlerr"
|
||||||
"github.com/docker/go-units"
|
"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/authz"
|
||||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
"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]
|
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
|
// default is no team
|
||||||
decoded.TeamID = 0
|
decoded.TeamID = 0
|
||||||
|
@ -11,7 +11,9 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -146,8 +148,32 @@ func (c *Client) ValidateBootstrapPackageFromURL(url string) (*fleet.MDMAppleBoo
|
|||||||
return downloadRemoteMacosBootstrapPackage(url)
|
return downloadRemoteMacosBootstrapPackage(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadRemoteMacosBootstrapPackage(url string) (*fleet.MDMAppleBootstrapPackage, error) {
|
func extractFilenameFromPath(p string) string {
|
||||||
resp, err := http.Get(url) // nolint:gosec // we want this URL to be provided by the user. It will run on their machine.
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("downloading bootstrap package: %w", err)
|
return nil, fmt.Errorf("downloading bootstrap package: %w", err)
|
||||||
}
|
}
|
||||||
@ -166,6 +192,13 @@ func downloadRemoteMacosBootstrapPackage(url string) (*fleet.MDMAppleBootstrapPa
|
|||||||
filename = params["filename"]
|
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 == "" {
|
if filename == "" {
|
||||||
filename = "bootstrap-package.pkg"
|
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"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||||
"github.com/fleetdm/fleet/v4/server/config"
|
"github.com/fleetdm/fleet/v4/server/config"
|
||||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||||
"github.com/fleetdm/fleet/v4/server/datastore/redis/redistest"
|
"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")
|
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: signedPkg}, http.StatusBadRequest, "package multipart field is required")
|
||||||
// invalid
|
// invalid
|
||||||
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: invalidPkg, Name: "invalid.tar.gz"}, http.StatusBadRequest, "invalid file type")
|
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
|
// unsigned
|
||||||
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: unsignedPkg, Name: "pkg.pkg"}, http.StatusBadRequest, "file is not signed")
|
s.uploadBootstrapPackage(&fleet.MDMAppleBootstrapPackage{Bytes: unsignedPkg, Name: "pkg.pkg"}, http.StatusBadRequest, "file is not signed")
|
||||||
// wrong TOC
|
// wrong TOC
|
||||||
|
Loading…
Reference in New Issue
Block a user