Compare commits

...

14 Commits

6 changed files with 1379 additions and 1059 deletions

View File

@ -18,7 +18,7 @@ const (
// FIXME: Not very strict
IPv6RE = `(?:(?:[0-9a-fA-F]{1,4}\:){7})[0-9a-fA-F]{1,4}`
HostRE = `([0-9A-Za-z\-\_\.]*)`
ProcessRE = `(slapd\[[0-9]{1,5}\])`
ProcessRE = `(slapd\[[0-9]{1,7}\])(?:\:)?`
// group[4]
ConnIdRE = `conn=([0-9]{4,10})`
ConnFdRE = `(?:fd=([0-9]{1,10}))?`
@ -43,10 +43,14 @@ const (
UnbindRE = `(UNBIND)?`
// group[40]
ConnClosedRE = `(closed)?(?: \(connection lost\))?`
// group[41]
AddDnRE = `(?:ADD dn="(.*)")?`
// group[42]
DelDnRE = `(?:DEL dn="(.*)")?`
LogLineRE = SyslogPri + TimeRE + ` ` + HostRE + ` ` + ProcessRE + ` ` + ConnIdRE + ` ` + ConnFdRE + OperationIdRE + ` ` +
AcceptRE + STARTTLSRE + BindMethodRE + BindMechRE + ResultRE + SearchBaseRE + SearchAttrRE + SearchResultRE + ModDnRE + ModAttrRE +
PassModRE + UnbindRE + ConnClosedRE
PassModRE + UnbindRE + ConnClosedRE + AddDnRE + DelDnRE
)
type (
@ -74,6 +78,8 @@ type (
SSF string `json:"ssf"`
ModDN string `json:"mod_dn"`
ModAttr string `json:"mod_attr"`
AddDN string `json:"add_dn"`
DelDN string `json:"del_dn"`
PassModDN string `json:"passmod_dn"`
Result bool
ResTag string `json:"result_tag"`
@ -184,10 +190,12 @@ func (o *OpenldapLog) Parse(text []byte) (LogFormat, error) {
ModDN: string(group[36]),
ModAttr: string(group[37]),
PassModDN: string(group[38]),
AddDN: string(group[41]),
DelDN: string(group[42]),
}
// Now handle Operation Type
if len(group[11]) > 0 || len(group[13]) > 0 || len(group[14]) > 0 || len(group[15]) > 0 {
if len(group[12]) > 0 || len(group[13]) > 0 || len(group[14]) > 0 || len(group[15]) > 0 || len(group[16]) > 0 {
logFormat.OpType = "bind"
} else if len(group[11]) > 0 {
logFormat.OpType = "starttls"
@ -203,6 +211,10 @@ func (o *OpenldapLog) Parse(text []byte) (LogFormat, error) {
logFormat.OpType = "unbind"
} else if len(group[40]) > 0 {
logFormat.OpType = "close"
} else if len(group[41]) > 0 {
logFormat.OpType = "add"
} else if len(group[42]) > 0 {
logFormat.OpType = "del"
}
return logFormat, nil

View File

@ -39,10 +39,10 @@ type (
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
BindDN *string `json:"bind_dn"`
BindMethod *string `json:"bind_method"`
BindMech *string `json:"bind_mech"`
BindSSF *string `json:"bind_ssf"`
SSF *string `json:"ssf"`
BindMethod *string `json:"bind_method,omitempty"`
BindMech *string `json:"bind_mech,omitempty"`
BindSSF *string `json:"bind_ssf,omitempty"`
SSF *string `json:"ssf,omitempty"`
StartTLS bool `json:"starttls"`
}
@ -57,6 +57,8 @@ type (
SSF string `json:"ssf,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
AddDN string `json:"add_dn,omitempty"`
DelDN string `json:"del_dn,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
@ -100,6 +102,8 @@ type (
StartTLS bool `json:"starttls,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
AddDN string `json:"add_dn,omitempty"`
DelDN string `json:"del_dn,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
@ -125,7 +129,7 @@ var (
File os.File
Writer *bufio.Writer
Version = "0.6.5"
Version = "0.6.13"
BuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "openldaplogparser_build_info",
@ -151,34 +155,46 @@ var (
Name: "openldaplogparser_client_count",
Help: "Number of connected clients",
})
AcceptCnt = promauto.NewCounterVec(prometheus.CounterOpts{
AcceptCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_accept_count",
Help: "Number of ACCEPT commands executed",
}, []string{"host"})
BindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
BindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_bind_count",
Help: "Number of BIND commands executed",
}, []string{"host"})
SearchCnt = promauto.NewCounterVec(prometheus.CounterOpts{
SearchCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_search_count",
Help: "Number of SRCH commands executed",
}, []string{"host"})
ModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
ModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_mod_count",
Help: "Number of MOD commands executed",
}, []string{"host"})
PassModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
AddCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_add_count",
Help: "Number of ADD commands executed",
}, []string{"host"})
DelCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_del_count",
Help: "Number of DEL commands executed",
}, []string{"host"})
PassModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_passmod_count",
Help: "Number of PASSMOD commands executed",
}, []string{"host"})
UnbindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
UnbindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_unbind_count",
Help: "Number of UNBIND commands executed",
}, []string{"host"})
CloseCnt = promauto.NewCounterVec(prometheus.CounterOpts{
CloseCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_close_count",
Help: "Number of closed connections",
}, []string{"host"})
StartTLSCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_starttlscount",
Help: "Number of STARTTLS commands executed",
}, []string{"host"})
rootCmd = &cobra.Command{
Use: "openldap-log-parser",
@ -196,6 +212,7 @@ var (
gPromMetricPath string
gDebug bool
gDispUnkConn bool
)
func Execute() {
@ -212,7 +229,7 @@ func OlcToFlat(olc *OpenLdapConnection) []OpenLdapConnectionFlat {
for i := range olc.Operations {
olcf[i] = OpenLdapConnectionFlat{
Time: olc.Time,
Time: olc.Operations[i].Time,
Hostname: olc.Hostname,
Process: olc.Process,
ClientIp: olc.ClientIp,
@ -268,6 +285,22 @@ func OlcToFlat(olc *OpenLdapConnection) []OpenLdapConnectionFlat {
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "add":
olcf[i].AddDN = olc.Operations[i].AddDN
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "del":
olcf[i].DelDN = olc.Operations[i].DelDN
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "passmod":
olcf[i].PassModDN = olc.Operations[i].PassModDN
olcf[i].ResTag = olc.Operations[i].ResTag
@ -316,35 +349,40 @@ func writeOut(msg string, filename string) error {
return nil
}
// Every 24H, remove sent, milter-rejected and deferred that entered queue more than 5 days ago
/*
func periodicallyCleanMQueue(mqueue map[int]*PostfixLogParser, mqMtx *sync.Mutex) {
var ok int
func cleanMQueue(mqueue map[string]*OpenLdapConnection, mqMtx *sync.Mutex, age time.Duration) {
var ok bool
for range time.Tick(time.Hour * 24) {
// Do we need read lock?
for _, inmail := range mqueue {
ok = 0
// Check all mails were sent (multiple destinations mails)
// or rejected
for _, outmail := range inmail.Messages {
if outmail.Status == "sent" || outmail.Status == "milter-reject" {
ok += 1
} else if outmail.Status == "deferred" {
if inmail.Time.Add(time.Hour * 5 * 24).Before(time.Now()) {
ok += 1
}
log.Printf("Start cleaning queue task: %d items in queue", len(mqueue))
// We need lock here
mqMtx.Lock()
for uid, ldcon := range mqueue {
ok = false
// Check if a close operation exist
for _, op := range ldcon.Operations {
if op.OpType == "close" {
if op.Time.Add(age).Before(time.Now()) {
ok = true
}
}
if ok == len(inmail.Messages) {
mqMtx.Lock()
delete(mqueue, inmail.MessageId)
mqMtx.Unlock()
}
}
if ok == true {
// We already in RW lock
delete(mqueue, uid)
}
}
mqMtx.Unlock()
log.Printf("Finished cleaning queue task: %d items in queue", len(mqueue))
}
// Every 24H, remove connections closed more than 24 hours ago
func periodicallyCleanMQueue(mqueue map[string]*OpenLdapConnection, mqMtx *sync.Mutex) {
for range time.Tick(time.Hour * 24) {
cleanMQueue(mqueue, mqMtx, 24 * time.Hour)
}
}
*/
func initConfig() {}
@ -359,6 +397,7 @@ func init() {
rootCmd.Flags().StringVarP(&gPromListenAddress, "prom.listen-address", "l", "do-not-listen", "Address to listen on for prometheus metrics")
rootCmd.Flags().StringVarP(&gPromMetricPath, "prom.telemetry-path", "m", "/metrics", "Path under which to expose metrics.")
rootCmd.Flags().BoolVarP(&gDebug, "debug", "d", false, "debug mode")
rootCmd.Flags().BoolVarP(&gDispUnkConn, "unknown", "u", false, "display operations missing connection details (b/c accept was not seen)")
cobra.OnInitialize(initConfig)
}
@ -422,67 +461,96 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = nil
}
mqMtx.Lock()
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
mqMtx.Unlock()
AcceptCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=0 STARTTLS
If we don't have the initial connect, we will discard logs
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=0 STARTTLS
*/
if logFormat.OpType == "starttls" {
opexist := false
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// We may be here for the result of STARTTLS operation
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
opexist = true
break
}
}
if false == opexist {
op := &Operation{
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
olc.Operations = append(olc.Operations, op)
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
StartTLSCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
2022-07-18T17:18:19.785570+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
If we don't have the initial connect, we will discard logs
2022-07-18T17:18:19.785570+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
*/
if logFormat.BindDN != "" && logFormat.BindMethod != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// FIXME: What if this bind is not successful?
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if gDispUnkConn == false {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
olc.Operations = append(olc.Operations, op)
BindCnt.WithLabelValues(olc.Hostname).Inc()
}
}
// FIXME: What if this bind is not successful?
olc.BindDN = &logFormat.BindDN
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
BindCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
2022-07-18T17:18:19.786218+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
2022-07-18T17:18:19.786218+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
*/
if logFormat.BindDN != "" && logFormat.BindMech != "" {
mqMtx.Lock()
@ -496,6 +564,7 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
}
}
mqMtx.Unlock()
return nil
}
/*
@ -504,6 +573,7 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
*/
if logFormat.Result == true {
mqMtx.Lock()
// If we dont know conn_id here, then we also dont know operation which we are processing result, so we dont care
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
@ -537,27 +607,48 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
}
}
mqMtx.Unlock()
return nil
}
/*
2022-07-18T17:18:19.785881+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(cn=pika)"
*/
if logFormat.SearchBase != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
SearchBase: logFormat.SearchBase,
SearchScope: logFormat.SearchScope,
SearchDeref: logFormat.SearchDeref,
SearchFilter: logFormat.SearchFilter,
}
olc.Operations = append(olc.Operations, op)
SearchCnt.WithLabelValues(olc.Hostname).Inc()
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
SearchBase: logFormat.SearchBase,
SearchScope: logFormat.SearchScope,
SearchDeref: logFormat.SearchDeref,
SearchFilter: logFormat.SearchFilter,
}
mqMtx.Lock()
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
SearchCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
@ -574,6 +665,7 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
}
}
mqMtx.Unlock()
return nil
}
/*
@ -614,31 +706,49 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
}
}
mqMtx.Unlock()
return nil
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
ModDN: logFormat.ModDN,
}
olc.Operations = append(olc.Operations, op)
ModCnt.WithLabelValues(olc.Hostname).Inc()
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
ModDN: logFormat.ModDN,
}
mqMtx.Lock()
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
ModCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
2022-07-18T14:35:17.381233+02:00 ldap.domain.org slapd[82581] conn=16113 op=3 MOD attr=description
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModAttr != "" {
@ -652,26 +762,121 @@ func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *
}
}
mqMtx.Unlock()
return nil
}
/*
* 2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 ADD dn="cn=coincoin,dc=domain,dc=org"
*/
if logFormat.AddDN != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
AddDN: logFormat.AddDN,
}
mqMtx.Lock()
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
AddCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
* 2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 DEL dn="cn=coincoin,dc=domain,dc=org"
*/
if logFormat.DelDN != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
DelDN: logFormat.DelDN,
}
mqMtx.Lock()
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
DelCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
2022-07-18T11:13:17.521717+02:00 ldap.domain.org slapd[82581] conn=16113 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new
If we don't have the initial connect, we will discard logs
*/
if logFormat.PassModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
PassModDN: logFormat.PassModDN,
}
olc.Operations = append(olc.Operations, op)
PassModCnt.WithLabelValues(olc.Hostname).Inc()
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
PassModDN: logFormat.PassModDN,
}
mqMtx.Lock()
olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]
if false == ok {
if false == gDispUnkConn {
mqMtx.Unlock()
return nil
} else {
// Create connection
olc = &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
}
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
}
}
olc.Operations = append(olc.Operations, op)
mqMtx.Unlock()
PassModCnt.WithLabelValues(olc.Hostname).Inc()
return nil
}
/*
@ -879,7 +1084,18 @@ func processLogs(cmd *cobra.Command, args []string) {
}
// Cleaner thread
//go periodicallyCleanMQueue(mQueue, &mqMtx)
go periodicallyCleanMQueue(mQueue, &mqMtx)
// On demand Mqueue cleaning... For debug, dont try this at home, kids!
/* sig2 := make(chan os.Signal)
signal.Notify(sig2, syscall.SIGUSR2)
go func() {
for {
<-sig2
cleanMQueue(mQueue, &mqMtx, 5 * time.Minute)
}
}()
*/
// Initialize Stdin input...
if true == strings.EqualFold(gSyslogListenAddress, "do-not-listen") {

View File

@ -1,941 +0,0 @@
package cmd
import (
"os"
"fmt"
"log"
"net"
"sync"
"time"
"bufio"
"errors"
"runtime"
"strings"
"syscall"
"net/http"
"os/signal"
"encoding/json"
"github.com/spf13/cobra"
"github.com/tabalt/pidfile"
"github.com/prometheus/client_golang/prometheus"
openldaplog "git.nosd.in/yo/openldap-log-parser"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func init() {}
type (
OpenLdapConnection struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
Operations []*Operation `json:"operations"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
BindDN *string `json:"bind_dn"`
BindMethod *string `json:"bind_method"`
BindMech *string `json:"bind_mech"`
BindSSF *string `json:"bind_ssf"`
SSF *string `json:"ssf"`
StartTLS bool `json:"starttls"`
}
Operation struct {
Time *time.Time `json:"time"`
OpType string `json:"op_type"`
OpId *int `json:"op_id,omitempty"`
BindDN string `json:"bind_dn,omitempty"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
// To use "omitempty" on int, they have to be pointers
// This way it willl be displayed when set to 0, and not display when not set (null)
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResNEntries *int `json:"result_nentries,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
OpenLdapConnectionFlat struct {
Time *time.Time `json:"time"`
Hostname string `json:"hostname"`
Process string `json:"process"`
ClientIp string `json:"client_ip"`
ClientPort int `json:"client_port"`
ServerIp string `json:"server_ip"`
ServerPort int `json:"server_port"`
BindDN string `json:"bind_dn,omitempty"`
ConnId int `json:"conn_id"`
ConnFd int `json:"conn_fd"`
OpId *int `json:"op_id,omitempty"`
OpType string `json:"op_type"`
BindMethod string `json:"bind_method,omitempty"`
BindMech string `json:"bind_mech,omitempty"`
BindSSF string `json:"bind_ssf,omitempty"`
SSF string `json:"ssf,omitempty"`
StartTLS bool `json:"starttls,omitempty"`
ModDN string `json:"mod_dn,omitempty"`
ModAttr string `json:"mod_attr,omitempty"`
PassModDN string `json:"passmod_dn,omitempty"`
ResTag string `json:"result_tag,omitempty"`
ResOid string `json:"result_oid,omitempty"`
ResErr *int `json:"result_err,omitempty"`
ResQTime string `json:"result_qtime,omitempty"`
ResETime string `json:"result_etime,omitempty"`
ResText string `json:"result_text,omitempty"`
SearchBase string `json:"search_base,omitempty"`
SearchScope string `json:"search_scope,omitempty"`
SearchDeref string `json:"search_deref,omitempty"`
SearchFilter string `json:"search_filter,omitempty"`
SearchAttr string `json:"search_attr,omitempty"`
SearchResTag string `json:"search_res_tag,omitempty"`
SearchResErr *int `json:"search_res_err,omitempty"`
SearchResQTime string `json:"search_res_qtime,omitempty"`
SearchResETime string `json:"search_res_etime,omitempty"`
SearchResNEntries *int `json:"search_res_nentries,omitempty"`
SearchResText string `json:"search_res_text,omitempty"`
}
)
var (
File os.File
Writer *bufio.Writer
Version = "0.7.0a"
BuildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "openldaplogparser_build_info",
Help: "Constant 1 value labeled by version and goversion from which openldap-log-parser was built",
}, []string{"version", "goversion"})
StartTime = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_time_start_seconds",
Help: "Process start time in UNIX timestamp (seconds)",
})
LineReadCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_read_count",
Help: "Number of lines read",
})
LineIncorrectCnt = promauto.NewCounter(prometheus.CounterOpts{
Name: "openldaplogparser_line_incorrect_count",
Help: "Number of lines with incorrect format",
})
LineOutCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_line_out_count",
Help: "Number of lines written to ouput",
}, []string{"host"})
ConnectedClientCnt = promauto.NewGauge(prometheus.GaugeOpts{
Name: "openldaplogparser_client_count",
Help: "Number of connected clients",
})
AcceptCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_accept_count",
Help: "Number of ACCEPT commands executed",
}, []string{"host"})
BindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_bind_count",
Help: "Number of BIND commands executed",
}, []string{"host"})
SearchCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_search_count",
Help: "Number of SRCH commands executed",
}, []string{"host"})
ModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_mod_count",
Help: "Number of MOD commands executed",
}, []string{"host"})
PassModCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_passmod_count",
Help: "Number of PASSMOD commands executed",
}, []string{"host"})
UnbindCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_unbind_count",
Help: "Number of UNBIND commands executed",
}, []string{"host"})
CloseCnt = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "openldaplogparser_close_count",
Help: "Number of closed connections",
}, []string{"host"})
rootCmd = &cobra.Command{
Use: "openldap-log-parser",
Short: "OpenLDAP Log Parser v" + Version + ". Parse openldap log, and output json format",
Run: func(cmd *cobra.Command, args []string) {
processLogs(cmd, args)
},
}
gFlatten bool
gOutputFile string
gPidFilePath string
gSyslogListenAddress string
gPromListenAddress string
gPromMetricPath string
gDebug bool
gDispUnkConn bool
)
func Execute() {
if err := rootCmd.Execute(); err != nil {
rootCmd.SetOutput(os.Stderr)
rootCmd.Println(err)
os.Exit(1)
}
}
// Flatten OpenLdapConnection by creating an item for each operation
func OlcToFlat(olc *OpenLdapConnection) []OpenLdapConnectionFlat {
var olcf = make([]OpenLdapConnectionFlat, len(olc.Operations))
for i := range olc.Operations {
olcf[i] = OpenLdapConnectionFlat{
Time: olc.Time,
Hostname: olc.Hostname,
Process: olc.Process,
ClientIp: olc.ClientIp,
ClientPort: olc.ClientPort,
ServerIp: olc.ServerIp,
ServerPort: olc.ServerPort,
ConnId: olc.ConnId,
ConnFd: olc.ConnFd,
OpType: olc.Operations[i].OpType,
OpId: olc.Operations[i].OpId,
}
if olc.BindDN != nil {
olcf[i].BindDN = *olc.BindDN
}
switch olc.Operations[i].OpType {
case "starttls":
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "bind":
olcf[i].BindMethod = olc.Operations[i].BindMethod
olcf[i].BindMech = olc.Operations[i].BindMech
olcf[i].BindSSF = olc.Operations[i].BindSSF
olcf[i].SSF = olc.Operations[i].SSF
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "search":
olcf[i].SearchBase = olc.Operations[i].SearchBase
olcf[i].SearchScope = olc.Operations[i].SearchScope
olcf[i].SearchDeref = olc.Operations[i].SearchDeref
olcf[i].SearchFilter = olc.Operations[i].SearchFilter
olcf[i].SearchAttr = olc.Operations[i].SearchAttr
olcf[i].SearchResTag = olc.Operations[i].SearchResTag
olcf[i].SearchResErr = olc.Operations[i].SearchResErr
olcf[i].SearchResQTime = olc.Operations[i].SearchResQTime
olcf[i].SearchResETime = olc.Operations[i].SearchResETime
olcf[i].SearchResNEntries = olc.Operations[i].SearchResNEntries
olcf[i].SearchResText = olc.Operations[i].SearchResText
case "mod":
olcf[i].ModDN = olc.Operations[i].ModDN
olcf[i].ModAttr = olc.Operations[i].ModAttr
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
case "passmod":
olcf[i].PassModDN = olc.Operations[i].PassModDN
olcf[i].ResTag = olc.Operations[i].ResTag
olcf[i].ResOid = olc.Operations[i].ResOid
olcf[i].ResErr = olc.Operations[i].ResErr
olcf[i].ResQTime = olc.Operations[i].ResQTime
olcf[i].ResETime = olc.Operations[i].ResETime
olcf[i].ResText = olc.Operations[i].ResText
}
}
return olcf
}
func NewWriter(file string) (*bufio.Writer, *os.File, error) {
if len(file) > 0 {
var f *os.File
var err error
if _, err = os.Stat(file); err == nil {
f, err = os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0640)
} else if os.IsNotExist(err) {
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0640)
}
if err != nil {
return nil, nil, err
}
Writer = bufio.NewWriter(f)
return Writer, f, nil
} else {
Writer = bufio.NewWriter(os.Stdout)
return Writer, nil, nil
}
}
func writeOut(msg string, filename string) error {
_, err := fmt.Fprintln(Writer, msg)
Writer.Flush()
if err != nil {
return err
}
var tmpOlc OpenLdapConnection
json.Unmarshal([]byte(msg), &tmpOlc)
LineOutCnt.WithLabelValues(tmpOlc.Hostname).Inc()
return nil
}
// Every 24H, remove sent, milter-rejected and deferred that entered queue more than 5 days ago
/*
func periodicallyCleanMQueue(mqueue map[int]*PostfixLogParser, mqMtx *sync.Mutex) {
var ok int
for range time.Tick(time.Hour * 24) {
// Do we need read lock?
for _, inmail := range mqueue {
ok = 0
// Check all mails were sent (multiple destinations mails)
// or rejected
for _, outmail := range inmail.Messages {
if outmail.Status == "sent" || outmail.Status == "milter-reject" {
ok += 1
} else if outmail.Status == "deferred" {
if inmail.Time.Add(time.Hour * 5 * 24).Before(time.Now()) {
ok += 1
}
}
}
if ok == len(inmail.Messages) {
mqMtx.Lock()
delete(mqueue, inmail.MessageId)
mqMtx.Unlock()
}
}
}
}
*/
func initConfig() {}
func init() {
rootCmd.Version = Version
rootCmd.Flags().BoolVarP(&gFlatten, "flatten", "f", false, "Flatten output for using with syslog")
rootCmd.Flags().StringVarP(&gOutputFile, "out", "o", "", "Output to file, append if exists")
rootCmd.Flags().StringVarP(&gPidFilePath, "pidfile", "p", "", "pid file path")
rootCmd.Flags().StringVarP(&gSyslogListenAddress, "syslog.listen-address", "s", "do-not-listen", "Address to listen on for syslog incoming messages. Default is to parse stdin")
rootCmd.Flags().StringVarP(&gPromListenAddress, "prom.listen-address", "l", "do-not-listen", "Address to listen on for prometheus metrics")
rootCmd.Flags().StringVarP(&gPromMetricPath, "prom.telemetry-path", "m", "/metrics", "Path under which to expose metrics.")
rootCmd.Flags().BoolVarP(&gDebug, "debug", "d", false, "debug mode")
rootCmd.Flags().BoolVarP(&gDebug, "unknown", "u", false, "display operations without connection (b/c accept was not seen)")
cobra.OnInitialize(initConfig)
}
/*
* This is the function doing the work.
* Each input is stored in a map - indexed by "hostname+conn_id" so we can handle many hosts -
* then written to output when we recognize it as the last line
*/
func parseStoreAndWrite(input []byte, mq map[string]*OpenLdapConnection, mqMtx *sync.Mutex,
outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
logFormat, err := o.Parse(input)
if err != nil {
// Incorrect line, just skip it
if err.Error() == "Error: Line do not match regex" {
LineIncorrectCnt.Inc()
return err
}
return err
}
/*
2022-07-18T17:18:19.785512+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 ACCEPT from IP=10.11.12.13:55689 (IP=0.0.0.0:389)
*/
if logFormat.ClientIp != "" {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
var ops []*Operation
ops = append(ops, op)
olc := &OpenLdapConnection{
Time: logFormat.Time,
Hostname: logFormat.Hostname,
Process: logFormat.Process,
ConnId: logFormat.ConnId,
ConnFd: logFormat.ConnFd,
ClientIp: logFormat.ClientIp,
ClientPort: logFormat.ClientPort,
ServerIp: logFormat.ServerIp,
ServerPort: logFormat.ServerPort,
Operations: ops,
}
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[0])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = nil
}
mqMtx.Lock()
mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)] = olc
mqMtx.Unlock()
AcceptCnt.WithLabelValues(olc.Hostname).Inc()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=0 STARTTLS
If we don't have the initial connect, we will discard logs
*/
if logFormat.OpType == "starttls" {
opexist := false
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// We may be here for the result of STARTTLS operation
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
opexist = true
break
}
}
if false == opexist {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785570+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
If we don't have the initial connect, we will discard logs
*/
if logFormat.BindDN != "" && logFormat.BindMethod != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
// FIXME: What if this bind is not successful?
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
olc.Operations = append(olc.Operations, op)
BindCnt.WithLabelValues(olc.Hostname).Inc()
} else {
if gDispUnkConn == true {
// use conn_id = 0
olc, ok := mq[fmt.Sprintf("%s:0", logFormat.Hostname)]
if false == ok {
// Create connection with conn_id = 0
}
olc.BindDN = &logFormat.BindDN
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
BindDN: logFormat.BindDN,
BindMethod: logFormat.BindMethod,
}
olc.Operations = append(olc.Operations, op)
BindCntUnk.WithLabelValues(olc.Hostname).Inc()
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.786218+02:00 ldap.domain.org slapd[82581] conn=16113 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
*/
if logFormat.BindDN != "" && logFormat.BindMech != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].BindMech = logFormat.BindMech
olc.Operations[i].BindSSF = logFormat.BindSSF
olc.Operations[i].SSF = logFormat.SSF
}
}
}
mqMtx.Unlock()
}
/*
// Can be the result of many operation types
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=0 RESULT tag=97 err=0 text=
*/
if logFormat.Result == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ResTag = logFormat.ResTag
olc.Operations[i].ResOid = logFormat.ResOid
olc.Operations[i].ResErr = &logFormat.ResErr
olc.Operations[i].ResQTime = logFormat.ResQTime
olc.Operations[i].ResETime = logFormat.ResETime
olc.Operations[i].ResText = logFormat.ResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785881+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(cn=pika)"
*/
if logFormat.SearchBase != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
SearchBase: logFormat.SearchBase,
SearchScope: logFormat.SearchScope,
SearchDeref: logFormat.SearchDeref,
SearchFilter: logFormat.SearchFilter,
}
olc.Operations = append(olc.Operations, op)
SearchCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785897+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SRCH attr=dn
*/
if logFormat.SearchAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchAttr = logFormat.SearchAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785989+02:00 ldap.domain.org slapd[82581] conn=16113 op=2 SEARCH RESULT tag=101 err=0 nentries=1 text=
*/
if logFormat.SearchResult == true {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].SearchResTag = logFormat.SearchResTag
olc.Operations[i].SearchResErr = &logFormat.SearchResErr
olc.Operations[i].SearchResQTime = logFormat.SearchResQTime
olc.Operations[i].SearchResETime = logFormat.SearchResETime
olc.Operations[i].SearchResNEntries = &logFormat.SearchResNEntries
olc.Operations[i].SearchResText = logFormat.SearchResText
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[i])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = append(olc.Operations[:i], olc.Operations[i+1:]...)
}
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381223+02:00 ldap.domain.org slapd slapd[82581] conn=16113 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
ModDN: logFormat.ModDN,
}
olc.Operations = append(olc.Operations, op)
ModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T14:35:17.381233+02:00 ldap.domain.org slapd[82581] conn=16113 op=3 MOD attr=description
If we don't have the initial connect, we will discard logs
*/
if logFormat.ModAttr != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
for i := range olc.Operations {
if olc.Operations[i].OpId != nil && *olc.Operations[i].OpId == logFormat.OpId {
olc.Operations[i].ModAttr = logFormat.ModAttr
break
}
}
}
mqMtx.Unlock()
}
/*
2022-07-18T11:13:17.521717+02:00 ldap.domain.org slapd[82581] conn=16113 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new
If we don't have the initial connect, we will discard logs
*/
if logFormat.PassModDN != "" {
mqMtx.Lock()
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
PassModDN: logFormat.PassModDN,
}
olc.Operations = append(olc.Operations, op)
PassModCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 op=8 UNBIND
*/
if logFormat.OpType == "unbind" {
mqMtx.Lock()
// unbind is a new operation
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
OpId: &logFormat.OpId,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
var jsonBytes []byte
if gFlatten {
jsonBytes, err = json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
}
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
UnbindCnt.WithLabelValues(olc.Hostname).Inc()
}
}
mqMtx.Unlock()
}
/*
2022-07-18T17:18:19.785681+02:00 ldap.domain.org slapd[82581] conn=16113 fd=34 closed
*/
// If gFlatten == false && We do not catch "closed", then the connection will never be displayed
if logFormat.OpType == "close" {
mqMtx.Lock()
// close is a new operation with no op_id
if olc, ok := mq[fmt.Sprintf("%s:%d", logFormat.Hostname, logFormat.ConnId)]; ok {
op := &Operation{
Time: logFormat.Time,
OpType: logFormat.OpType,
}
olc.Operations = append(olc.Operations, op)
// Dump to stdout if gFlatten...
if gFlatten == true {
jsonBytes, err := json.Marshal(OlcToFlat(olc)[len(olc.Operations)-1])
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
// Then remove operation from OpenLDAPConnection so it wont output again
olc.Operations = olc.Operations[:len(olc.Operations)-1]
} else {
jsonBytes, err := json.Marshal(olc)
if err != nil {
log.Fatal(err)
}
outfMtx.Lock()
err = writeOut(string(jsonBytes), gOutputFile)
outfMtx.Unlock()
if err != nil {
log.Fatal(err)
}
}
CloseCnt.WithLabelValues(olc.Hostname).Inc()
}
mqMtx.Unlock()
}
return nil
}
func scanAndProcess(scanner *bufio.Scanner, isStdin bool, conn net.Conn, mQueue map[string]*OpenLdapConnection,
mqMtx *sync.Mutex, outfMtx *sync.Mutex, o *openldaplog.OpenldapLog) error {
for {
// If input is made via TCP Conn, we need to read from a connected net.Conn
if scanner == nil || (isStdin == false && conn == nil) {
return errors.New("Invalid input")
}
if false == scanner.Scan() {
// After Scan returns false, the Err method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil
if err := scanner.Err(); err != nil {
log.Printf("Error reading data: %v\n", err.Error())
}
if isStdin == false {
log.Printf("No more data, closing connection.\n")
// Should we?
conn.Close()
}
// input is dead, abort mission!
return errors.New("Read error")
}
// Extend timeout after successful read (so we got an idle timeout)
if isStdin == false && conn != nil {
conn.SetReadDeadline(time.Now().Add(time.Duration(600) * time.Second))
}
LineReadCnt.Inc()
read := scanner.Bytes()
if gDebug {
fmt.Printf("DEBUG: Received %v\n", string(read))
}
err := parseStoreAndWrite(read, mQueue, mqMtx, outfMtx, o)
if err != nil {
if err.Error() != "Error: Line do not match regex" {
return err
} else {
log.Printf("input do not match regex: %s\n", string(read))
}
}
}
return nil
}
func processLogs(cmd *cobra.Command, args []string) {
//var scanner *bufio.Scanner
var listener net.Listener
// Output file mutex
var outfMtx sync.Mutex
// mQueue mutex
var mqMtx sync.Mutex
var useStdin bool
// create map of messages
mQueue := make(map[string]*OpenLdapConnection)
BuildInfo.WithLabelValues(Version, runtime.Version()).Set(1)
StartTime.Set(float64(time.Now().Unix()))
// Prometheus exporter
if gPromListenAddress != "do-not-listen" {
go func() {
http.Handle(gPromMetricPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<html>
<head><title>Openldap-log-parser Exporter</title></head>
<body>
<h1>Openldap-log-parser Exporter</h1>
<p><a href='` + gPromMetricPath + `'>Metrics</a></p>
</body>
</html>`))
})
log.Fatal(http.ListenAndServe(gPromListenAddress, nil))
}()
}
// Create PID file
if len(gPidFilePath) > 0 {
if pid, err := pidfile.Create(gPidFilePath); err != nil {
log.Fatal(err)
} else {
defer pid.Clear()
}
}
// initialize
o := openldaplog.NewOpenldapLog(gDebug)
// Get a writer, file or stdout
_, File, err := NewWriter(gOutputFile)
if err != nil {
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
// Manage output file rotation when receiving SIGUSR1
if len(gOutputFile) > 0 {
sig := make(chan os.Signal)
signal.Notify(sig, syscall.SIGUSR1)
go func() {
for {
<-sig
outfMtx.Lock()
fmt.Println("SIGUSR1 received, recreating output file")
File.Close()
_, File, err = NewWriter(gOutputFile)
if err != nil {
outfMtx.Unlock()
cmd.SetOutput(os.Stderr)
cmd.Println(err)
os.Exit(1)
}
outfMtx.Unlock()
}
}()
}
// Cleaner thread
//go periodicallyCleanMQueue(mQueue, &mqMtx)
// Initialize Stdin input...
if true == strings.EqualFold(gSyslogListenAddress, "do-not-listen") {
useStdin = true
scanner := bufio.NewScanner(os.Stdin)
scanAndProcess(scanner, useStdin, nil, mQueue, &mqMtx, &outfMtx, o)
// ...or manages incoming connections
} else {
if gDebug {
fmt.Printf("DEBUG: Listening on %s\n", gSyslogListenAddress)
}
listener, err = net.Listen("tcp", gSyslogListenAddress)
if err != nil {
log.Fatal(fmt.Sprintf("Error listening on %s: %v\n", gSyslogListenAddress, err))
}
for {
connClt, err := listener.Accept()
if err != nil {
log.Printf("Error accepting: %v", err)
// Loop
continue
}
if gDebug {
fmt.Printf("DEBUG: Accept connection from %s\n", connClt.RemoteAddr().String())
}
scanner := bufio.NewScanner(connClt)
ConnectedClientCnt.Inc()
go scanAndProcess(scanner, useStdin, connClt, mQueue, &mqMtx, &outfMtx, o)
ConnectedClientCnt.Dec()
}
}
if File != nil {
outfMtx.Lock()
File.Close()
outfMtx.Unlock()
}
}

863
openldap-log-parser_test.go Normal file
View File

@ -0,0 +1,863 @@
package openldaplog
import (
"fmt"
"time"
"strings"
"testing"
)
var (
gOlog = NewOpenldapLog(false)
)
const (
)
func TestParseAccept(t *testing.T) {
line := `2022-07-18T09:23:20.160516+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 ACCEPT from IP=10.11.12.16:64482 (IP=0.0.0.0:389)`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 23, 20, 160516*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 10 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 10\n")
t.Error("Parsing ConnFd")
}
if false == strings.EqualFold(lf.OpType, "accept") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: accept\n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.ClientIp, "10.11.12.16") {
fmt.Printf("Got : %v\n", lf.ClientIp)
fmt.Printf("Wanted: 10.11.12.16\n")
t.Error("Parsing ClientIp")
}
if lf.ClientPort != 64482 {
fmt.Printf("Got : %v\n", lf.ClientPort)
fmt.Printf("Wanted: 64482\n")
t.Error("Parsing ClientPort")
}
if false == strings.EqualFold(lf.ServerIp, "0.0.0.0") {
fmt.Printf("Got : %v\n", lf.ServerIp)
fmt.Printf("Wanted: 0.0.0.0\n")
t.Error("Parsing ServerIp")
}
if lf.ServerPort != 389 {
fmt.Printf("Got : %v\n", lf.ServerPort)
fmt.Printf("Wanted: 389\n")
t.Error("Parsing ServerPort")
}
}
func TestParseStartTLS(t *testing.T) {
line := `2022-07-18T22:57:50.184389+02:00 ldap.domain.org slapd[82581] conn=1623 op=0 STARTTLS`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 22, 57, 50, 184389*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1623 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 0 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "starttls") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: \n")
t.Error("Parsing OpType")
}
}
func TestParseBindMethod(t *testing.T) {
line := `2022-07-18T09:25:35.224296+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224296*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 1 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 1\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "bind") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: accept\n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.BindDN, "cn=coincoin,dc=domain,dc=org") {
fmt.Printf("Got : %v\n", lf.BindDN)
fmt.Printf("Wanted: cn=coincoin,dc=domain,dc=org\n")
t.Error("Parsing BindDN")
}
if false == strings.EqualFold(lf.BindMethod, "128") {
fmt.Printf("Got : %v\n", lf.BindMethod)
fmt.Printf("Wanted: 128\n")
t.Error("Parsing BindMethod")
}
}
func TestParseBindMech(t *testing.T) {
line := `2022-07-18T09:25:35.224329+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224329*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 1 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 1\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "bind") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: accept\n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.BindDN, "") {
fmt.Printf("Got : %v\n", lf.BindDN)
fmt.Printf("Wanted: \n")
t.Error("Parsing BindDN")
}
if false == strings.EqualFold(lf.BindMech, "simple") {
fmt.Printf("Got : %v\n", lf.BindMech)
fmt.Printf("Wanted: simple\n")
t.Error("Parsing BindMech")
}
}
func TestParseResult(t *testing.T) {
line := `2022-07-18T09:25:35.224353+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 RESULT tag=97 err=0 text=`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224353*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 1 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 1\n")
t.Error("Parsing OpId")
}
if lf.Result != true {
fmt.Printf("Got : %v\n", lf.Result)
fmt.Printf("Wanted: true\n")
t.Error("Parsing Result")
}
if false == strings.EqualFold(lf.OpType, "") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: \n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.ResTag, "97") {
fmt.Printf("Got : %v\n", lf.ResTag)
fmt.Printf("Wanted: 97\n")
t.Error("Parsing ResTag")
}
if lf.ResErr != 0 {
fmt.Printf("Got : %v\n", lf.ResErr)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ResErr")
}
if false == strings.EqualFold(lf.ResText, "") {
fmt.Printf("Got : %v\n", lf.ResText)
fmt.Printf("Wanted: \n")
t.Error("Parsing ResText")
}
}
func TestParseUnbind(t *testing.T) {
line := `2022-07-18T09:25:35.225177+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 UNBIND`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 225177*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 2 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 2\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "unbind") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: unbind\n")
t.Error("Parsing OpType")
}
}
func TestParseModDN(t *testing.T) {
line := `2022-07-18T09:25:35.224767+02:00 ldap.domain.org slapd[82581] conn=1512 op=3 MOD dn="cn=coincoin,dc=domain,dc=org"`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224767*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 3 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 3\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.ModDN, "cn=coincoin,dc=domain,dc=org") {
fmt.Printf("Got : %v\n", lf.ModDN)
fmt.Printf("Wanted: cn=coincoin,dc=domain,dc=org\n")
t.Error("Parsing ModDN")
}
}
func TestParseModAttr(t *testing.T) {
line := `2022-07-18T09:25:35.224779+02:00 ldap.domain.org slapd[82581] conn=1512 op=3 MOD attr=description`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224779*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 3 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 3\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "mod") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: \n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.ModAttr, "description") {
fmt.Printf("Got : %v\n", lf.ModAttr)
fmt.Printf("Wanted: description\n")
t.Error("Parsing ModAttr")
}
}
func TestParsePassMod(t *testing.T) {
line := `2022-07-18T09:25:35.224767+02:00 ldap.domain.org slapd[82581] conn=1512 op=4 PASSMOD id="cn=pika,ou=users,dc=domain,dc=org" new`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224767*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 4 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 4\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.PassModDN, "cn=pika,ou=users,dc=domain,dc=org") {
fmt.Printf("Got : %v\n", lf.PassModDN)
fmt.Printf("Wanted: cn=pika,ou=users,dc=domain,dc=org\n")
t.Error("Parsing PassModDN")
}
}
func TestParseSearchBase(t *testing.T) {
line := `2022-07-18T09:25:35.224787+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SRCH base="ou=users,dc=domain,dc=org" scope=2 deref=0 filter="(&(objectClass=person)(cn=pika))"`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224787*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 2 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 2\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "search") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: search\n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.SearchBase, "ou=users,dc=domain,dc=org") {
fmt.Printf("Got : %v\n", lf.SearchBase)
fmt.Printf("Wanted: ou=users,dc=domain,dc=org\n")
t.Error("Parsing SearchBase")
}
if false == strings.EqualFold(lf.SearchScope, "2") {
fmt.Printf("Got : %v\n", lf.SearchScope)
fmt.Printf("Wanted: 2\n")
t.Error("Parsing SearchScope")
}
if false == strings.EqualFold(lf.SearchDeref, "0") {
fmt.Printf("Got : %v\n", lf.SearchDeref)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing SearchDeref")
}
if false == strings.EqualFold(lf.SearchFilter, "(&(objectClass=person)(cn=pika))") {
fmt.Printf("Got : %v\n", lf.SearchFilter)
fmt.Printf("Wanted: (&(objectClass=person)(cn=pika))\n")
t.Error("Parsing SearchFilter")
}
}
func TestParseSearchAttr(t *testing.T) {
line := `2022-07-18T09:25:35.224779+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SRCH attr=objectClass userPrincipalName userAccountControl mail rfc822Mailbox entryUUID uid cn`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224779*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 2 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 2\n")
t.Error("Parsing OpId")
}
if false == strings.EqualFold(lf.OpType, "") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: \n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.SearchAttr, "objectClass userPrincipalName userAccountControl mail rfc822Mailbox entryUUID uid cn") {
fmt.Printf("Got : %v\n", lf.SearchAttr)
fmt.Printf("Wanted: objectClass userPrincipalName userAccountControl mail rfc822Mailbox entryUUID uid cn\n")
t.Error("Parsing SearchAttr")
}
if false == strings.EqualFold(lf.SearchScope, "") {
fmt.Printf("Got : %v\n", lf.SearchScope)
fmt.Printf("Wanted: \n")
t.Error("Parsing SearchScope")
}
if false == strings.EqualFold(lf.SearchDeref, "") {
fmt.Printf("Got : %v\n", lf.SearchDeref)
fmt.Printf("Wanted: \n")
t.Error("Parsing SearchDeref")
}
if false == strings.EqualFold(lf.SearchFilter, "") {
fmt.Printf("Got : %v\n", lf.SearchFilter)
fmt.Printf("Wanted: \n")
t.Error("Parsing SearchFilter")
}
}
func TestParseSearchResult(t *testing.T) {
line := `2022-07-18T09:25:35.224843+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 SEARCH RESULT tag=101 err=0 nentries=0 text=`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 25, 35, 224843*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 0 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing ConnFd")
}
if lf.OpId != 2 {
fmt.Printf("Got : %v\n", lf.OpId)
fmt.Printf("Wanted: 2\n")
t.Error("Parsing OpId")
}
if lf.SearchResult != true {
fmt.Printf("Got : %v\n", lf.SearchResult)
fmt.Printf("Wanted: true\n")
t.Error("Parsing SearchResult")
}
if false == strings.EqualFold(lf.OpType, "") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: \n")
t.Error("Parsing OpType")
}
if false == strings.EqualFold(lf.SearchResTag, "101") {
fmt.Printf("Got : %v\n", lf.SearchResTag)
fmt.Printf("Wanted: 101\n")
t.Error("Parsing SearchResTag")
}
if lf.SearchResErr != 0 {
fmt.Printf("Got : %v\n", lf.SearchResErr)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing SearchResErr")
}
if lf.SearchResNEntries != 0 {
fmt.Printf("Got : %v\n", lf.SearchResNEntries)
fmt.Printf("Wanted: 0\n")
t.Error("Parsing SearchResNEntries")
}
if false == strings.EqualFold(lf.SearchResText, "") {
fmt.Printf("Got : %v\n", lf.SearchResText)
fmt.Printf("Wanted: \n")
t.Error("Parsing SearchResText")
}
}
func TestParseClosed(t *testing.T) {
line := `2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed`
loc,_ := time.LoadLocation("CET")
wt := time.Date(2022, 7, 18, 9, 23, 20, 226352*1000, loc)
lf, err := gOlog.Parse([]byte(line))
if err != nil {
t.Error(err)
}
if false == lf.Time.Equal(wt) {
fmt.Printf("Got : %v\n", lf.Time)
fmt.Printf("Wanted: %v\n", wt)
t.Error("Parsing time")
}
if false == strings.EqualFold(lf.Hostname, "ldap.domain.org") {
fmt.Printf("Got : %v\n", lf.Hostname)
fmt.Printf("Wanted: ldap.domain.org\n")
t.Error("Parsing hostname")
}
if false == strings.EqualFold(lf.Process, "slapd[82581]") {
fmt.Printf("Got : %v\n", lf.Process)
fmt.Printf("Wanted: slapd[82581]\n")
t.Error("Parsing process")
}
if lf.ConnId != 1512 {
fmt.Printf("Got : %v\n", lf.ConnId)
fmt.Printf("Wanted: 1512\n")
t.Error("Parsing ConnId")
}
if lf.ConnFd != 10 {
fmt.Printf("Got : %v\n", lf.ConnFd)
fmt.Printf("Wanted: 10\n")
t.Error("Parsing ConnFd")
}
if false == strings.EqualFold(lf.OpType, "close") {
fmt.Printf("Got : %v\n", lf.OpType)
fmt.Printf("Wanted: close\n")
t.Error("Parsing OpType")
}
}

View File

@ -0,0 +1,165 @@
module(load="imfile") # lecture slapd.log.json
module(load="mmjsonparse") # parsing slapd.log.json
# Template de mise en forme JSON
template(name="sendJsonToGrayLogTemplate"
type="list" option.json="on") {
constant(value="{ ")
constant(value="\"facility\":\"local4\", ")
constant(value="\"facility_num\":\"20\", ")
constant(value="\"level\":\"6\", ")
constant(value="\"type\":\"")
property(name="programname")
constant(value="\", ")
# on renomme les proprietes venant de openldap-log-parser
constant(value="\"time\":\"")
property(name="$!time")
constant(value="\", ")
constant(value="\"source\":\"")
property(name="$!hostname")
constant(value="\", ")
constant(value="\"process\":\"")
property(name="$!process")
constant(value="\", ")
constant(value="\"client_ip\":\"")
property(name="$!client_ip")
constant(value="\", ")
constant(value="\"client_port\":\"")
property(name="$!client_port")
constant(value="\", ")
constant(value="\"server_ip\":\"")
property(name="$!server_ip")
constant(value="\", ")
constant(value="\"server_port\":\"")
property(name="$!server_port")
constant(value="\", ")
constant(value="\"bind_dn\":\"")
property(name="$!bind_dn")
constant(value="\", ")
constant(value="\"conn_id\":\"")
property(name="$!conn_id")
constant(value="\", ")
constant(value="\"conn_fd\":\"")
property(name="$!conn_fd")
constant(value="\", ")
constant(value="\"op_id\":\"")
property(name="$!op_id")
constant(value="\", ")
constant(value="\"op_type\":\"")
property(name="$!op_type")
constant(value="\", ")
constant(value="\"bind_method\":\"")
property(name="$!bind_method")
constant(value="\", ")
constant(value="\"bind_mech\":\"")
property(name="$!bind_mech")
constant(value="\", ")
constant(value="\"bind_ssf\":\"")
property(name="$!bind_ssf")
constant(value="\", ")
constant(value="\"ssf\":\"")
property(name="$!ssf")
constant(value="\", ")
constant(value="\"starttls\":\"")
property(name="$!starttls")
constant(value="\", ")
constant(value="\"mod_dn\":\"")
property(name="$!mod_dn")
constant(value="\", ")
constant(value="\"mod_attr\":\"")
property(name="$!mod_attr")
constant(value="\", ")
constant(value="\"add_dn\":\"")
property(name="$!add_dn")
constant(value="\", ")
constant(value="\"del_dn\":\"")
property(name="$!del_dn")
constant(value="\", ")
constant(value="\"passmod_dn\":\"")
property(name="$!passmod_dn")
constant(value="\", ")
constant(value="\"res_tag\":\"")
property(name="$!result_tag")
constant(value="\", ")
constant(value="\"res_oid\":\"")
property(name="$!result_oid")
constant(value="\", ")
constant(value="\"res_err\":\"")
property(name="$!result_err")
constant(value="\", ")
constant(value="\"res_qtime\":\"")
property(name="$!result_qtime")
constant(value="\", ")
constant(value="\"res_etime\":\"")
property(name="$!result_etime")
constant(value="\", ")
constant(value="\"res_text\":\"")
property(name="$!result_text")
constant(value="\", ")
constant(value="\"search_base\":\"")
property(name="$!search_base")
constant(value="\", ")
constant(value="\"search_scope\":\"")
property(name="$!search_scope")
constant(value="\", ")
constant(value="\"search_deref\":\"")
property(name="$!search_deref")
constant(value="\", ")
constant(value="\"search_filter\":\"")
property(name="$!search_filter")
constant(value="\", ")
constant(value="\"search_attr\":\"")
property(name="$!search_attr")
constant(value="\", ")
constant(value="\"search_res_tag\":\"")
property(name="$!search_res_tag")
constant(value="\", ")
constant(value="\"search_res_err\":\"")
property(name="$!search_res_err")
constant(value="\", ")
constant(value="\"search_res_qtime\":\"")
property(name="$!search_res_qtime")
constant(value="\", ")
constant(value="\"search_res_etime\":\"")
property(name="$!search_res_etime")
constant(value="\", ")
constant(value="\"search_res_nentries\":\"")
property(name="$!search_res_nentries")
constant(value="\", ")
constant(value="\"search_res_text\":\"")
property(name="$!search_res_text")
constant(value="\", ")
constant(value="\"message\":\"")
property(name="$!message")
constant(value="\" ")
constant(value=" }")
}
# On envoit les logs ldap vers openldap-log-parser qui tourne en tant que service
if $programname == 'slapd' then action(
type="omfwd"
Target="127.0.0.1"
Port="6514"
Protocol="tcp"
template="RSYSLOG_FileFormat")
# Le flux post openldap-log-parser, qu'on relit pour envoyer vers graylog
input(type="imfile"
File="/var/log/slapd.log.json"
Tag="openldap-agg"
addMetadata="on"
ruleset="remoteAllJsonLog"
)
ruleset(name="remoteAllJsonLog") {
action(type="mmjsonparse" cookie="")
action(
type="omfwd"
Target="graylog.example.org"
Port="2514"
Protocol="tcp"
template="sendJsonToGrayLogTemplate"
)
stop
}

View File

@ -0,0 +1,5 @@
2022-07-18T09:25:35.224296+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" method=128
2022-07-18T09:25:35.224329+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 BIND dn="cn=coincoin,dc=domain,dc=org" mech=SIMPLE ssf=0
2022-07-18T09:25:35.224353+02:00 ldap.domain.org slapd[82581] conn=1512 op=1 RESULT tag=97 err=0 text=
2022-07-18T09:25:35.225177+02:00 ldap.domain.org slapd[82581] conn=1512 op=2 UNBIND
2022-07-18T09:23:20.226352+02:00 ldap.domain.org slapd[82581] conn=1512 fd=10 closed