tetragon
317 строк · 9.7 Кб
1// SPDX-License-Identifier: Apache-2.0
2// Copyright Authors of Tetragon
3
4package tracingpolicy
5
6import (
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
14ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
15extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
16apischema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
17structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
18"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
19apivalidation "k8s.io/apimachinery/pkg/api/validation"
20k8sv1 "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.
30var validatorState = struct {
31validators map[schema.GroupVersionKind]validation.SchemaValidator
32init sync.Once
33initError error
34}{
35validators: 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.
40var defaultState = struct {
41structuralSchemaTP *apischema.Structural
42structuralSchemaTPN *apischema.Structural
43init sync.Once
44initError 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).
52func ApplyCRDDefault(rawPolicy []byte) (rawPolicyWithDefault []byte, namespaced bool, err error) {
53defaultState.init.Do(func() {
54// retrieve CRD
55var crvInternalTP ext.CustomResourceDefinition
56err := extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(
57&client.TracingPolicyCRD.Definition,
58&crvInternalTP,
59nil,
60)
61if err != nil {
62defaultState.initError = fmt.Errorf("failed to convert TracingPolicy CRD: %w", err)
63return
64}
65
66var crvInternalTPN ext.CustomResourceDefinition
67err = extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(
68&client.TracingPolicyNamespacedCRD.Definition,
69&crvInternalTPN,
70nil,
71)
72if err != nil {
73defaultState.initError = fmt.Errorf("failed to convert TracingPolicyNamespaced CRD: %w", err)
74return
75}
76
77// create a structural schema from the CRD
78defaultState.structuralSchemaTP, err = apischema.NewStructural(crvInternalTP.Spec.Validation.OpenAPIV3Schema)
79if err != nil {
80defaultState.initError = fmt.Errorf("failed to initialize structural for TracingPolicy: %w", err)
81return
82}
83defaultState.structuralSchemaTPN, err = apischema.NewStructural(crvInternalTPN.Spec.Validation.OpenAPIV3Schema)
84if err != nil {
85defaultState.initError = fmt.Errorf("failed to initialize structural for TracingPolicyNamespaced: %w", err)
86return
87}
88})
89
90if defaultState.initError != nil {
91return nil, false, fmt.Errorf("failed to initialize default structural schemas: %w", validatorState.initError)
92}
93
94// unmarshall into an unstructured object
95var policyUnstr unstructured.Unstructured
96err = yaml.UnmarshalStrict(rawPolicy, &policyUnstr)
97if err != nil {
98return nil, false, fmt.Errorf("failed to unmarshall policy: %v", err)
99}
100
101// apply defaults
102switch policyUnstr.GetKind() {
103case v1alpha1.TPKindDefinition:
104structuraldefaulting.Default(policyUnstr.Object, defaultState.structuralSchemaTP)
105case v1alpha1.TPNamespacedKindDefinition:
106structuraldefaulting.Default(policyUnstr.Object, defaultState.structuralSchemaTPN)
107namespaced = true
108}
109
110// marshal defaulted unstructured object into json
111rawPolicyWithDefault, err = policyUnstr.MarshalJSON()
112if err != nil {
113return nil, false, fmt.Errorf("failed to marshal defaulted object: %w", err)
114}
115
116return 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.
128type K8sTracingPolicyObject interface {
129TracingPolicy
130GetKind() string
131GetGroupVersionKind() schema.GroupVersionKind
132GetMetadata() k8sv1.ObjectMeta
133}
134
135func (gtp GenericTracingPolicy) GetKind() string {
136return gtp.Kind
137}
138func (gtp GenericTracingPolicy) GetGroupVersionKind() schema.GroupVersionKind {
139return gtp.GroupVersionKind()
140}
141func (gtp GenericTracingPolicy) GetMetadata() k8sv1.ObjectMeta {
142return gtp.Metadata
143}
144
145func (gtp GenericTracingPolicyNamespaced) GetKind() string {
146return gtp.Kind
147}
148func (gtp GenericTracingPolicyNamespaced) GetGroupVersionKind() schema.GroupVersionKind {
149return gtp.GroupVersionKind()
150}
151func (gtp GenericTracingPolicyNamespaced) GetMetadata() k8sv1.ObjectMeta {
152return gtp.Metadata
153}
154
155// ValidateCRD validates the metadata of the objects (name, labels,
156// annotations...) and the specification using the custom CRD schemas.
157func ValidateCRD(policy K8sTracingPolicyObject) (*validate.Result, error) {
158metaErrors := ValidateCRDMeta(policy)
159
160specErrors, err := ValidateCRDSpec(policy)
161if err != nil {
162return nil, err
163}
164
165// combine meta and spec validation errors
166specErrors.Errors = append(metaErrors, specErrors.Errors...)
167return specErrors, nil
168}
169
170func ValidateCRDMeta(policy K8sTracingPolicyObject) []error {
171errs := []error{}
172requireNamespace := false
173if policy.GetKind() == v1alpha1.TPNamespacedKindDefinition {
174requireNamespace = true
175}
176metadata := policy.GetMetadata()
177
178errorList := apivalidation.ValidateObjectMeta(&metadata, requireNamespace, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
179for _, err := range errorList {
180errs = append(errs, err)
181}
182return errs
183}
184
185func ValidateCRDSpec(policy K8sTracingPolicyObject) (*validate.Result, error) {
186validatorState.init.Do(func() {
187crds := []*extv1.CustomResourceDefinition{
188&client.TracingPolicyCRD.Definition,
189&client.TracingPolicyNamespacedCRD.Definition,
190}
191
192// initialize the validators from the CRDs
193for _, crd := range crds {
194internal := &ext.CustomResourceDefinition{}
195if err := extv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(crd, internal, nil); err != nil {
196validatorState.initError = err
197return
198}
199for _, ver := range internal.Spec.Versions {
200var sv validation.SchemaValidator
201var err error
202if ver.Schema != nil {
203sv, _, err = validation.NewSchemaValidator(ver.Schema.OpenAPIV3Schema)
204if err != nil {
205validatorState.initError = err
206return
207}
208}
209if internal.Spec.Validation != nil {
210sv, _, err = validation.NewSchemaValidator(internal.Spec.Validation.OpenAPIV3Schema)
211if err != nil {
212validatorState.initError = err
213return
214}
215}
216validatorState.validators[schema.GroupVersionKind{
217Group: internal.Spec.Group,
218Version: ver.Name,
219Kind: internal.Spec.Names.Kind,
220}] = sv
221}
222}
223})
224
225if validatorState.initError != nil {
226return nil, fmt.Errorf("failed to initialize validators: %w", validatorState.initError)
227}
228
229v, ok := validatorState.validators[policy.GetGroupVersionKind()]
230if !ok {
231return nil, fmt.Errorf("could not find validator for: " + policy.GetGroupVersionKind().String())
232}
233
234return v.Validate(policy), nil
235}
236
237type GenericTracingPolicy struct {
238k8sv1.TypeMeta
239Metadata k8sv1.ObjectMeta `json:"metadata"`
240Spec v1alpha1.TracingPolicySpec `json:"spec"`
241}
242
243func (gtp *GenericTracingPolicy) TpName() string {
244return gtp.Metadata.Name
245}
246
247func (gtp *GenericTracingPolicy) TpSpec() *v1alpha1.TracingPolicySpec {
248return >p.Spec
249}
250
251func (gtp *GenericTracingPolicy) TpInfo() string {
252return gtp.Metadata.Name
253}
254
255func FromYAML(data string) (TracingPolicy, error) {
256rawPolicy, namespaced, err := ApplyCRDDefault([]byte(data))
257if err != nil {
258return nil, fmt.Errorf("error applying CRD defaults: %w", err)
259}
260
261var policy K8sTracingPolicyObject
262if namespaced {
263policy = &GenericTracingPolicyNamespaced{}
264} else {
265policy = &GenericTracingPolicy{}
266}
267
268err = yaml.UnmarshalStrict(rawPolicy, &policy)
269if err != nil {
270return nil, fmt.Errorf("failed to unmarshal object with defaults: %w", err)
271}
272
273validationResult, err := ValidateCRD(policy)
274if err != nil {
275return nil, fmt.Errorf("validation failed %q: %w", policy.GetMetadata().Name, err)
276}
277
278if len(validationResult.Errors) > 0 {
279return nil, fmt.Errorf("validation failed: %q: %w", policy.GetMetadata().Name, validationResult.AsError())
280}
281
282return policy, nil
283}
284
285func FromFile(path string) (TracingPolicy, error) {
286policy, err := os.ReadFile(path)
287if err != nil {
288return nil, err
289}
290tp, err := FromYAML(string(policy))
291if err != nil {
292return nil, fmt.Errorf("failed loading tracing policy file %q: %w", path, err)
293}
294return tp, nil
295}
296
297type GenericTracingPolicyNamespaced struct {
298k8sv1.TypeMeta
299Metadata k8sv1.ObjectMeta `json:"metadata"`
300Spec v1alpha1.TracingPolicySpec `json:"spec"`
301}
302
303func (gtp *GenericTracingPolicyNamespaced) TpNamespace() string {
304return gtp.Metadata.Namespace
305}
306
307func (gtp *GenericTracingPolicyNamespaced) TpName() string {
308return gtp.Metadata.Name
309}
310
311func (gtp *GenericTracingPolicyNamespaced) TpSpec() *v1alpha1.TracingPolicySpec {
312return >p.Spec
313}
314
315func (gtp *GenericTracingPolicyNamespaced) TpInfo() string {
316return gtp.Metadata.Name
317}
318