Skip to content

Commit 1f423b4

Browse files
authored
Allow relative path.localDir paths when new --root flag is given (#25)
Add new `--root <path>` flag to `netbootd server` command which defines the path from which mount `path.localDir` values should be relative if they are not given as absolute paths. Fixes #24.
1 parent 53aec01 commit 1f423b4

File tree

10 files changed

+60
-38
lines changed

10 files changed

+60
-38
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ Flags:
227227
-p, --http-port int HTTP port to listen on (default 8080)
228228
-i, --interface string interface to listen on, e.g. eth0 (DHCP)
229229
-m, --manifests string load manifests from directory
230+
--root string if not given as an absolute path, a mount's path.localDir is relative to this directory
230231

231232
Global Flags:
232233
-d, --debug enable debug logging

api/server.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Server struct {
2626

2727
// NewServer set up HTTP API server instance
2828
// If authorization is passed, requires privileged operation callers to present Authorization header with this content.
29-
func NewServer(store *store.Store, authorization string) (server *Server, err error) {
29+
func NewServer(store *store.Store, authorization, rootPath string) (server *Server, err error) {
3030
r := mux.NewRouter()
3131

3232
server = &Server{
@@ -119,13 +119,13 @@ func NewServer(store *store.Store, authorization string) (server *Server, err er
119119
buf, _ := ioutil.ReadAll(r.Body)
120120
var m manifest.Manifest
121121
if r.Header.Get("Content-Type") == "application/json" {
122-
err = json.Unmarshal(buf, &m)
122+
m, err = manifest.ManifestFromJson(buf, rootPath)
123123
if err != nil {
124124
http.Error(w, err.Error(), http.StatusBadRequest)
125125
return
126126
}
127127
} else {
128-
m, err = manifest.ManifestFromYaml(buf)
128+
m, err = manifest.ManifestFromYaml(buf, rootPath)
129129
if err != nil {
130130
http.Error(w, err.Error(), http.StatusBadRequest)
131131
return

cmd/server.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var (
2626
apiTlsCert string
2727
apiTlsKey string
2828
manifestPath string
29+
rootPath string
2930
)
3031

3132
func init() {
@@ -50,6 +51,9 @@ func init() {
5051
serverCmd.Flags().StringVarP(&manifestPath, "manifests", "m", "", "load manifests from directory")
5152
viper.BindPFlag("manifestPath", serverCmd.Flags().Lookup("manifests"))
5253

54+
serverCmd.Flags().StringVarP(&rootPath, "root", "", "", "if not given as an absolute path, a mount's path.localDir is relative to this directory")
55+
viper.BindPFlag("rootPath", serverCmd.Flags().Lookup("root"))
56+
5357
rootCmd.AddCommand(serverCmd)
5458
}
5559

@@ -73,7 +77,7 @@ var serverCmd = &cobra.Command{
7377
})
7478
if viper.GetString("manifestPath") != "" {
7579
log.Info().Str("path", viper.GetString("manifestPath")).Msg("Loading manifests")
76-
_ = store.LoadFromDirectory(viper.GetString("manifestPath"))
80+
_ = store.LoadFromDirectory(viper.GetString("manifestPath"), viper.GetString("rootPath"))
7781
}
7882
store.GlobalHints.HttpPort = viper.GetInt("http.port")
7983
store.GlobalHints.ApiPort = viper.GetInt("api.port")
@@ -86,7 +90,7 @@ var serverCmd = &cobra.Command{
8690
go dhcpServer.Serve()
8791

8892
// TFTP
89-
tftpServer, err := tftpd.NewServer(store)
93+
tftpServer, err := tftpd.NewServer(store, viper.GetString("rootPath"))
9094
if err != nil {
9195
log.Fatal().Err(err).Msg("Failed to create TFTP server")
9296
}
@@ -100,7 +104,7 @@ var serverCmd = &cobra.Command{
100104
go tftpServer.Serve(connTftp)
101105

102106
// HTTP service
103-
httpServer, err := httpd.NewServer(store)
107+
httpServer, err := httpd.NewServer(store, viper.GetString("rootPath"))
104108
if err != nil {
105109
log.Fatal().Err(err).Msg("Failed to create HTTP server")
106110
}
@@ -115,7 +119,7 @@ var serverCmd = &cobra.Command{
115119
log.Info().Interface("addr", connHttp.Addr()).Msg("HTTP listening")
116120

117121
// HTTP API service
118-
apiServer, err := api.NewServer(store, viper.GetString("api.authorization"))
122+
apiServer, err := api.NewServer(store, viper.GetString("api.authorization"), viper.GetString("rootPath"))
119123
if err != nil {
120124
log.Fatal().Err(err).Msg("Failed to create HTTP API server")
121125
}

httpd/handler.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"net/http/httputil"
1212
"net/url"
1313
"os"
14-
"path/filepath"
1514
"strconv"
1615
"strings"
1716
"text/template"
@@ -176,13 +175,9 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
176175
rp.ServeHTTP(w, r)
177176
return
178177
} else if mount.LocalDir != "" {
179-
path := filepath.Join(mount.LocalDir, mount.Path)
178+
path := mount.HostPath(h.server.rootPath, r.URL.Path)
180179

181-
if mount.AppendSuffix {
182-
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(r.URL.Path, mount.Path))
183-
}
184-
185-
if !strings.HasPrefix(path, mount.LocalDir) {
180+
if !mount.ValidateHostPath(h.server.rootPath, path) {
186181
h.server.logger.Error().
187182
Err(err).
188183
Msgf("Requested path is invalid: %q", path)

httpd/server.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,22 @@ type Server struct {
1414
httpClient *http.Client
1515
httpServer *http.Server
1616

17-
logger zerolog.Logger
18-
store *store.Store
17+
logger zerolog.Logger
18+
store *store.Store
19+
rootPath string
1920
}
2021

21-
func NewServer(store *store.Store) (server *Server, err error) {
22+
func NewServer(store *store.Store, rootPath string) (server *Server, err error) {
2223

2324
server = &Server{
2425
httpServer: &http.Server{
2526
ReadTimeout: 10 * time.Second,
2627
MaxHeaderBytes: 1 << 20,
2728
IdleTimeout: 10 * time.Second,
2829
},
29-
logger: log.With().Str("service", "http").Logger(),
30-
store: store,
30+
logger: log.With().Str("service", "http").Logger(),
31+
store: store,
32+
rootPath: rootPath,
3133
}
3234

3335
server.httpServer.Handler = Handler{server: server}

manifest/io.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,33 @@ import (
88
"gopkg.in/yaml.v2"
99
)
1010

11-
func ManifestFromJson(content []byte) (manifest Manifest, err error) {
11+
func ManifestFromJson(content []byte, rootPath string) (manifest Manifest, err error) {
1212
err = json.Unmarshal(content, &manifest)
1313
if err != nil {
1414
return manifest, err
1515
}
1616

17-
return manifest, manifest.Validate()
17+
return manifest, manifest.Validate(rootPath)
1818
}
1919

2020
func (m *Manifest) ToJson() ([]byte, error) {
2121
return json.MarshalIndent(m, "", " ")
2222
}
2323

24-
func ManifestFromYaml(content []byte) (manifest Manifest, err error) {
24+
func ManifestFromYaml(content []byte, rootPath string) (manifest Manifest, err error) {
2525
err = yaml.Unmarshal(content, &manifest)
2626
if err != nil {
2727
return manifest, err
2828
}
2929

30-
return manifest, manifest.Validate()
30+
return manifest, manifest.Validate(rootPath)
3131
}
3232

33-
func (m Manifest) Validate() error {
33+
func (m Manifest) Validate(rootPath string) error {
3434
for _, mount := range m.Mounts {
3535
if mount.LocalDir != "" {
36-
if !filepath.IsAbs(mount.LocalDir) {
37-
return fmt.Errorf("localDir needs to be absolute path")
36+
if !filepath.IsAbs(mount.LocalDir) && rootPath == "" {
37+
return fmt.Errorf("localDir needs to be absolute path when rootPath is not set")
3838
}
3939
}
4040
}

manifest/schema.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net"
66
"net/http"
77
"net/url"
8+
"path/filepath"
89
"strings"
910
"time"
1011
)
@@ -52,9 +53,31 @@ type Mount struct {
5253
// Provides a path on the host to find the files.
5354
// So that LocalDir: /tftpboot path: /subdir and client requests: /subdir/file.x the path on the host
5455
// becomes /tfptboot/file.x
56+
// If LocalDir is not absolute, path is relative to rootPath passed into HostPath and ValidateHostPath.
57+
// So that RootPath: /tftpboot, LocalDir: ./files, path: /subdir and client request: /subdir/file.x on the host
58+
// becomes /tftpboot/files/file.x
5559
LocalDir string `yaml:"localDir"`
5660
}
5761

62+
func (m Mount) hostPathPrefix(rootPath string) string {
63+
if filepath.IsAbs(m.LocalDir) {
64+
return m.LocalDir
65+
}
66+
return filepath.Join(rootPath, m.LocalDir)
67+
}
68+
69+
func (m Mount) HostPath(rootPath, requestPath string) string {
70+
path := m.Path
71+
if m.AppendSuffix {
72+
path = strings.TrimPrefix(requestPath, m.Path)
73+
}
74+
return filepath.Join(m.hostPathPrefix(rootPath), path)
75+
}
76+
77+
func (m Mount) ValidateHostPath(rootPath string, hostPath string) bool {
78+
return strings.HasPrefix(hostPath, m.hostPathPrefix(rootPath))
79+
}
80+
5881
func (m Mount) ProxyDirector() (func(req *http.Request), error) {
5982
target, err := url.Parse(m.Proxy)
6083
if err != nil {

store/store.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func NewStore(cfg Config) (*Store, error) {
5353
return &store, nil
5454
}
5555

56-
func (s *Store) LoadFromDirectory(path string) (err error) {
56+
func (s *Store) LoadFromDirectory(path, rootPath string) (err error) {
5757
items, err := os.ReadDir(path)
5858
for _, item := range items {
5959
if !item.Type().IsRegular() ||
@@ -68,7 +68,7 @@ func (s *Store) LoadFromDirectory(path string) (err error) {
6868
Msg("cannot open file")
6969
continue
7070
}
71-
m, err := manifest.ManifestFromYaml(b)
71+
m, err := manifest.ManifestFromYaml(b, rootPath)
7272
if err != nil {
7373
s.logger.Error().
7474
Err(err).

tftpd/handler.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"net/http"
1111
"net/url"
1212
"os"
13-
"path/filepath"
1413
"strconv"
1514
"strings"
1615
"text/template"
@@ -172,13 +171,9 @@ func (server *Server) tftpReadHandler(filename string, rf io.ReaderFrom) error {
172171
Int64("sent", n).
173172
Msg("transfer finished")
174173
} else if mount.LocalDir != "" {
175-
path := filepath.Join(mount.LocalDir, mount.Path)
174+
path := mount.HostPath(server.rootPath, filename)
176175

177-
if mount.AppendSuffix {
178-
path = filepath.Join(mount.LocalDir, strings.TrimPrefix(filename, mount.Path))
179-
}
180-
181-
if !strings.HasPrefix(path, mount.LocalDir) {
176+
if !mount.ValidateHostPath(server.rootPath, path) {
182177
err := fmt.Errorf("requested path is invalid")
183178
server.logger.Error().
184179
Err(err).

tftpd/server.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ type Server struct {
1414
httpClient *http.Client
1515
tftpServer *tftp.Server
1616

17-
logger zerolog.Logger
18-
store *store.Store
17+
logger zerolog.Logger
18+
store *store.Store
19+
rootPath string
1920
}
2021

21-
func NewServer(store *store.Store) (server *Server, err error) {
22+
func NewServer(store *store.Store, rootPath string) (server *Server, err error) {
2223

2324
server = &Server{
2425
httpClient: &http.Client{},
2526
logger: log.With().Str("service", "tftp").Logger(),
2627
store: store,
28+
rootPath: rootPath,
2729
}
2830

2931
return server, nil

0 commit comments

Comments
 (0)