github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/observer/auditfilter.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package observer 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 13 "github.com/juju/juju/controller" 14 "github.com/juju/juju/core/auditlog" 15 ) 16 17 // bufferedLog defers writing records to its destination audit log 18 // until it sees an interesting request - then all buffered messages 19 // and subsequent ones get forwarded on. 20 type bufferedLog struct { 21 mu sync.Mutex 22 buffer []interface{} 23 dest auditlog.AuditLog 24 interesting func(auditlog.Request) bool 25 } 26 27 // NewAuditLogFilter returns an auditlog.AuditLog that will only log 28 // conversations to the underlying log passed in if they include a 29 // request that satisfies the filter function passed in. 30 func NewAuditLogFilter(log auditlog.AuditLog, filter func(auditlog.Request) bool) auditlog.AuditLog { 31 return &bufferedLog{ 32 dest: log, 33 interesting: filter, 34 } 35 } 36 37 // AddConversation implements auditlog.AuditLog. 38 func (l *bufferedLog) AddConversation(c auditlog.Conversation) error { 39 l.mu.Lock() 40 defer l.mu.Unlock() 41 // We always buffer the conversation, since we don't know whether 42 // it will have any interesting requests yet. 43 l.deferMessage(c) 44 return nil 45 } 46 47 // AddRequest implements auditlog.AuditLog. 48 func (l *bufferedLog) AddRequest(r auditlog.Request) error { 49 l.mu.Lock() 50 if len(l.buffer) > 0 { 51 l.deferMessage(r) 52 var err error 53 if l.interesting(r) { 54 err = l.flush() 55 } 56 l.mu.Unlock() 57 return err 58 } 59 l.mu.Unlock() 60 // We've already flushed messages, forward this on 61 // immediately. 62 return l.dest.AddRequest(r) 63 } 64 65 // AddResponse implements auditlog.AuditLog. 66 func (l *bufferedLog) AddResponse(r auditlog.ResponseErrors) error { 67 l.mu.Lock() 68 if len(l.buffer) > 0 { 69 l.deferMessage(r) 70 l.mu.Unlock() 71 return nil 72 } 73 l.mu.Unlock() 74 // We've already flushed messages, forward this on 75 // immediately. 76 return l.dest.AddResponse(r) 77 } 78 79 // Close implements auditlog.AuditLog. 80 func (l *bufferedLog) Close() error { 81 return errors.Trace(l.dest.Close()) 82 } 83 84 func (l *bufferedLog) deferMessage(m interface{}) { 85 l.buffer = append(l.buffer, m) 86 } 87 88 func (l *bufferedLog) flush() error { 89 for _, message := range l.buffer { 90 var err error 91 switch m := message.(type) { 92 case auditlog.Conversation: 93 err = l.dest.AddConversation(m) 94 case auditlog.Request: 95 err = l.dest.AddRequest(m) 96 case auditlog.ResponseErrors: 97 err = l.dest.AddResponse(m) 98 default: 99 err = errors.Errorf("unknown audit log message type %T %+v", m, m) 100 } 101 if err != nil { 102 return errors.Trace(err) 103 } 104 } 105 l.buffer = nil 106 return nil 107 } 108 109 // MakeInterestingRequestFilter takes a set of method names (as 110 // facade.method, e.g. "Client.FullStatus") that aren't very 111 // interesting from an auditing perspective, and returns a filter 112 // function for audit logging that will mark the request as 113 // interesting if it's a call to a method that isn't listed. If one of 114 // the entries is "ReadOnlyMethods", any method matching the fixed 115 // list of read-only methods below will also be considered 116 // uninteresting. 117 func MakeInterestingRequestFilter(excludeMethods set.Strings) func(auditlog.Request) bool { 118 return func(req auditlog.Request) bool { 119 methodName := fmt.Sprintf("%s.%s", req.Facade, req.Method) 120 if excludeMethods.Contains(methodName) { 121 return false 122 } 123 if excludeMethods.Contains(controller.ReadOnlyMethodsWildcard) { 124 return !readonlyMethods.Contains(methodName) 125 } 126 return true 127 } 128 } 129 130 var readonlyMethods = set.NewStrings( 131 // Collected by running read-only commands. 132 "Action.Actions", 133 "Action.ApplicationsCharmsActions", 134 "Action.FindActionsByNames", 135 "Action.FindActionTagsByPrefix", 136 "Application.GetConstraints", 137 "ApplicationOffers.ApplicationOffers", 138 "Backups.Info", 139 "Client.FullStatus", 140 "Client.GetModelConstraints", 141 "Client.StatusHistory", 142 "Controller.AllModels", 143 "Controller.ControllerConfig", 144 "Controller.GetControllerAccess", 145 "Controller.ModelConfig", 146 "Controller.ModelStatus", 147 "MetricsDebug.GetMetrics", 148 "ModelConfig.ModelGet", 149 "ModelManager.ModelInfo", 150 "ModelManager.ModelDefaults", 151 "Pinger.Ping", 152 "UserManager.UserInfo", 153 154 // Don't filter out Application.Get - since it includes secrets 155 // it's worthwhile to track when it's run, and it's not likely to 156 // swamp the log. 157 158 // All client facade methods that start with List. 159 "Action.ListAll", 160 "Action.ListPending", 161 "Action.ListRunning", 162 "Action.ListComplete", 163 "ApplicationOffers.ListApplicationOffers", 164 "Backups.List", 165 "Block.List", 166 "Charms.List", 167 "Controller.ListBlockedModels", 168 "FirewallRules.ListFirewallRules", 169 "ImageManager.ListImages", 170 "ImageMetadata.List", 171 "KeyManager.ListKeys", 172 "ModelManager.ListModels", 173 "ModelManager.ListModelSummaries", 174 "Payloads.List", 175 "PayloadsHookContext.List", 176 "Resources.ListResources", 177 "ResourcesHookContext.ListResources", 178 "Spaces.ListSpaces", 179 "Storage.ListStorageDetails", 180 "Storage.ListPools", 181 "Storage.ListVolumes", 182 "Storage.ListFilesystems", 183 "Subnets.ListSubnets", 184 )