1
16
17 package utils
18
19 import (
20 "bufio"
21 "fmt"
22 "io"
23 "reflect"
24 "sort"
25 "strings"
26
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
31 auditinternal "k8s.io/apiserver/pkg/apis/audit"
32 "k8s.io/apiserver/pkg/audit"
33 )
34
35
36 type AuditEvent struct {
37 ID types.UID
38 Level auditinternal.Level
39 Stage auditinternal.Stage
40 RequestURI string
41 Verb string
42 Code int32
43 User string
44 ImpersonatedUser string
45 ImpersonatedGroups string
46 Resource string
47 Namespace string
48 RequestObject bool
49 ResponseObject bool
50 AuthorizeDecision string
51
52
53
54 AdmissionWebhookMutationAnnotations map[string]string
55 AdmissionWebhookPatchAnnotations map[string]string
56
57
58 CustomAuditAnnotations map[string]string
59 }
60
61 type AuditAnnotationsFilter func(key, val string) bool
62
63
64 type MissingEventsReport struct {
65 FirstEventChecked *auditinternal.Event
66 LastEventChecked *auditinternal.Event
67 NumEventsChecked int
68 MissingEvents []AuditEvent
69 }
70
71
72 func (m *MissingEventsReport) String() string {
73 return fmt.Sprintf(`missing %d events
74
75 - first event checked: %#v
76
77 - last event checked: %#v
78
79 - number of events checked: %d
80
81 - missing events: %#v`, len(m.MissingEvents), m.FirstEventChecked, m.LastEventChecked, m.NumEventsChecked, m.MissingEvents)
82 }
83
84
85 func CheckAuditLines(stream io.Reader, expected []AuditEvent, version schema.GroupVersion) (missingReport *MissingEventsReport, err error) {
86 return CheckAuditLinesFiltered(stream, expected, version, nil)
87 }
88
89
90
91
92 func CheckAuditLinesFiltered(stream io.Reader, expected []AuditEvent, version schema.GroupVersion, customAnnotationsFilter AuditAnnotationsFilter) (missingReport *MissingEventsReport, err error) {
93 expectations := newAuditEventTracker(expected)
94
95 scanner := bufio.NewScanner(stream)
96
97 missingReport = &MissingEventsReport{
98 MissingEvents: expected,
99 }
100
101 var i int
102 for i = 0; scanner.Scan(); i++ {
103 line := scanner.Text()
104
105 e := &auditinternal.Event{}
106 decoder := audit.Codecs.UniversalDecoder(version)
107 if err := runtime.DecodeInto(decoder, []byte(line), e); err != nil {
108 return missingReport, fmt.Errorf("failed decoding buf: %s, apiVersion: %s", line, version)
109 }
110 if i == 0 {
111 missingReport.FirstEventChecked = e
112 }
113 missingReport.LastEventChecked = e
114
115 event, err := testEventFromInternalFiltered(e, customAnnotationsFilter)
116 if err != nil {
117 return missingReport, err
118 }
119
120 expectations.Mark(event)
121 }
122 if err := scanner.Err(); err != nil {
123 return missingReport, err
124 }
125
126 missingReport.MissingEvents = expectations.Missing()
127 missingReport.NumEventsChecked = i
128 return missingReport, nil
129 }
130
131
132 func CheckAuditList(el auditinternal.EventList, expected []AuditEvent) (missing []AuditEvent, err error) {
133 expectations := newAuditEventTracker(expected)
134
135 for _, e := range el.Items {
136 event, err := testEventFromInternal(&e)
137 if err != nil {
138 return expected, err
139 }
140
141 expectations.Mark(event)
142 }
143
144 return expectations.Missing(), nil
145 }
146
147
148 func CheckForDuplicates(el auditinternal.EventList) (auditinternal.EventList, error) {
149
150 existingEvents := []AuditEvent{}
151 duplicates := auditinternal.EventList{}
152 for _, e := range el.Items {
153 event, err := testEventFromInternal(&e)
154 if err != nil {
155 return duplicates, err
156 }
157 event.ID = e.AuditID
158 for _, existing := range existingEvents {
159 if reflect.DeepEqual(existing, event) {
160 duplicates.Items = append(duplicates.Items, e)
161 continue
162 }
163 }
164 existingEvents = append(existingEvents, event)
165 }
166
167 var err error
168 if len(duplicates.Items) > 0 {
169 err = fmt.Errorf("failed duplicate check")
170 }
171
172 return duplicates, err
173 }
174
175
176 func testEventFromInternal(e *auditinternal.Event) (AuditEvent, error) {
177 return testEventFromInternalFiltered(e, nil)
178 }
179
180
181
182
183 func testEventFromInternalFiltered(e *auditinternal.Event, customAnnotationsFilter AuditAnnotationsFilter) (AuditEvent, error) {
184 event := AuditEvent{
185 Level: e.Level,
186 Stage: e.Stage,
187 RequestURI: e.RequestURI,
188 Verb: e.Verb,
189 User: e.User.Username,
190 }
191 if e.ObjectRef != nil {
192 event.Namespace = e.ObjectRef.Namespace
193 event.Resource = e.ObjectRef.Resource
194 }
195 if e.ResponseStatus != nil {
196 event.Code = e.ResponseStatus.Code
197 }
198 if e.ResponseObject != nil {
199 event.ResponseObject = true
200 }
201 if e.RequestObject != nil {
202 event.RequestObject = true
203 }
204 if e.ImpersonatedUser != nil {
205 event.ImpersonatedUser = e.ImpersonatedUser.Username
206 sort.Strings(e.ImpersonatedUser.Groups)
207 event.ImpersonatedGroups = strings.Join(e.ImpersonatedUser.Groups, ",")
208 }
209 event.AuthorizeDecision = e.Annotations["authorization.k8s.io/decision"]
210 for k, v := range e.Annotations {
211 if strings.HasPrefix(k, mutating.PatchAuditAnnotationPrefix) {
212 if event.AdmissionWebhookPatchAnnotations == nil {
213 event.AdmissionWebhookPatchAnnotations = map[string]string{}
214 }
215 event.AdmissionWebhookPatchAnnotations[k] = v
216 } else if strings.HasPrefix(k, mutating.MutationAuditAnnotationPrefix) {
217 if event.AdmissionWebhookMutationAnnotations == nil {
218 event.AdmissionWebhookMutationAnnotations = map[string]string{}
219 }
220 event.AdmissionWebhookMutationAnnotations[k] = v
221 } else if customAnnotationsFilter != nil && customAnnotationsFilter(k, v) {
222 if event.CustomAuditAnnotations == nil {
223 event.CustomAuditAnnotations = map[string]string{}
224 }
225 event.CustomAuditAnnotations[k] = v
226 }
227 }
228 return event, nil
229 }
230
231
232 type auditEvent struct {
233 event AuditEvent
234 found bool
235 }
236
237
238 type auditEventTracker struct {
239 events []*auditEvent
240 }
241
242
243 func newAuditEventTracker(expected []AuditEvent) *auditEventTracker {
244 expectations := &auditEventTracker{events: []*auditEvent{}}
245 for _, event := range expected {
246
247 expectations.events = append(expectations.events, &auditEvent{event: event, found: false})
248 }
249 return expectations
250 }
251
252
253 func (t *auditEventTracker) Mark(event AuditEvent) {
254 for _, e := range t.events {
255 if reflect.DeepEqual(e.event, event) {
256 e.found = true
257 }
258 }
259 }
260
261
262 func (t *auditEventTracker) Missing() []AuditEvent {
263 var missing []AuditEvent
264 for _, e := range t.events {
265 if !e.found {
266 missing = append(missing, e.event)
267 }
268 }
269 return missing
270 }
271
View as plain text