tetragon

Форк
0
/
generictracingpolicy.go 
317 строк · 9.7 Кб
1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright Authors of Tetragon
3

4
package tracingpolicy
5

6
import (
7
	"fmt"
8
	"os"
9
	"sync"
10

11
	"github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/client"
12
	"github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1"
13

14
	ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
15
	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16
	apischema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
17
	structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
18
	"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
19
	apivalidation "k8s.io/apimachinery/pkg/api/validation"
20
	k8sv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22
	"k8s.io/apimachinery/pkg/runtime/schema"
23
	"k8s.io/apimachinery/pkg/util/validation/field"
24
	"k8s.io/kube-openapi/pkg/validation/validate"
25
	"sigs.k8s.io/yaml"
26
)
27

28
// validatorState is used by the CRD validation process to store the validators
29
// structures.
30
var validatorState = struct {
31
	validators map[schema.GroupVersionKind]validation.SchemaValidator
32
	init       sync.Once
33
	initError  error
34
}{
35
	validators: make(map[schema.GroupVersionKind]validation.SchemaValidator),
36
}
37

38
// defaultState is used by to store the structural schemas apply the defaults in
39
// the custom resources.
40
var defaultState = struct {
41
	structuralSchemaTP  *apischema.Structural
42
	structuralSchemaTPN *apischema.Structural
43
	init                sync.Once
44
	initError           error
45
}{}
46

47
// ApplyCRDDefault uses internal k8s api server machinery and can only process
48
// unustructured objects (unfortunately, since it requires to unmarshal and
49
// marshal).
50
// This first reading step is also used to return if the resource is namespaced
51
// or not (second return value).
52
func ApplyCRDDefault(rawPolicy []byte) (rawPolicyWithDefault []byte, namespaced bool, err error) {
53
	defaultState.init.Do(func() {
54
		// retrieve CRD
55
		var crvInternalTP ext.CustomResourceDefinition
56
		err := extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(
57
			&client.TracingPolicyCRD.Definition,
58
			&crvInternalTP,
59
			nil,
60
		)
61
		if err != nil {
62
			defaultState.initError = fmt.Errorf("failed to convert TracingPolicy CRD: %w", err)
63
			return
64
		}
65

66
		var crvInternalTPN ext.CustomResourceDefinition
67
		err = extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(
68
			&client.TracingPolicyNamespacedCRD.Definition,
69
			&crvInternalTPN,
70
			nil,
71
		)
72
		if err != nil {
73
			defaultState.initError = fmt.Errorf("failed to convert TracingPolicyNamespaced CRD: %w", err)
74
			return
75
		}
76

77
		// create a structural schema from the CRD
78
		defaultState.structuralSchemaTP, err = apischema.NewStructural(crvInternalTP.Spec.Validation.OpenAPIV3Schema)
79
		if err != nil {
80
			defaultState.initError = fmt.Errorf("failed to initialize structural for TracingPolicy: %w", err)
81
			return
82
		}
83
		defaultState.structuralSchemaTPN, err = apischema.NewStructural(crvInternalTPN.Spec.Validation.OpenAPIV3Schema)
84
		if err != nil {
85
			defaultState.initError = fmt.Errorf("failed to initialize structural for TracingPolicyNamespaced: %w", err)
86
			return
87
		}
88
	})
89

90
	if defaultState.initError != nil {
91
		return nil, false, fmt.Errorf("failed to initialize default structural schemas: %w", validatorState.initError)
92
	}
93

94
	// unmarshall into an unstructured object
95
	var policyUnstr unstructured.Unstructured
96
	err = yaml.UnmarshalStrict(rawPolicy, &policyUnstr)
97
	if err != nil {
98
		return nil, false, fmt.Errorf("failed to unmarshall policy: %v", err)
99
	}
100

101
	// apply defaults
102
	switch policyUnstr.GetKind() {
103
	case v1alpha1.TPKindDefinition:
104
		structuraldefaulting.Default(policyUnstr.Object, defaultState.structuralSchemaTP)
105
	case v1alpha1.TPNamespacedKindDefinition:
106
		structuraldefaulting.Default(policyUnstr.Object, defaultState.structuralSchemaTPN)
107
		namespaced = true
108
	}
109

110
	// marshal defaulted unstructured object into json
111
	rawPolicyWithDefault, err = policyUnstr.MarshalJSON()
112
	if err != nil {
113
		return nil, false, fmt.Errorf("failed to marshal defaulted object: %w", err)
114
	}
115

116
	return rawPolicyWithDefault, namespaced, nil
117
}
118

119
// K8sTracingPolicyObject is necessary to have a common type for
120
// GenericTracingPolicy and GenericTracingPolicyNamespaced for the validation
121
// functions.
122
//
123
// NB: we could get rid of one type as they represent the same object
124
// internally, just keep GenericTracingPolicy and remove that interface. We can
125
// then distinguish between Namespaced or not by reading the Kind of the
126
// resource. That's a matter of preference between type casting and calling a
127
// method to distinguish which kind is it really.
128
type K8sTracingPolicyObject interface {
129
	TracingPolicy
130
	GetKind() string
131
	GetGroupVersionKind() schema.GroupVersionKind
132
	GetMetadata() k8sv1.ObjectMeta
133
}
134

135
func (gtp GenericTracingPolicy) GetKind() string {
136
	return gtp.Kind
137
}
138
func (gtp GenericTracingPolicy) GetGroupVersionKind() schema.GroupVersionKind {
139
	return gtp.GroupVersionKind()
140
}
141
func (gtp GenericTracingPolicy) GetMetadata() k8sv1.ObjectMeta {
142
	return gtp.Metadata
143
}
144

145
func (gtp GenericTracingPolicyNamespaced) GetKind() string {
146
	return gtp.Kind
147
}
148
func (gtp GenericTracingPolicyNamespaced) GetGroupVersionKind() schema.GroupVersionKind {
149
	return gtp.GroupVersionKind()
150
}
151
func (gtp GenericTracingPolicyNamespaced) GetMetadata() k8sv1.ObjectMeta {
152
	return gtp.Metadata
153
}
154

155
// ValidateCRD validates the metadata of the objects (name, labels,
156
// annotations...) and the specification using the custom CRD schemas.
157
func ValidateCRD(policy K8sTracingPolicyObject) (*validate.Result, error) {
158
	metaErrors := ValidateCRDMeta(policy)
159

160
	specErrors, err := ValidateCRDSpec(policy)
161
	if err != nil {
162
		return nil, err
163
	}
164

165
	// combine meta and spec validation errors
166
	specErrors.Errors = append(metaErrors, specErrors.Errors...)
167
	return specErrors, nil
168
}
169

170
func ValidateCRDMeta(policy K8sTracingPolicyObject) []error {
171
	errs := []error{}
172
	requireNamespace := false
173
	if policy.GetKind() == v1alpha1.TPNamespacedKindDefinition {
174
		requireNamespace = true
175
	}
176
	metadata := policy.GetMetadata()
177

178
	errorList := apivalidation.ValidateObjectMeta(&metadata, requireNamespace, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
179
	for _, err := range errorList {
180
		errs = append(errs, err)
181
	}
182
	return errs
183
}
184

185
func ValidateCRDSpec(policy K8sTracingPolicyObject) (*validate.Result, error) {
186
	validatorState.init.Do(func() {
187
		crds := []*extv1.CustomResourceDefinition{
188
			&client.TracingPolicyCRD.Definition,
189
			&client.TracingPolicyNamespacedCRD.Definition,
190
		}
191

192
		// initialize the validators from the CRDs
193
		for _, crd := range crds {
194
			internal := &ext.CustomResourceDefinition{}
195
			if err := extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(crd, internal, nil); err != nil {
196
				validatorState.initError = err
197
				return
198
			}
199
			for _, ver := range internal.Spec.Versions {
200
				var sv validation.SchemaValidator
201
				var err error
202
				if ver.Schema != nil {
203
					sv, _, err = validation.NewSchemaValidator(ver.Schema.OpenAPIV3Schema)
204
					if err != nil {
205
						validatorState.initError = err
206
						return
207
					}
208
				}
209
				if internal.Spec.Validation != nil {
210
					sv, _, err = validation.NewSchemaValidator(internal.Spec.Validation.OpenAPIV3Schema)
211
					if err != nil {
212
						validatorState.initError = err
213
						return
214
					}
215
				}
216
				validatorState.validators[schema.GroupVersionKind{
217
					Group:   internal.Spec.Group,
218
					Version: ver.Name,
219
					Kind:    internal.Spec.Names.Kind,
220
				}] = sv
221
			}
222
		}
223
	})
224

225
	if validatorState.initError != nil {
226
		return nil, fmt.Errorf("failed to initialize validators: %w", validatorState.initError)
227
	}
228

229
	v, ok := validatorState.validators[policy.GetGroupVersionKind()]
230
	if !ok {
231
		return nil, fmt.Errorf("could not find validator for: " + policy.GetGroupVersionKind().String())
232
	}
233

234
	return v.Validate(policy), nil
235
}
236

237
type GenericTracingPolicy struct {
238
	k8sv1.TypeMeta
239
	Metadata k8sv1.ObjectMeta           `json:"metadata"`
240
	Spec     v1alpha1.TracingPolicySpec `json:"spec"`
241
}
242

243
func (gtp *GenericTracingPolicy) TpName() string {
244
	return gtp.Metadata.Name
245
}
246

247
func (gtp *GenericTracingPolicy) TpSpec() *v1alpha1.TracingPolicySpec {
248
	return &gtp.Spec
249
}
250

251
func (gtp *GenericTracingPolicy) TpInfo() string {
252
	return gtp.Metadata.Name
253
}
254

255
func FromYAML(data string) (TracingPolicy, error) {
256
	rawPolicy, namespaced, err := ApplyCRDDefault([]byte(data))
257
	if err != nil {
258
		return nil, fmt.Errorf("error applying CRD defaults: %w", err)
259
	}
260

261
	var policy K8sTracingPolicyObject
262
	if namespaced {
263
		policy = &GenericTracingPolicyNamespaced{}
264
	} else {
265
		policy = &GenericTracingPolicy{}
266
	}
267

268
	err = yaml.UnmarshalStrict(rawPolicy, &policy)
269
	if err != nil {
270
		return nil, fmt.Errorf("failed to unmarshal object with defaults: %w", err)
271
	}
272

273
	validationResult, err := ValidateCRD(policy)
274
	if err != nil {
275
		return nil, fmt.Errorf("validation failed %q: %w", policy.GetMetadata().Name, err)
276
	}
277

278
	if len(validationResult.Errors) > 0 {
279
		return nil, fmt.Errorf("validation failed: %q: %w", policy.GetMetadata().Name, validationResult.AsError())
280
	}
281

282
	return policy, nil
283
}
284

285
func FromFile(path string) (TracingPolicy, error) {
286
	policy, err := os.ReadFile(path)
287
	if err != nil {
288
		return nil, err
289
	}
290
	tp, err := FromYAML(string(policy))
291
	if err != nil {
292
		return nil, fmt.Errorf("failed loading tracing policy file %q: %w", path, err)
293
	}
294
	return tp, nil
295
}
296

297
type GenericTracingPolicyNamespaced struct {
298
	k8sv1.TypeMeta
299
	Metadata k8sv1.ObjectMeta           `json:"metadata"`
300
	Spec     v1alpha1.TracingPolicySpec `json:"spec"`
301
}
302

303
func (gtp *GenericTracingPolicyNamespaced) TpNamespace() string {
304
	return gtp.Metadata.Namespace
305
}
306

307
func (gtp *GenericTracingPolicyNamespaced) TpName() string {
308
	return gtp.Metadata.Name
309
}
310

311
func (gtp *GenericTracingPolicyNamespaced) TpSpec() *v1alpha1.TracingPolicySpec {
312
	return &gtp.Spec
313
}
314

315
func (gtp *GenericTracingPolicyNamespaced) TpInfo() string {
316
	return gtp.Metadata.Name
317
}
318

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.