Skip to content

Commit ba4dc6c

Browse files
authored
Merge pull request #160 from IBM/validator
mounter args validator implementation
2 parents 9b53ee4 + 02e114c commit ba4dc6c

File tree

2 files changed

+145
-7
lines changed

2 files changed

+145
-7
lines changed

cos-csi-mounter/server/server.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,7 @@ func main() {
9797

9898
func handleCosMount() gin.HandlerFunc {
9999
return func(c *gin.Context) {
100-
var request struct {
101-
Path string `json:"path"`
102-
Mounter string `json:"mounter"`
103-
Args []string `json:"args"`
104-
}
105-
100+
var request MountRequest
106101
logger.Info("New mount request with values: ", zap.Any("Request:", request))
107102

108103
if err := c.BindJSON(&request); err != nil {
@@ -119,8 +114,16 @@ func handleCosMount() gin.HandlerFunc {
119114
return
120115
}
121116

117+
// validate mounter args
118+
args, err := request.ParseMounterArgs()
119+
if err != nil {
120+
logger.Error("Failed to parse mounter args", zap.Any("mounter", request.Mounter), zap.Error(err))
121+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid args for mounter"})
122+
return
123+
}
124+
122125
utils := mounterUtils.MounterOptsUtils{}
123-
err := utils.FuseMount(request.Path, request.Mounter, request.Args)
126+
err = utils.FuseMount(request.Path, request.Mounter, args)
124127
if err != nil {
125128
logger.Error("Mount Failed: ", zap.Error(err))
126129
c.JSON(http.StatusBadRequest, gin.H{"error": "Mount Failed"})

cos-csi-mounter/server/utils.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
)
9+
10+
// MountRequest ...
11+
type MountRequest struct {
12+
Path string `json:"path"`
13+
Bucket string `json:"bucket"`
14+
Mounter string `json:"mounter"`
15+
Args json.RawMessage `json:"args"`
16+
}
17+
18+
// MounterArgs ...
19+
type MounterArgs interface {
20+
PopulateArgsSlice(string, string) ([]string, error)
21+
}
22+
type S3FSArgs struct {
23+
URL string `json:"url,omitempty"`
24+
PasswdFilePath string `json:"passwd_file,omitempty"`
25+
UsePathRequestStyle string `json:"use_path_request_style,omitempty"`
26+
SigV2 string `json:"sigv2,omitempty"`
27+
AllowOther string `json:"allow_other,omitempty"`
28+
MPUmask string `json:"mp_umask,omitempty"`
29+
EndPoint string `json:"endpoint,omitempty"`
30+
IBMIamAuth string `json:"ibm_iam_auth,omitempty"`
31+
IBMIamEndpoint string `json:"ibm_iam_endpoint,omitempty"`
32+
DefaultACL string `json:"default_acl,omitempty"`
33+
}
34+
35+
func (args S3FSArgs) PopulateArgsSlice(bucket, targetPath string) ([]string, error) {
36+
// Marshal to JSON
37+
raw, err := json.Marshal(args)
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
// Unmarshal into map[string]string
43+
var m map[string]string
44+
if err := json.Unmarshal(raw, &m); err != nil {
45+
return nil, err
46+
}
47+
48+
// Convert to key=value slice
49+
result := []string{bucket, targetPath}
50+
for k, v := range m {
51+
result = append(result, "-o")
52+
if strings.ToLower(strings.TrimSpace(v)) == "true" {
53+
result = append(result, fmt.Sprintf("%s", k)) // -o, key
54+
} else {
55+
result = append(result, fmt.Sprintf("%s=%v", k, v)) // -o, key=value
56+
}
57+
}
58+
59+
return result, nil // [bucket, path, -o, key1=value1, -o, key2=value2]
60+
}
61+
62+
type RCloneArgs struct {
63+
Endpoint string `json:"endpoint,omitempty"`
64+
BackupDir string `json:"backup-dir,omitempty"`
65+
Bind string `json:"bind,omitempty"`
66+
BWLimit string `json:"bwlimit,omitempty"`
67+
}
68+
69+
func (args RCloneArgs) PopulateArgsSlice(bucket, targetPath string) ([]string, error) {
70+
// Marshal to JSON
71+
raw, err := json.Marshal(args)
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
// Unmarshal into map[string]string
77+
var m map[string]string
78+
if err := json.Unmarshal(raw, &m); err != nil {
79+
return nil, err
80+
}
81+
82+
// Convert to key=value slice
83+
result := []string{"mount", bucket, targetPath}
84+
for k, v := range m {
85+
result = append(result, fmt.Sprintf("--%s=%v", k, v)) // --key=value
86+
}
87+
88+
return result, nil // [mount, bucket, path, --key1=value1, --key2=value2]
89+
}
90+
91+
func strictDecodeForUnknownFields(data json.RawMessage, v interface{}) error {
92+
dec := json.NewDecoder(bytes.NewReader(data))
93+
dec.DisallowUnknownFields()
94+
return dec.Decode(v)
95+
}
96+
97+
func argsValidator(endpoint, targetPath string) error {
98+
if !(strings.HasPrefix(endpoint, "https://") || strings.HasPrefix(endpoint, "http://")) {
99+
return fmt.Errorf("Bad value for COS endpoint \"%v\": scheme is missing. "+
100+
"Must be of the form http://<hostname> or https://<hostname>", endpoint)
101+
}
102+
if !(strings.HasPrefix(targetPath, "/var/data/kubelet/pods") || strings.HasPrefix(targetPath, "/var/lib/kubelet/pods")) {
103+
return fmt.Errorf("Bad value for target path \"%v\"", targetPath)
104+
}
105+
return nil
106+
}
107+
108+
// --- Parser for Mounter Arguments ---
109+
110+
func (req *MountRequest) ParseMounterArgs() ([]string, error) {
111+
switch req.Mounter {
112+
case s3fs:
113+
var args S3FSArgs
114+
if err := strictDecodeForUnknownFields(req.Args, &args); err != nil {
115+
return nil, fmt.Errorf("invalid s3fs args decode error: %w", err)
116+
}
117+
if err := argsValidator(args.URL, req.Path); err != nil {
118+
return nil, fmt.Errorf("s3fs endpoint or target path validation failed: %w", err)
119+
}
120+
return args.PopulateArgsSlice(req.Bucket, req.Path)
121+
122+
case rclone:
123+
var args RCloneArgs
124+
if err := strictDecodeForUnknownFields(req.Args, &args); err != nil {
125+
return nil, fmt.Errorf("invalid rclone args decode error: %w", err)
126+
}
127+
if err := argsValidator(args.Endpoint, req.Path); err != nil {
128+
return nil, fmt.Errorf("rclone endpoint or target path validation failed: %w", err)
129+
}
130+
return args.PopulateArgsSlice(req.Bucket, req.Path)
131+
132+
default:
133+
return nil, fmt.Errorf("unknown mounter: %s", req.Mounter)
134+
}
135+
}

0 commit comments

Comments
 (0)