Skip to content

Commit

Permalink
add new fields with count of failed and denied MFA requests
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Labarussias <[email protected]>
  • Loading branch information
Issif authored and poiana committed Sep 26, 2022
1 parent 7b86e9d commit 7aa76d0
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 60 deletions.
1 change: 1 addition & 0 deletions plugins/okta/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
falco.yaml
2 changes: 1 addition & 1 deletion plugins/okta/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ all: $(OUTPUT)
clean:
@rm -f $(OUTPUT)

$(OUTPUT):
$(OUTPUT): clean
@$(GODEBUGFLAGS) $(GO) build -buildmode=c-shared -o $(OUTPUT) ./plugin

readme:
Expand Down
96 changes: 51 additions & 45 deletions plugins/okta/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,53 @@ The event source for `okta` events is `okta`.
# Supported Fields

<!-- README-PLUGIN-FIELDS -->
| NAME | TYPE | ARG | DESCRIPTION |
|---------------------------------|----------|------|---------------------------------|
| `okta.app` | `string` | None | Application |
| `okta.evt.type` | `string` | None | Event Type |
| `okta.evt.legacytype` | `string` | None | Event Legacy Type |
| `okta.severity` | `string` | None | Severity |
| `okta.message` | `string` | None | Message |
| `okta.actor.id` | `string` | None | Actor ID |
| `okta.actor.Type` | `string` | None | Actor Type |
| `okta.actor.alternateid` | `string` | None | Actor Alternate ID |
| `okta.actor.name` | `string` | None | Actor Display Name |
| `okta.client.zone` | `string` | None | Client Zone |
| `okta.client.ip` | `string` | None | Client IP Address |
| `okta.client.device` | `string` | None | Client Device |
| `okta.client.id` | `string` | None | Client ID |
| `okta.client.geo.city` | `string` | None | Client Geographical City |
| `okta.client.geo.state` | `string` | None | Client Geographical State |
| `okta.client.geo.country` | `string` | None | Client Geographical Country |
| `okta.client.geo.postalcode` | `string` | None | Client Geographical Postal Code |
| `okta.client.geo.lat` | `string` | None | Client Geographical Latitude |
| `okta.client.geo.lon` | `string` | None | Client Geographical Longitude |
| `okta.useragent.os` | `string` | None | Useragent OS |
| `okta.useragent.browser` | `string` | None | Useragent Browser |
| `okta.useragent.raw` | `string` | None | Raw Useragent |
| `okta.result` | `string` | None | Outcome Result |
| `okta.reason` | `string` | None | Outcome Reason |
| `okta.transaction.id` | `string` | None | Transaction ID |
| `okta.transaction.type` | `string` | None | Transaction Type |
| `okta.requesturi` | `string` | None | Request URI |
| `okta.principal.id` | `string` | None | Principal ID |
| `okta.principal.alternateid` | `string` | None | Principal Alternate ID |
| `okta.principal.type` | `string` | None | Principal Type |
| `okta.principal.name` | `string` | None | Principal Name |
| `okta.authentication.step` | `string` | None | Authentication Step |
| `okta.authentication.sessionid` | `string` | None | External Session ID |
| `okta.security.asnumber` | `uint64` | None | Security AS Number |
| `okta.security.asorg` | `string` | None | Security AS Org |
| `okta.security.isp` | `string` | None | Security ISP |
| `okta.security.domain` | `string` | None | Security Domain |
| `okta.target.user.id` | `string` | None | Target User ID |
| `okta.target.user.aternateid` | `string` | None | Target User Alternate ID |
| `okta.target.user.name` | `string` | None | Target User Name |
| `okta.target.group.id` | `string` | None | Target Group ID |
| `okta.target.group.aternateid` | `string` | None | Target Group Alternate ID |
| `okta.target.group.name` | `string` | None | Target Group Name |
| NAME | TYPE | ARG | DESCRIPTION |
|---------------------------------|----------|---------------|--------------------------------------|
| `okta.app` | `string` | None | Application |
| `okta.evt.type` | `string` | None | Event Type |
| `okta.evt.legacytype` | `string` | None | Event Legacy Type |
| `okta.severity` | `string` | None | Severity |
| `okta.message` | `string` | None | Message |
| `okta.actor.id` | `string` | None | Actor ID |
| `okta.actor.Type` | `string` | None | Actor Type |
| `okta.actor.alternateid` | `string` | None | Actor Alternate ID |
| `okta.actor.name` | `string` | None | Actor Display Name |
| `okta.client.zone` | `string` | None | Client Zone |
| `okta.client.ip` | `string` | None | Client IP Address |
| `okta.client.device` | `string` | None | Client Device |
| `okta.client.id` | `string` | None | Client ID |
| `okta.client.geo.city` | `string` | None | Client Geographical City |
| `okta.client.geo.state` | `string` | None | Client Geographical State |
| `okta.client.geo.country` | `string` | None | Client Geographical Country |
| `okta.client.geo.postalcode` | `string` | None | Client Geographical Postal Code |
| `okta.client.geo.lat` | `string` | None | Client Geographical Latitude |
| `okta.client.geo.lon` | `string` | None | Client Geographical Longitude |
| `okta.useragent.os` | `string` | None | Useragent OS |
| `okta.useragent.browser` | `string` | None | Useragent Browser |
| `okta.useragent.raw` | `string` | None | Raw Useragent |
| `okta.result` | `string` | None | Outcome Result |
| `okta.reason` | `string` | None | Outcome Reason |
| `okta.transaction.id` | `string` | None | Transaction ID |
| `okta.transaction.type` | `string` | None | Transaction Type |
| `okta.requesturi` | `string` | None | Request URI |
| `okta.principal.id` | `string` | None | Principal ID |
| `okta.principal.alternateid` | `string` | None | Principal Alternate ID |
| `okta.principal.type` | `string` | None | Principal Type |
| `okta.principal.name` | `string` | None | Principal Name |
| `okta.authentication.step` | `string` | None | Authentication Step |
| `okta.authentication.sessionid` | `string` | None | External Session ID |
| `okta.security.asnumber` | `uint64` | None | Security AS Number |
| `okta.security.asorg` | `string` | None | Security AS Org |
| `okta.security.isp` | `string` | None | Security ISP |
| `okta.security.domain` | `string` | None | Security Domain |
| `okta.target.user.id` | `string` | None | Target User ID |
| `okta.target.user.aternateid` | `string` | None | Target User Alternate ID |
| `okta.target.user.name` | `string` | None | Target User Name |
| `okta.target.group.id` | `string` | None | Target Group ID |
| `okta.target.group.aternateid` | `string` | None | Target Group Alternate ID |
| `okta.target.group.name` | `string` | None | Target Group Name |
| `okta.mfa.failure.countlast` | `uint64` | Key, Required | Count of MFA failures n last seconds |
| `okta.mfa.deny.countlast` | `uint64` | Key, Required | Count of MFA denies in last seconds |
<!-- /README-PLUGIN-FIELDS -->

# Development
Expand All @@ -87,6 +89,8 @@ make
Only `init` accepts settings:
* `organization`: the name of your organization (same as in *https://xxxx.okta.com*)
* `api_token`: your API Token to access Okta API
* `cache_expiration`: TTL in seconds for keys in cache for MFA events (default: 600)
* `cache_usermaxsize`: Max size by user for the cache (default: 200)

# Configurations

Expand All @@ -99,6 +103,8 @@ Only `init` accepts settings:
init_config:
organization: myorg
api_token: xxxxxxxxxxx
cache_expiration: 84600 #24h
cache_usermaxsize: 200
open_params: ''

load_plugins: [okta]
Expand Down
3 changes: 2 additions & 1 deletion plugins/okta/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ go 1.17

require (
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
github.com/falcosecurity/plugin-sdk-go v0.6.0-rc2
github.com/bluele/gcache v0.0.2
github.com/falcosecurity/plugin-sdk-go v0.5.0
)

require github.com/iancoleman/orderedmap v0.2.0 // indirect
11 changes: 4 additions & 7 deletions plugins/okta/go.sum
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog=
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/falcosecurity/plugin-sdk-go v0.6.0-rc2 h1:mwLygQKXu3594M08H6mZg8Ty1kEySJM7vwt5z/QU1aI=
github.com/falcosecurity/plugin-sdk-go v0.6.0-rc2/go.mod h1:YNeijGR1gojfv1CV17WoQMGeCheTJdCwTgShPpPpgFA=
github.com/falcosecurity/plugin-sdk-go v0.5.0 h1:BPHdVyzJGx0mILvHUJ13lO2H02ZgISlZ5luWwIWpG98=
github.com/falcosecurity/plugin-sdk-go v0.5.0/go.mod h1:9IdFIqRwJIFDfKnwTTM6S4mLITNfdjVl+5r4RY0TmRo=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
57 changes: 52 additions & 5 deletions plugins/okta/pkg/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"time"

"github.com/alecthomas/jsonschema"
"github.com/bluele/gcache"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/source"
Expand Down Expand Up @@ -107,10 +108,13 @@ type LogEvent struct {
// Plugin represents our plugin
type Plugin struct {
plugins.BasePlugin
APIToken string `json:"api_token" jsonschema:"title=API token,description=API Token,required"`
Organization string `json:"organization" jsonschema:"title=Organization,description=Your Okta organization,required"`
lastLogEvent LogEvent
lastEventNum uint64
APIToken string `json:"api_token" jsonschema:"title=API token,description=API Token,required"`
Organization string `json:"organization" jsonschema:"title=Organization,description=Your Okta organization,required"`
CacheExpiration uint64 `json:"cache_expiration" jsonschema:"title=Cache Expiration,description=TTL in seconds for keys in cache for MFA events (default: 600)"`
CacheUserMaxSize uint64 `json:"cache_usermaxsize" jsonschema:"title=Cache User Max Size,description=Max size by user for the cache (default: 200)"`
lastLogEvent LogEvent
lastEventNum uint64
cache gcache.Cache
}

// PluginInstance represents a opened stream based on our Plugin
Expand All @@ -131,7 +135,7 @@ func (oktaPlugin *Plugin) Info() *plugins.Info {
Name: "okta",
Description: "Okta Log Events",
Contact: "github.com/falcosecurity/plugins/",
Version: "0.4.0",
Version: "0.5.0",
EventSource: "okta",
}
}
Expand All @@ -154,10 +158,13 @@ func (oktaPlugin *Plugin) InitSchema() *sdk.SchemaInfo {
// we use it for setting default configuration values and mapping
// values from `init_config` (json format for this plugin)
func (oktaPlugin *Plugin) Init(config string) error {
oktaPlugin.CacheExpiration = 84600
oktaPlugin.CacheUserMaxSize = 200
err := json.Unmarshal([]byte(config), &oktaPlugin)
if err != nil {
return err
}
oktaPlugin.cache = gcache.New(10000).LFU().Build()
return nil
}

Expand Down Expand Up @@ -207,6 +214,8 @@ func (oktaPlugin *Plugin) Fields() []sdk.FieldEntry {
{Type: "string", Name: "okta.target.group.id", Desc: "Target Group ID"},
{Type: "string", Name: "okta.target.group.aternateid", Desc: "Target Group Alternate ID"},
{Type: "string", Name: "okta.target.group.name", Desc: "Target Group Name"},
{Type: "uint64", Name: "okta.mfa.failure.countlast", Desc: "Count of MFA failures in last seconds", Arg: sdk.FieldEntryArg{IsRequired: true, IsIndex: true}},
{Type: "uint64", Name: "okta.mfa.deny.countlast", Desc: "Count of MFA denies in last seconds", Arg: sdk.FieldEntryArg{IsRequired: true, IsIndex: true}},
}
}

Expand All @@ -227,6 +236,25 @@ func (oktaPlugin *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) e

oktaPlugin.lastLogEvent = data
oktaPlugin.lastEventNum = evt.EventNum()

if data.EventType == "user.mfa.okta_verify.deny_push" || (data.EventType == "user.authentication.auth_via_mfa" && data.Outcome.Result == "FAILURE") {
key := data.EventType + ":" + data.Actor.ID
valueList := []uint64{}
value, err := oktaPlugin.cache.Get(key)
if err == nil {
if value != nil {
valueList = value.([]uint64)
}
}
valueList = append(valueList, evt.Timestamp())
if uint64(len(valueList)) > oktaPlugin.CacheUserMaxSize {
valueList = valueList[1:]
}
err = oktaPlugin.cache.SetWithExpire(key, valueList, time.Duration(oktaPlugin.CacheExpiration)*time.Second)
if err != nil {
return err
}
}
}

switch req.Field() {
Expand Down Expand Up @@ -343,6 +371,25 @@ func (oktaPlugin *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) e
req.SetValue(i.DisplayName)
}
}
case "okta.mfa.failure.countlast", "okta.mfa.deny.countlast":
if data.EventType == "user.mfa.okta_verify.deny_push" || (data.EventType == "user.authentication.auth_via_mfa" && data.Outcome.Result == "FAILURE") {
key := data.EventType + ":" + data.Actor.ID
shift := req.ArgIndex()
getvalue, err := oktaPlugin.cache.Get(key)
if err == nil {
if getvalue != nil {
var count uint64
values := getvalue.([]uint64)
oldest := evt.Timestamp() - uint64(shift*1e9)
for _, i := range values {
if i > uint64(oldest) {
count++
}
}
req.SetValue(count)
}
}
}
// case "okta.security.isproxy":
// req.SetValue(data.SecurityContext.IsProxy)
default:
Expand Down
18 changes: 17 additions & 1 deletion plugins/okta/rules/okta_rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

- required_plugin_versions:
- name: okta
version: 0.1.0
version: 0.5.0

# Example Rule on login in to OKTA. Disabled by default since it might be noisy
#- rule: User logged in to OKTA
Expand Down Expand Up @@ -198,3 +198,19 @@
source: okta
tags: [okta]
enabled: false

- rule: Too many failed MFA in last 5min
desc: An user failed MFA too many times in the last 5min
condition: okta.mfa.failure.countlast[300]>=3
output: "A user has failed MFA too many times in last 5min (user='%okta.actor.name' last_reason='%okta.reason' countLast300=%okta.mfa.failure.countlast[300])"
priority: WARNING
source: okta
tags: [okta]

- rule: Too many denied MFA Pushes in last 5min
desc: A user denied too many MFA Pushes in the last 5min
condition: okta.mfa.deny.countlast[300]>=3
output: "A user has denied too many MFA pushes in last 5min (user='%okta.actor.name' reason='%okta.reason' countLast300=%okta.mfa.deny.countlast[300])"
priority: WARNING
source: okta
tags: [okta]

0 comments on commit 7aa76d0

Please sign in to comment.