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