1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package audit
18
19import (
20	"bytes"
21	"fmt"
22	"net/http"
23	"reflect"
24	"time"
25
26	"github.com/google/uuid"
27	"k8s.io/klog/v2"
28
29	authnv1 "k8s.io/api/authentication/v1"
30	"k8s.io/apimachinery/pkg/api/meta"
31	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32	"k8s.io/apimachinery/pkg/runtime"
33	"k8s.io/apimachinery/pkg/runtime/schema"
34	"k8s.io/apimachinery/pkg/types"
35	utilnet "k8s.io/apimachinery/pkg/util/net"
36	auditinternal "k8s.io/apiserver/pkg/apis/audit"
37	"k8s.io/apiserver/pkg/authentication/user"
38	"k8s.io/apiserver/pkg/authorization/authorizer"
39)
40
41const (
42	maxUserAgentLength      = 1024
43	userAgentTruncateSuffix = "...TRUNCATED"
44)
45
46func NewEventFromRequest(req *http.Request, requestReceivedTimestamp time.Time, level auditinternal.Level, attribs authorizer.Attributes) (*auditinternal.Event, error) {
47	ev := &auditinternal.Event{
48		RequestReceivedTimestamp: metav1.NewMicroTime(requestReceivedTimestamp),
49		Verb:                     attribs.GetVerb(),
50		RequestURI:               req.URL.RequestURI(),
51		UserAgent:                maybeTruncateUserAgent(req),
52		Level:                    level,
53	}
54
55	// prefer the id from the headers. If not available, create a new one.
56	// TODO(audit): do we want to forbid the header for non-front-proxy users?
57	ids := req.Header.Get(auditinternal.HeaderAuditID)
58	if ids != "" {
59		ev.AuditID = types.UID(ids)
60	} else {
61		ev.AuditID = types.UID(uuid.New().String())
62	}
63
64	ips := utilnet.SourceIPs(req)
65	ev.SourceIPs = make([]string, len(ips))
66	for i := range ips {
67		ev.SourceIPs[i] = ips[i].String()
68	}
69
70	if user := attribs.GetUser(); user != nil {
71		ev.User.Username = user.GetName()
72		ev.User.Extra = map[string]authnv1.ExtraValue{}
73		for k, v := range user.GetExtra() {
74			ev.User.Extra[k] = authnv1.ExtraValue(v)
75		}
76		ev.User.Groups = user.GetGroups()
77		ev.User.UID = user.GetUID()
78	}
79
80	if attribs.IsResourceRequest() {
81		ev.ObjectRef = &auditinternal.ObjectReference{
82			Namespace:   attribs.GetNamespace(),
83			Name:        attribs.GetName(),
84			Resource:    attribs.GetResource(),
85			Subresource: attribs.GetSubresource(),
86			APIGroup:    attribs.GetAPIGroup(),
87			APIVersion:  attribs.GetAPIVersion(),
88		}
89	}
90
91	for _, kv := range auditAnnotationsFrom(req.Context()) {
92		LogAnnotation(ev, kv.key, kv.value)
93	}
94
95	return ev, nil
96}
97
98// LogImpersonatedUser fills in the impersonated user attributes into an audit event.
99func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) {
100	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
101		return
102	}
103	ae.ImpersonatedUser = &authnv1.UserInfo{
104		Username: user.GetName(),
105	}
106	ae.ImpersonatedUser.Groups = user.GetGroups()
107	ae.ImpersonatedUser.UID = user.GetUID()
108	ae.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{}
109	for k, v := range user.GetExtra() {
110		ae.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v)
111	}
112}
113
114// LogRequestObject fills in the request object into an audit event. The passed runtime.Object
115// will be converted to the given gv.
116func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) {
117	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
118		return
119	}
120
121	// complete ObjectRef
122	if ae.ObjectRef == nil {
123		ae.ObjectRef = &auditinternal.ObjectReference{}
124	}
125
126	// meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits
127	if meta, err := meta.Accessor(obj); err == nil {
128		if len(ae.ObjectRef.Namespace) == 0 {
129			ae.ObjectRef.Namespace = meta.GetNamespace()
130		}
131		if len(ae.ObjectRef.Name) == 0 {
132			ae.ObjectRef.Name = meta.GetName()
133		}
134		if len(ae.ObjectRef.UID) == 0 {
135			ae.ObjectRef.UID = meta.GetUID()
136		}
137		if len(ae.ObjectRef.ResourceVersion) == 0 {
138			ae.ObjectRef.ResourceVersion = meta.GetResourceVersion()
139		}
140	}
141	if len(ae.ObjectRef.APIVersion) == 0 {
142		ae.ObjectRef.APIGroup = gvr.Group
143		ae.ObjectRef.APIVersion = gvr.Version
144	}
145	if len(ae.ObjectRef.Resource) == 0 {
146		ae.ObjectRef.Resource = gvr.Resource
147	}
148	if len(ae.ObjectRef.Subresource) == 0 {
149		ae.ObjectRef.Subresource = subresource
150	}
151
152	if ae.Level.Less(auditinternal.LevelRequest) {
153		return
154	}
155
156	// TODO(audit): hook into the serializer to avoid double conversion
157	var err error
158	ae.RequestObject, err = encodeObject(obj, gvr.GroupVersion(), s)
159	if err != nil {
160		// TODO(audit): add error slice to audit event struct
161		klog.Warningf("Auditing failed of %v request: %v", reflect.TypeOf(obj).Name(), err)
162		return
163	}
164}
165
166// LogRequestPatch fills in the given patch as the request object into an audit event.
167func LogRequestPatch(ae *auditinternal.Event, patch []byte) {
168	if ae == nil || ae.Level.Less(auditinternal.LevelRequest) {
169		return
170	}
171
172	ae.RequestObject = &runtime.Unknown{
173		Raw:         patch,
174		ContentType: runtime.ContentTypeJSON,
175	}
176}
177
178// LogResponseObject fills in the response object into an audit event. The passed runtime.Object
179// will be converted to the given gv.
180func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) {
181	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
182		return
183	}
184	if status, ok := obj.(*metav1.Status); ok {
185		// selectively copy the bounded fields.
186		ae.ResponseStatus = &metav1.Status{
187			Status: status.Status,
188			Reason: status.Reason,
189			Code:   status.Code,
190		}
191	}
192
193	if ae.Level.Less(auditinternal.LevelRequestResponse) {
194		return
195	}
196	// TODO(audit): hook into the serializer to avoid double conversion
197	var err error
198	ae.ResponseObject, err = encodeObject(obj, gv, s)
199	if err != nil {
200		klog.Warningf("Audit failed for %q response: %v", reflect.TypeOf(obj).Name(), err)
201	}
202}
203
204func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) {
205	const mediaType = runtime.ContentTypeJSON
206	info, ok := runtime.SerializerInfoForMediaType(serializer.SupportedMediaTypes(), mediaType)
207	if !ok {
208		return nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType)
209	}
210
211	enc := serializer.EncoderForVersion(info.Serializer, gv)
212	var buf bytes.Buffer
213	if err := enc.Encode(obj, &buf); err != nil {
214		return nil, fmt.Errorf("encoding failed: %v", err)
215	}
216
217	return &runtime.Unknown{
218		Raw:         buf.Bytes(),
219		ContentType: runtime.ContentTypeJSON,
220	}, nil
221}
222
223// LogAnnotation fills in the Annotations according to the key value pair.
224func LogAnnotation(ae *auditinternal.Event, key, value string) {
225	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
226		return
227	}
228	if ae.Annotations == nil {
229		ae.Annotations = make(map[string]string)
230	}
231	if v, ok := ae.Annotations[key]; ok && v != value {
232		klog.Warningf("Failed to set annotations[%q] to %q for audit:%q, it has already been set to %q", key, value, ae.AuditID, ae.Annotations[key])
233		return
234	}
235	ae.Annotations[key] = value
236}
237
238// truncate User-Agent if too long, otherwise return it directly.
239func maybeTruncateUserAgent(req *http.Request) string {
240	ua := req.UserAgent()
241	if len(ua) > maxUserAgentLength {
242		ua = ua[:maxUserAgentLength] + userAgentTruncateSuffix
243	}
244
245	return ua
246}
247