summaryrefslogtreecommitdiffstats
path: root/src/orchestrator/pkg/infra/validation/validation.go
blob: c43d29ec209ae2953ef8c12b5e1c8d55984e254b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/*
 * Copyright 2020 Intel Corporation, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package validation

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"net"
	"regexp"
	"strconv"
	"strings"

	pkgerrors "github.com/pkg/errors"
	"k8s.io/apimachinery/pkg/util/validation"
)

func IsTarGz(r io.Reader) error {
	//Check if it is a valid gz
	gzf, err := gzip.NewReader(r)
	if err != nil {
		return pkgerrors.Wrap(err, "Invalid gzip format")
	}

	//Check if it is a valid tar file
	//Unfortunately this can only be done by inspecting all the tar contents
	tarR := tar.NewReader(gzf)
	first := true

	for true {
		header, err := tarR.Next()

		if err == io.EOF {
			//Check if we have just a gzip file without a tar archive inside
			if first {
				return pkgerrors.New("Empty or non-existant Tar file found")
			}
			//End of archive
			break
		}

		if err != nil {
			return pkgerrors.Errorf("Error reading tar file %s", err.Error())
		}

		//Check if files are of type directory and regular file
		if header.Typeflag != tar.TypeDir &&
			header.Typeflag != tar.TypeReg {
			return pkgerrors.Errorf("Unknown header in tar %s, %s",
				header.Name, string(header.Typeflag))
		}

		first = false
	}

	return nil
}

func IsIpv4Cidr(cidr string) error {
	ip, _, err := net.ParseCIDR(cidr)
	if err != nil || ip.To4() == nil {
		return pkgerrors.Wrapf(err, "could not parse ipv4 cidr %v", cidr)
	}
	return nil
}

func IsIp(ip string) error {
	addr := net.ParseIP(ip)
	if addr == nil {
		return pkgerrors.Errorf("invalid ip address %v", ip)
	}
	return nil
}

func IsIpv4(ip string) error {
	addr := net.ParseIP(ip)
	if addr == nil || addr.To4() == nil {
		return pkgerrors.Errorf("invalid ipv4 address %v", ip)
	}
	return nil
}

func IsMac(mac string) error {
	_, err := net.ParseMAC(mac)
	if err != nil {
		return pkgerrors.Errorf("invalid MAC address %v", mac)
	}
	return nil
}

// default name check - matches valid label value with addtion that length > 0
func IsValidName(name string) []string {
	var errs []string

	errs = validation.IsValidLabelValue(name)
	if len(name) == 0 {
		errs = append(errs, "name must have non-zero length")
	}
	return errs
}

const VALID_NAME_STR string = "NAME"

var validNameRegEx = regexp.MustCompile("^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")

const VALID_ALPHA_STR string = "ALPHA"

var validAlphaStrRegEx = regexp.MustCompile("^[A-Za-z]*$")

const VALID_ALPHANUM_STR string = "ALPHANUM"

var validAlphaNumStrRegEx = regexp.MustCompile("^[A-Za-z0-9]*$")

// doesn't verify valid base64 length - just checks for proper base64 characters
const VALID_BASE64_STR string = "BASE64"

var validBase64StrRegEx = regexp.MustCompile("^[A-Za-z0-9+/]+={0,2}$")

const VALID_ANY_STR string = "ANY"

var validAnyStrRegEx = regexp.MustCompile("(?s)^.*$")

// string check - validates for conformance to provided lengths and specified content
// min and max - the string
// if format string provided - check against matching predefined
func IsValidString(str string, min, max int, format string) []string {
	var errs []string

	if min > max {
		errs = append(errs, "Invalid string length constraints - min is greater than max")
		return errs
	}

	if len(str) < min {
		errs = append(errs, "string length is less than the minimum constraint")
		return errs
	}
	if len(str) > max {
		errs = append(errs, "string length is greater than the maximum constraint")
		return errs
	}

	switch format {
	case VALID_ALPHA_STR:
		if !validAlphaStrRegEx.MatchString(str) {
			errs = append(errs, "string does not match the alpha only constraint")
		}
	case VALID_ALPHANUM_STR:
		if !validAlphaNumStrRegEx.MatchString(str) {
			errs = append(errs, "string does not match the alphanumeric only constraint")
		}
	case VALID_NAME_STR:
		if !validNameRegEx.MatchString(str) {
			errs = append(errs, "string does not match the valid k8s name constraint")
		}
	case VALID_BASE64_STR:
		if !validBase64StrRegEx.MatchString(str) {
			errs = append(errs, "string does not match the valid base64 characters constraint")
		}
		if len(str)%4 != 0 {
			errs = append(errs, "base64 string length should be a multiple of 4")
		}
	case VALID_ANY_STR:
		if !validAnyStrRegEx.MatchString(str) {
			errs = append(errs, "string does not match the any characters constraint")
		}
	default:
		// invalid string format supplied
		errs = append(errs, "an invalid string constraint was supplied")
	}

	return errs
}

// validate that label conforms to kubernetes label conventions
//  general label format expected is:
//  "<labelprefix>/<labelname>=<Labelvalue>"
//  where labelprefix matches DNS1123Subdomain format
//        labelname matches DNS1123Label format
//
// Input labels are allowed to  match following formats:
//  "<DNS1123Subdomain>/<DNS1123Label>=<Labelvalue>"
//  "<DNS1123Label>=<LabelValue>"
//  "<LabelValue>"
func IsValidLabel(label string) []string {
	var labelerrs []string

	expectLabelName := false
	expectLabelPrefix := false

	// split label up into prefix, name and value
	// format:  prefix/name=value
	var labelprefix, labelname, labelvalue string

	kv := strings.SplitN(label, "=", 2)
	if len(kv) == 1 {
		labelprefix = ""
		labelname = ""
		labelvalue = kv[0]
	} else {
		pn := strings.SplitN(kv[0], "/", 2)
		if len(pn) == 1 {
			labelprefix = ""
			labelname = pn[0]
		} else {
			labelprefix = pn[0]
			labelname = pn[1]
			expectLabelPrefix = true
		}
		labelvalue = kv[1]
		// if "=" was in the label input, then expect a non-zero length name
		expectLabelName = true
	}

	// check label prefix validity - prefix is optional
	if len(labelprefix) > 0 {
		errs := validation.IsDNS1123Subdomain(labelprefix)
		if len(errs) > 0 {
			labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"], errors: ")
			for _, err := range errs {
				labelerrs = append(labelerrs, err)
			}
		}
	} else if expectLabelPrefix {
		labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"]")
	}
	if expectLabelName {
		errs := validation.IsDNS1123Label(labelname)
		if len(errs) > 0 {
			labelerrs = append(labelerrs, "Invalid label name - label=["+label+"%], labelname=["+labelname+"], errors: ")
			for _, err := range errs {
				labelerrs = append(labelerrs, err)
			}
		}
	}
	if len(labelvalue) > 0 {
		errs := validation.IsValidLabelValue(labelvalue)
		if len(errs) > 0 {
			labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"], errors: ")
			for _, err := range errs {
				labelerrs = append(labelerrs, err)
			}
		}
	} else {
		// expect a non-zero value
		labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"]")
	}

	return labelerrs
}

func IsValidNumber(value, min, max int) []string {
	var errs []string

	if min > max {
		errs = append(errs, "invalid constraints")
		return errs
	}

	if value < min {
		errs = append(errs, "value less than minimum")
	}
	if value > max {
		errs = append(errs, "value greater than maximum")
	}
	return errs
}

func IsValidNumberStr(value string, min, max int) []string {
	var errs []string

	if min > max {
		errs = append(errs, "invalid constraints")
		return errs
	}

	n, err := strconv.Atoi(value)
	if err != nil {
		errs = append(errs, err.Error())
		return errs
	}
	if n < min {
		errs = append(errs, "value less than minimum")
	}
	if n > max {
		errs = append(errs, "value greater than maximum")
	}
	return errs
}

/*
IsValidParameterPresent method takes in a vars map and a array of string parameters
that you expect to be present in the GET request.
Returns Nil if all the parameters are present or else shall return error message.
*/
func IsValidParameterPresent(vars map[string]string, sp []string) error {

	for i := range sp {
		v := vars[sp[i]]
		if v == "" {
			errMessage := fmt.Sprintf("Missing %v in GET request", sp[i])
			return fmt.Errorf(errMessage)
		}

	}
	return nil

}