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  )