github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/events/events.go (about) 1 /* 2 Copyright 2022 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package events 18 19 import ( 20 "crypto/sha256" 21 "encoding/hex" 22 "encoding/json" 23 24 "github.com/gravitational/trace" 25 "google.golang.org/protobuf/types/known/structpb" 26 "google.golang.org/protobuf/types/known/timestamppb" 27 28 auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" 29 "github.com/gravitational/teleport/api/utils" 30 ) 31 32 func trimN(s string, n int) string { 33 // Starting at 2 to leave room for quotes at the begging and end. 34 charCount := 2 35 for i, r := range s { 36 // Make sure we always have room to add an escape character if necessary. 37 if charCount+1 > n { 38 return s[:i] 39 } 40 if r == rune('"') || r == '\\' { 41 charCount++ 42 } 43 charCount++ 44 } 45 return s 46 } 47 48 func maxSizePerField(maxLength, customFields int) int { 49 if customFields == 0 { 50 return maxLength 51 } 52 return maxLength / customFields 53 } 54 55 // TrimToMaxSize trims the DatabaseSessionQuery message content. The maxSize is used to calculate 56 // per-field max size where only user input message fields DatabaseQuery and DatabaseQueryParameters are taken into 57 // account. 58 func (m *DatabaseSessionQuery) TrimToMaxSize(maxSize int) AuditEvent { 59 size := m.Size() 60 if size <= maxSize { 61 return m 62 } 63 64 out := utils.CloneProtoMsg(m) 65 out.DatabaseQuery = "" 66 out.DatabaseQueryParameters = nil 67 68 // Use 10% max size ballast + message size without custom fields. 69 sizeBallast := maxSize/10 + out.Size() 70 maxSize -= sizeBallast 71 72 // Check how many custom fields are set. 73 customFieldsCount := 0 74 if m.DatabaseQuery != "" { 75 customFieldsCount++ 76 } 77 for range m.DatabaseQueryParameters { 78 customFieldsCount++ 79 } 80 81 maxFieldsSize := maxSizePerField(maxSize, customFieldsCount) 82 83 out.DatabaseQuery = trimN(m.DatabaseQuery, maxFieldsSize) 84 if m.DatabaseQueryParameters != nil { 85 out.DatabaseQueryParameters = make([]string, len(m.DatabaseQueryParameters)) 86 } 87 for i, v := range m.DatabaseQueryParameters { 88 out.DatabaseQueryParameters[i] = trimN(v, maxFieldsSize) 89 } 90 return out 91 } 92 93 // TrimToMaxSize trims the SessionStart event to the given maximum size. 94 // Currently assumes that the largest field will be InitialCommand and tries to 95 // trim that. 96 func (e *SessionStart) TrimToMaxSize(maxSize int) AuditEvent { 97 size := e.Size() 98 if size <= maxSize { 99 return e 100 } 101 102 out := utils.CloneProtoMsg(e) 103 out.InitialCommand = nil 104 105 // Use 10% max size ballast + message size without InitialCommand 106 sizeBallast := maxSize/10 + out.Size() 107 maxSize -= sizeBallast 108 109 maxFieldSize := maxSizePerField(maxSize, len(e.InitialCommand)) 110 111 out.InitialCommand = make([]string, len(e.InitialCommand)) 112 for i, c := range e.InitialCommand { 113 out.InitialCommand[i] = trimN(c, maxFieldSize) 114 } 115 116 return out 117 } 118 119 // TrimToMaxSize trims the Exec event to the given maximum size. 120 // Currently assumes that the largest field will be Command and tries to trim 121 // that. 122 func (e *Exec) TrimToMaxSize(maxSize int) AuditEvent { 123 size := e.Size() 124 if size <= maxSize { 125 return e 126 } 127 128 out := utils.CloneProtoMsg(e) 129 out.Command = "" 130 131 // Use 10% max size ballast + message size without Command 132 sizeBallast := maxSize/10 + out.Size() 133 maxSize -= sizeBallast 134 135 out.Command = trimN(e.Command, maxSize) 136 137 return out 138 } 139 140 // TrimToMaxSize trims the UserLogin event to the given maximum size. 141 // The initial implementation is to cover concerns that a malicious user could 142 // craft a request that creates error messages too large to be handled by the 143 // underlying storage and thus cause the events to be omitted entirely. See 144 // teleport-private#172. 145 func (e *UserLogin) TrimToMaxSize(maxSize int) AuditEvent { 146 size := e.Size() 147 if size <= maxSize { 148 return e 149 } 150 151 out := utils.CloneProtoMsg(e) 152 out.Status.Error = "" 153 out.Status.UserMessage = "" 154 155 // Use 10% max size ballast + message size without Error and UserMessage 156 sizeBallast := maxSize/10 + out.Size() 157 maxSize -= sizeBallast 158 159 maxFieldSize := maxSizePerField(maxSize, 2) 160 161 out.Status.Error = trimN(e.Status.Error, maxFieldSize) 162 out.Status.UserMessage = trimN(e.Status.UserMessage, maxFieldSize) 163 164 return out 165 } 166 167 // ToUnstructured converts the event stored in the AuditEvent interface 168 // to unstructured. 169 // If the event is a session print event, it is converted to a plugins printEvent struct 170 // which is then converted to structpb.Struct. Otherwise the event is marshaled directly. 171 func ToUnstructured(evt AuditEvent) (*auditlogpb.EventUnstructured, error) { 172 payload, err := json.Marshal(evt) 173 if err != nil { 174 return nil, trace.Wrap(err) 175 } 176 id := computeEventID(evt, payload) 177 if err != nil { 178 return nil, trace.Wrap(err) 179 } 180 181 str := &structpb.Struct{} 182 if err := str.UnmarshalJSON(payload); err != nil { 183 return nil, trace.Wrap(err) 184 } 185 186 // If the event is a session print event, convert it to a printEvent struct 187 // to include the `data` field in the JSON. 188 if p, ok := evt.(*SessionPrint); ok { 189 const printEventDataKey = "data" 190 // append the `data` field to the unstructured event 191 str.Fields[printEventDataKey], err = structpb.NewValue(p.Data) 192 if err != nil { 193 return nil, trace.Wrap(err) 194 } 195 } 196 197 return &auditlogpb.EventUnstructured{ 198 Type: evt.GetType(), 199 Index: evt.GetIndex(), 200 Time: timestamppb.New(evt.GetTime()), 201 Id: id, 202 Unstructured: str, 203 }, nil 204 } 205 206 // computeEventID computes the ID of the event. If the event already has an ID, it is returned. 207 // Otherwise, the event is marshaled to JSON and the SHA256 hash of the JSON is returned. 208 func computeEventID(evt AuditEvent, payload []byte) string { 209 id := evt.GetID() 210 if id != "" { 211 return id 212 } 213 214 hash := sha256.Sum256(payload) 215 return hex.EncodeToString(hash[:]) 216 }