github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/audit/choria.go (about)

     1  // Copyright (c) 2020-2023, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  // Package audit is a auditing system that's compatible with the
     6  // one found in the mcollective-choria Ruby project, log lines will
     7  // be identical and can be put in the same file as the ruby one
     8  package audit
     9  
    10  import (
    11  	"encoding/json"
    12  	"fmt"
    13  	"io/fs"
    14  	"os"
    15  	"os/user"
    16  	"strconv"
    17  	"sync"
    18  	"time"
    19  
    20  	log "github.com/sirupsen/logrus"
    21  
    22  	"github.com/choria-io/go-choria/config"
    23  	"github.com/choria-io/go-choria/protocol"
    24  )
    25  
    26  var mu = &sync.Mutex{}
    27  
    28  // Message is the format of a Choria audit log
    29  type Message struct {
    30  	TimeStamp   string          `json:"timestamp"`
    31  	RequestID   string          `json:"request_id"`
    32  	RequestTime int64           `json:"request_time"`
    33  	CallerID    string          `json:"caller"`
    34  	Sender      string          `json:"sender"`
    35  	Agent       string          `json:"agent"`
    36  	Action      string          `json:"action"`
    37  	Data        json.RawMessage `json:"data"`
    38  }
    39  
    40  // Request writes a audit log to a configured log
    41  func Request(request protocol.Request, agent string, action string, data json.RawMessage, cfg *config.Config) bool {
    42  	if !cfg.RPCAudit {
    43  		return false
    44  	}
    45  
    46  	logfile := cfg.Choria.RPCAuditLogfile
    47  	logfileGroup := cfg.Choria.RPCAuditLogfileGroup
    48  
    49  	logfileMode, err := strconv.ParseUint(cfg.Choria.RPCAuditLogFileMode, 0, 32)
    50  	if err != nil {
    51  		log.Errorf("Failed to parse plugin.rpcaudit.logfile.mode: %v", err)
    52  		return false
    53  	}
    54  
    55  	if logfile == "" {
    56  		log.Warnf("Choria RPC Auditing is enabled but no logfile is configured, skipping")
    57  		return false
    58  	}
    59  
    60  	amsg := Message{
    61  		TimeStamp:   time.Now().UTC().Format("2006-01-02T15:04:05.000000-0700"),
    62  		RequestID:   request.RequestID(),
    63  		RequestTime: request.Time().UTC().Unix(),
    64  		CallerID:    request.CallerID(),
    65  		Sender:      request.SenderID(),
    66  		Agent:       agent,
    67  		Action:      action,
    68  		Data:        data,
    69  	}
    70  
    71  	j, err := json.Marshal(amsg)
    72  	if err != nil {
    73  		log.Warnf("Auditing is not functional because the auditing data could not be represented as JSON: %s", err)
    74  		return false
    75  	}
    76  
    77  	mu.Lock()
    78  	defer mu.Unlock()
    79  
    80  	f, err := createAuditLog(logfile, logfileGroup, uint32(logfileMode))
    81  	if err != nil {
    82  		log.Warnf("Auditing is not functional because opening the logfile '%s' failed: %s", logfile, err)
    83  		return false
    84  	}
    85  	defer f.Close()
    86  
    87  	_, err = f.WriteString(fmt.Sprintf("%s\n", string(j)))
    88  	if err != nil {
    89  		log.Warnf("Auditing is not functional because writing to logfile '%s' failed: %s", logfile, err)
    90  		return false
    91  	}
    92  
    93  	return true
    94  }
    95  
    96  func createAuditLog(logfile string, logfileGroup string, logfileMode uint32) (*os.File, error) {
    97  	f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, fs.FileMode(logfileMode))
    98  	if err != nil {
    99  		return f, err
   100  	}
   101  
   102  	if logfileGroup == "" {
   103  		return f, nil
   104  	}
   105  
   106  	grp, err := user.LookupGroup(logfileGroup)
   107  	if err != nil {
   108  		f.Close()
   109  		return f, err
   110  	}
   111  
   112  	gid, err := strconv.Atoi(grp.Gid)
   113  	if err != nil {
   114  		f.Close()
   115  		return f, err
   116  	}
   117  
   118  	err = os.Chown(logfile, os.Getuid(), gid)
   119  	if err != nil {
   120  		f.Close()
   121  		return f, err
   122  	}
   123  
   124  	return f, nil
   125  }