internal/validate: relocate from app
All checks were successful
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 2m23s
Test / Hakurei (push) Successful in 3m9s
Test / Hpkg (push) Successful in 4m7s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m1s
Test / Flake checks (push) Successful in 1m30s
All checks were successful
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 2m23s
Test / Hakurei (push) Successful in 3m9s
Test / Hpkg (push) Successful in 4m7s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m1s
Test / Flake checks (push) Successful in 1m30s
These are free of the dispatcher from internal/app. This change relocates them into their own package. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -43,7 +43,7 @@ type outcomeState struct {
|
|||||||
Identity int
|
Identity int
|
||||||
// Copied from Identity.
|
// Copied from Identity.
|
||||||
identity *stringPair[int]
|
identity *stringPair[int]
|
||||||
// Returned by [Hsu.MustIDMsg].
|
// Returned by [Hsu.MustID].
|
||||||
UserID int
|
UserID int
|
||||||
// Target init namespace uid resolved from UserID and identity.
|
// Target init namespace uid resolved from UserID and identity.
|
||||||
uid *stringPair[int]
|
uid *stringPair[int]
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func deepContainsH(basepath, targpath string) (bool, error) {
|
|
||||||
const upper = ".." + string(filepath.Separator)
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(basepath, targpath)
|
|
||||||
return err == nil &&
|
|
||||||
rel != ".." &&
|
|
||||||
!strings.HasPrefix(rel, upper), err
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container/fhs"
|
"hakurei.app/container/fhs"
|
||||||
|
"hakurei.app/internal/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(spAccountOp{}) }
|
func init() { gob.Register(spAccountOp{}) }
|
||||||
@@ -21,7 +22,7 @@ func (s spAccountOp) toSystem(state *outcomeStateSys) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default is applied in toContainer
|
// default is applied in toContainer
|
||||||
if state.Container.Username != "" && !isValidUsername(state.Container.Username) {
|
if state.Container.Username != "" && !validate.IsValidUsername(state.Container.Username) {
|
||||||
return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username))
|
return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"hakurei.app/container/fhs"
|
"hakurei.app/container/fhs"
|
||||||
"hakurei.app/container/seccomp"
|
"hakurei.app/container/seccomp"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal/validate"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/acl"
|
"hakurei.app/system/acl"
|
||||||
@@ -243,7 +244,7 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil {
|
if ok, err := validate.DeepContainsH(p[0], hidePaths[i]); err != nil {
|
||||||
return &hst.AppError{Step: "determine path hiding outcome", Err: err}
|
return &hst.AppError{Step: "determine path hiding outcome", Err: err}
|
||||||
} else if ok {
|
} else if ok {
|
||||||
hidePathMatch[i] = true
|
hidePathMatch[i] = true
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
//#include <unistd.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
const _SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX
|
|
||||||
|
|
||||||
func sysconf(name C.int) int { return int(C.sysconf(name)) }
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsValidUsername(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("long", func(t *testing.T) {
|
|
||||||
if isValidUsername(strings.Repeat("a", sysconf(_SC_LOGIN_NAME_MAX))) {
|
|
||||||
t.Errorf("isValidUsername unexpected true")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("regexp", func(t *testing.T) {
|
|
||||||
if isValidUsername("0") {
|
|
||||||
t.Errorf("isValidUsername unexpected true")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("valid", func(t *testing.T) {
|
|
||||||
if !isValidUsername("alice") {
|
|
||||||
t.Errorf("isValidUsername unexpected false")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
8
internal/validate/sysconf.go
Normal file
8
internal/validate/sysconf.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package validate
|
||||||
|
|
||||||
|
//#include <unistd.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX
|
||||||
|
|
||||||
|
func Sysconf(name C.int) int { return int(C.sysconf(name)) }
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package app
|
package validate_test
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/validate"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_POSIX_LOGIN_NAME_MAX = 9
|
_POSIX_LOGIN_NAME_MAX = 9
|
||||||
@@ -10,7 +14,7 @@ func TestSysconf(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
t.Run("LOGIN_NAME_MAX", func(t *testing.T) {
|
t.Run("LOGIN_NAME_MAX", func(t *testing.T) {
|
||||||
if got := sysconf(_SC_LOGIN_NAME_MAX); got < _POSIX_LOGIN_NAME_MAX {
|
if got := validate.Sysconf(validate.SC_LOGIN_NAME_MAX); got < _POSIX_LOGIN_NAME_MAX {
|
||||||
t.Errorf("sysconf(_SC_LOGIN_NAME_MAX): %d < _POSIX_LOGIN_NAME_MAX", got)
|
t.Errorf("sysconf(_SC_LOGIN_NAME_MAX): %d < _POSIX_LOGIN_NAME_MAX", got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package app
|
package validate
|
||||||
|
|
||||||
import "regexp"
|
import "regexp"
|
||||||
|
|
||||||
// nameRegex is the default NAME_REGEX value from adduser.
|
// nameRegex is the default NAME_REGEX value from adduser.
|
||||||
var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`)
|
var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`)
|
||||||
|
|
||||||
// isValidUsername returns whether the argument is a valid username
|
// IsValidUsername returns whether the argument is a valid username.
|
||||||
func isValidUsername(username string) bool {
|
func IsValidUsername(username string) bool {
|
||||||
return len(username) < sysconf(_SC_LOGIN_NAME_MAX) &&
|
return len(username) < Sysconf(SC_LOGIN_NAME_MAX) &&
|
||||||
nameRegex.MatchString(username)
|
nameRegex.MatchString(username)
|
||||||
}
|
}
|
||||||
30
internal/validate/username_test.go
Normal file
30
internal/validate/username_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package validate_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsValidUsername(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("long", func(t *testing.T) {
|
||||||
|
if validate.IsValidUsername(strings.Repeat("a", validate.Sysconf(validate.SC_LOGIN_NAME_MAX))) {
|
||||||
|
t.Errorf("IsValidUsername unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("regexp", func(t *testing.T) {
|
||||||
|
if validate.IsValidUsername("0") {
|
||||||
|
t.Errorf("IsValidUsername unexpected true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
if !validate.IsValidUsername("alice") {
|
||||||
|
t.Errorf("IsValidUsername unexpected false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
20
internal/validate/validate.go
Normal file
20
internal/validate/validate.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Package validate provides functions for validating string values of various types.
|
||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepContainsH returns whether basepath is equivalent to or is the parent of targpath.
|
||||||
|
//
|
||||||
|
// This is used for path hiding warning behaviour, the purpose of which is to improve
|
||||||
|
// user experience and is *not* a security feature and must not be treated as such.
|
||||||
|
func DeepContainsH(basepath, targpath string) (bool, error) {
|
||||||
|
const upper = ".." + string(filepath.Separator)
|
||||||
|
|
||||||
|
rel, err := filepath.Rel(basepath, targpath)
|
||||||
|
return err == nil &&
|
||||||
|
rel != ".." &&
|
||||||
|
!strings.HasPrefix(rel, upper), err
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package app
|
package validate_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeepContainsH(t *testing.T) {
|
func TestDeepContainsH(t *testing.T) {
|
||||||
@@ -78,10 +80,10 @@ func TestDeepContainsH(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
|
if got, err := validate.DeepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
|
||||||
t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr)
|
t.Errorf("DeepContainsH: error = %v, wantErr %v", err, tc.wantErr)
|
||||||
} else if got != tc.want {
|
} else if got != tc.want {
|
||||||
t.Errorf("deepContainsH() = %v, want %v", got, tc.want)
|
t.Errorf("DeepContainsH: = %v, want %v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user