610300a131774bff45d86ab227200f8382ac4cb1
[icn/sdwan.git] /
1 // SPDX-License-Identifier: Apache-2.0\r
2 // Copyright (c) 2020 Intel Corporation\r
3 \r
4 package validation\r
5 \r
6 import (\r
7         "archive/tar"\r
8         "compress/gzip"\r
9         "encoding/json"\r
10         "fmt"\r
11         "io"\r
12         "io/ioutil"\r
13         "net"\r
14         "net/http"\r
15         "os"\r
16         "regexp"\r
17         "strconv"\r
18         "strings"\r
19 \r
20         log "github.com/open-ness/EMCO/src/orchestrator/pkg/infra/logutils"\r
21         pkgerrors "github.com/pkg/errors"\r
22         "github.com/xeipuuv/gojsonschema"\r
23         "k8s.io/apimachinery/pkg/util/validation"\r
24 )\r
25 \r
26 func IsTarGz(r io.Reader) error {\r
27         //Check if it is a valid gz\r
28         gzf, err := gzip.NewReader(r)\r
29         if err != nil {\r
30                 return pkgerrors.Wrap(err, "Invalid gzip format")\r
31         }\r
32 \r
33         //Check if it is a valid tar file\r
34         //Unfortunately this can only be done by inspecting all the tar contents\r
35         tarR := tar.NewReader(gzf)\r
36         first := true\r
37 \r
38         for true {\r
39                 header, err := tarR.Next()\r
40 \r
41                 if err == io.EOF {\r
42                         //Check if we have just a gzip file without a tar archive inside\r
43                         if first {\r
44                                 return pkgerrors.New("Empty or non-existant Tar file found")\r
45                         }\r
46                         //End of archive\r
47                         break\r
48                 }\r
49 \r
50                 if err != nil {\r
51                         return pkgerrors.Errorf("Error reading tar file %s", err.Error())\r
52                 }\r
53 \r
54                 //Check if files are of type directory and regular file\r
55                 if header.Typeflag != tar.TypeDir &&\r
56                         header.Typeflag != tar.TypeReg {\r
57                         return pkgerrors.Errorf("Unknown header in tar %s, %s",\r
58                                 header.Name, string(header.Typeflag))\r
59                 }\r
60 \r
61                 first = false\r
62         }\r
63 \r
64         return nil\r
65 }\r
66 \r
67 func IsIpv4Cidr(cidr string) error {\r
68         ip, _, err := net.ParseCIDR(cidr)\r
69         if err != nil || ip.To4() == nil {\r
70                 return pkgerrors.Wrapf(err, "could not parse ipv4 cidr %v", cidr)\r
71         }\r
72         return nil\r
73 }\r
74 \r
75 func IsIp(ip string) error {\r
76         addr := net.ParseIP(ip)\r
77         if addr == nil {\r
78                 return pkgerrors.Errorf("invalid ip address %v", ip)\r
79         }\r
80         return nil\r
81 }\r
82 \r
83 func IsIpv4(ip string) error {\r
84         addr := net.ParseIP(ip)\r
85         if addr == nil || addr.To4() == nil {\r
86                 return pkgerrors.Errorf("invalid ipv4 address %v", ip)\r
87         }\r
88         return nil\r
89 }\r
90 \r
91 func IsMac(mac string) error {\r
92         _, err := net.ParseMAC(mac)\r
93         if err != nil {\r
94                 return pkgerrors.Errorf("invalid MAC address %v", mac)\r
95         }\r
96         return nil\r
97 }\r
98 \r
99 // default name check - matches valid label value with addtion that length > 0\r
100 func IsValidName(name string) []string {\r
101         var errs []string\r
102 \r
103         errs = validation.IsValidLabelValue(name)\r
104         if len(name) == 0 {\r
105                 errs = append(errs, "name must have non-zero length")\r
106         }\r
107         return errs\r
108 }\r
109 \r
110 const VALID_NAME_STR string = "NAME"\r
111 \r
112 var validNameRegEx = regexp.MustCompile("^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")\r
113 \r
114 const VALID_ALPHA_STR string = "ALPHA"\r
115 \r
116 var validAlphaStrRegEx = regexp.MustCompile("^[A-Za-z]*$")\r
117 \r
118 const VALID_ALPHANUM_STR string = "ALPHANUM"\r
119 \r
120 var validAlphaNumStrRegEx = regexp.MustCompile("^[A-Za-z0-9]*$")\r
121 \r
122 // doesn't verify valid base64 length - just checks for proper base64 characters\r
123 const VALID_BASE64_STR string = "BASE64"\r
124 \r
125 var validBase64StrRegEx = regexp.MustCompile("^[A-Za-z0-9+/]+={0,2}$")\r
126 \r
127 const VALID_ANY_STR string = "ANY"\r
128 \r
129 var validAnyStrRegEx = regexp.MustCompile("(?s)^.*$")\r
130 \r
131 // string check - validates for conformance to provided lengths and specified content\r
132 // min and max - the string\r
133 // if format string provided - check against matching predefined\r
134 func IsValidString(str string, min, max int, format string) []string {\r
135         var errs []string\r
136 \r
137         if min > max {\r
138                 errs = append(errs, "Invalid string length constraints - min is greater than max")\r
139                 return errs\r
140         }\r
141 \r
142         if len(str) < min {\r
143                 errs = append(errs, "string length is less than the minimum constraint")\r
144                 return errs\r
145         }\r
146         if len(str) > max {\r
147                 errs = append(errs, "string length is greater than the maximum constraint")\r
148                 return errs\r
149         }\r
150 \r
151         switch format {\r
152         case VALID_ALPHA_STR:\r
153                 if !validAlphaStrRegEx.MatchString(str) {\r
154                         errs = append(errs, "string does not match the alpha only constraint")\r
155                 }\r
156         case VALID_ALPHANUM_STR:\r
157                 if !validAlphaNumStrRegEx.MatchString(str) {\r
158                         errs = append(errs, "string does not match the alphanumeric only constraint")\r
159                 }\r
160         case VALID_NAME_STR:\r
161                 if !validNameRegEx.MatchString(str) {\r
162                         errs = append(errs, "string does not match the valid k8s name constraint")\r
163                 }\r
164         case VALID_BASE64_STR:\r
165                 if !validBase64StrRegEx.MatchString(str) {\r
166                         errs = append(errs, "string does not match the valid base64 characters constraint")\r
167                 }\r
168                 if len(str)%4 != 0 {\r
169                         errs = append(errs, "base64 string length should be a multiple of 4")\r
170                 }\r
171         case VALID_ANY_STR:\r
172                 if !validAnyStrRegEx.MatchString(str) {\r
173                         errs = append(errs, "string does not match the any characters constraint")\r
174                 }\r
175         default:\r
176                 // invalid string format supplied\r
177                 errs = append(errs, "an invalid string constraint was supplied")\r
178         }\r
179 \r
180         return errs\r
181 }\r
182 \r
183 // validate that label conforms to kubernetes label conventions\r
184 //  general label format expected is:\r
185 //  "<labelprefix>/<labelname>=<Labelvalue>"\r
186 //  where labelprefix matches DNS1123Subdomain format\r
187 //        labelname matches DNS1123Label format\r
188 //\r
189 // Input labels are allowed to  match following formats:\r
190 //  "<DNS1123Subdomain>/<DNS1123Label>=<Labelvalue>"\r
191 //  "<DNS1123Label>=<LabelValue>"\r
192 //  "<LabelValue>"\r
193 func IsValidLabel(label string) []string {\r
194         var labelerrs []string\r
195 \r
196         expectLabelName := false\r
197         expectLabelPrefix := false\r
198 \r
199         // split label up into prefix, name and value\r
200         // format:  prefix/name=value\r
201         var labelprefix, labelname, labelvalue string\r
202 \r
203         kv := strings.SplitN(label, "=", 2)\r
204         if len(kv) == 1 {\r
205                 labelprefix = ""\r
206                 labelname = ""\r
207                 labelvalue = kv[0]\r
208         } else {\r
209                 pn := strings.SplitN(kv[0], "/", 2)\r
210                 if len(pn) == 1 {\r
211                         labelprefix = ""\r
212                         labelname = pn[0]\r
213                 } else {\r
214                         labelprefix = pn[0]\r
215                         labelname = pn[1]\r
216                         expectLabelPrefix = true\r
217                 }\r
218                 labelvalue = kv[1]\r
219                 // if "=" was in the label input, then expect a non-zero length name\r
220                 expectLabelName = true\r
221         }\r
222 \r
223         // check label prefix validity - prefix is optional\r
224         if len(labelprefix) > 0 {\r
225                 errs := validation.IsDNS1123Subdomain(labelprefix)\r
226                 if len(errs) > 0 {\r
227                         labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"], errors: ")\r
228                         for _, err := range errs {\r
229                                 labelerrs = append(labelerrs, err)\r
230                         }\r
231                 }\r
232         } else if expectLabelPrefix {\r
233                 labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"]")\r
234         }\r
235         if expectLabelName {\r
236                 errs := validation.IsDNS1123Label(labelname)\r
237                 if len(errs) > 0 {\r
238                         labelerrs = append(labelerrs, "Invalid label name - label=["+label+"%], labelname=["+labelname+"], errors: ")\r
239                         for _, err := range errs {\r
240                                 labelerrs = append(labelerrs, err)\r
241                         }\r
242                 }\r
243         }\r
244         if len(labelvalue) > 0 {\r
245                 errs := validation.IsValidLabelValue(labelvalue)\r
246                 if len(errs) > 0 {\r
247                         labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"], errors: ")\r
248                         for _, err := range errs {\r
249                                 labelerrs = append(labelerrs, err)\r
250                         }\r
251                 }\r
252         } else {\r
253                 // expect a non-zero value\r
254                 labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"]")\r
255         }\r
256 \r
257         return labelerrs\r
258 }\r
259 \r
260 func IsValidNumber(value, min, max int) []string {\r
261         var errs []string\r
262 \r
263         if min > max {\r
264                 errs = append(errs, "invalid constraints")\r
265                 return errs\r
266         }\r
267 \r
268         if value < min {\r
269                 errs = append(errs, "value less than minimum")\r
270         }\r
271         if value > max {\r
272                 errs = append(errs, "value greater than maximum")\r
273         }\r
274         return errs\r
275 }\r
276 \r
277 func IsValidNumberStr(value string, min, max int) []string {\r
278         var errs []string\r
279 \r
280         if min > max {\r
281                 errs = append(errs, "invalid constraints")\r
282                 return errs\r
283         }\r
284 \r
285         n, err := strconv.Atoi(value)\r
286         if err != nil {\r
287                 errs = append(errs, err.Error())\r
288                 return errs\r
289         }\r
290         if n < min {\r
291                 errs = append(errs, "value less than minimum")\r
292         }\r
293         if n > max {\r
294                 errs = append(errs, "value greater than maximum")\r
295         }\r
296         return errs\r
297 }\r
298 \r
299 /*\r
300 IsValidParameterPresent method takes in a vars map and a array of string parameters\r
301 that you expect to be present in the GET request.\r
302 Returns Nil if all the parameters are present or else shall return error message.\r
303 */\r
304 func IsValidParameterPresent(vars map[string]string, sp []string) error {\r
305 \r
306         for i := range sp {\r
307                 v := vars[sp[i]]\r
308                 if v == "" {\r
309                         errMessage := fmt.Sprintf("Missing %v in GET request", sp[i])\r
310                         return fmt.Errorf(errMessage)\r
311                 }\r
312 \r
313         }\r
314         return nil\r
315 \r
316 }\r
317 \r
318 // ValidateJsonSchemaData function validates the document against the Json Schema\r
319 func ValidateJsonSchemaData(jsonSchemaFile string, jsonData interface{}) (error, int) {\r
320 \r
321         // Read the Json Schema File\r
322         if _, err := os.Stat(jsonSchemaFile); err != nil {\r
323                 if os.IsNotExist(err) {\r
324                         err = pkgerrors.New("JsonSchemaValidation: File " + jsonSchemaFile + " not found")\r
325                 } else {\r
326                         err = pkgerrors.Wrap(err, "JsonSchemaValidation: Stat file error")\r
327                 }\r
328                 return err, http.StatusInternalServerError\r
329         }\r
330         rawBytes, err := ioutil.ReadFile(jsonSchemaFile)\r
331         if err != nil {\r
332                 return pkgerrors.Wrap(err, "JsonSchemaValidation: Read JSON file error"), http.StatusInternalServerError\r
333         }\r
334 \r
335         // Json encode the data\r
336         req, err := json.Marshal(jsonData)\r
337         if err != nil {\r
338                 return pkgerrors.Wrap(err, "JsonSchemaValidation, Request Body error"), http.StatusBadRequest\r
339         }\r
340 \r
341         // Load schema and document\r
342         schemaLoader := gojsonschema.NewStringLoader(string(rawBytes))\r
343         s, err := gojsonschema.NewSchema(schemaLoader)\r
344         if err != nil {\r
345                 return pkgerrors.Wrap(err, "JsonSchemaValidation: Validation error"), http.StatusInternalServerError\r
346         }\r
347         documentLoader := gojsonschema.NewStringLoader(string(req))\r
348         result, err := s.Validate(documentLoader)\r
349         if err != nil {\r
350                 return pkgerrors.Wrap(err, "JsonSchemaValidation: Validation error"), http.StatusInternalServerError\r
351         }\r
352         // Validate document against Json Schema\r
353         if !result.Valid() {\r
354                 for _, desc := range result.Errors() {\r
355                         log.Error("The document is not valid", log.Fields{\r
356                                 "Error": desc.Description(),\r
357                         })\r
358                 }\r
359                 return pkgerrors.New("JsonSchemaValidation: Document Validation failed"), http.StatusBadRequest\r
360         }\r
361 \r
362         return nil, 0\r
363 }\r