Skip to content

Commit

Permalink
Teleport: Update Rules (#966)
Browse files Browse the repository at this point in the history
* Teleport: Update Rules

* Update global_helpers/panther_default.py

* Update global_helpers/panther_default.py

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

Co-authored-by: Ariel Ropek <[email protected]>

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

Co-authored-by: Ariel Ropek <[email protected]>

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

Co-authored-by: Ariel Ropek <[email protected]>

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

appease fmt

* Update rules/gravitational_teleport_rules/teleport_long_lived_certs.py

---------

Co-authored-by: Ariel Ropek <[email protected]>
  • Loading branch information
jof and arielkr256 authored Nov 27, 2023
1 parent 6252b95 commit 1f8841b
Show file tree
Hide file tree
Showing 23 changed files with 511 additions and 10 deletions.
20 changes: 20 additions & 0 deletions global_helpers/panther_base_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
from collections import OrderedDict
from collections.abc import Mapping
from datetime import datetime
from fnmatch import fnmatch
from functools import reduce
from ipaddress import ip_address, ip_network
Expand Down Expand Up @@ -494,3 +495,22 @@ def m365_alert_context(event):
def defang_ioc(ioc):
"""return defanged IOC from 1.1.1.1 to 1[.]1[.]1[.]1"""
return ioc.replace(".", "[.]")


def panther_nanotime_to_python_datetime(panther_time: str) -> datetime:
panther_time_micros = re.search(r"\.(\d+)", panther_time).group(1)
panther_time_micros_rounded = panther_time_micros[0:6]
panther_time_rounded = re.sub(r"\.\d+", f".{panther_time_micros_rounded}", panther_time)
panther_time_format = r"%Y-%m-%d %H:%M:%S.%f"
return datetime.strptime(panther_time_rounded, panther_time_format)


def golang_nanotime_to_python_datetime(golang_time: str) -> datetime:
golang_time_format = r"%Y-%m-%dT%H:%M:%S.%fZ"
# Golang fractional seconds include a mix of microseconds and
# nanoseconds, which doesn't play well with Python's microseconds datetimes.
# This rounds the fractional seconds to a microsecond-size.
golang_time_micros = re.search(r"\.(\d+)Z", golang_time).group(1)
golang_time_micros_rounded = golang_time_micros[0:6]
golang_time_rounded = re.sub(r"\.\d+Z", f".{golang_time_micros_rounded}Z", golang_time)
return datetime.strptime(golang_time_rounded, golang_time_format)
3 changes: 2 additions & 1 deletion rules/gravitational_teleport_rules/teleport_auth_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ def rule(event):
def title(event):
return (
f"A high volume of SSH errors was detected from user "
f"[{event.get('user', '<UNKNOWN_USER>')}]"
f"[{event.get('user', '<UNKNOWN_USER>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Reports:
Description: A high volume of SSH errors could indicate a brute-force attack
Threshold: 10
DedupPeriodMinutes: 15
Reference: https://gravitational.com/teleport/docs/admin-guide/
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Check that the user making the failed requests legitimately tried logging in that many times.
SummaryAttributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ def rule(event):


def title(event):
return f"User [{event.get('user', '<UNKNOWN_USER>')}] has manually modified system users"
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has manually modified system users "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Reports:
Severity: High
Description: A user has been manually created, modified, or deleted
DedupPeriodMinutes: 15
Reference: https://gravitational.com/teleport/docs/admin-guide/
Reference: https://goteleport.com/docs/management/admin/
Runbook: Analyze why it was manually created and delete it if necessary.
SummaryAttributes:
- event
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
SENSITIVE_LOCAL_USERS = ["breakglass"]


def rule(event):
return (
event.get("event") == "user.login"
and event.get("success") == "true"
and event.get("method") == "local"
and not event.get("mfa_device")
)


def severity(event):
if event.get("user") in SENSITIVE_LOCAL_USERS:
return "HIGH"
return "MEDIUM"


def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] logged into "
f"[{event.get('cluster_name', '<UNNAMED_CLUSTER>')}] locally "
f"without using MFA"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
AnalysisType: rule
Filename: teleport_local_user_login_without_mfa.py
RuleID: Teleport.LocalUserLoginWithoutMFA
DisplayName: User Logged in wihout MFA
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: High
Description: A local User logged in without MFA
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0001:T1078
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A local user logged in without Multi-Factor Authentication
SummaryAttributes:
- event
- code
- user
- success
- mfa_device
Tests:
-
Name: User logged in with MFA
ExpectedResult: false
Log:
{
"addr.remote": "[2001:db8:feed:face:c0ff:eeb0:baf00:00d]:65123",
"cluster_name": "teleport.example.com",
"code": "T1000I",
"ei": 0,
"event": "user.login",
"method": "local",
"mfa_device": {
"mfa_device_name": "1Password",
"mfa_device_type": "WebAuthn",
"mfa_device_uuid": "88888888-4444-4444-4444-222222222222"
},
"success": true,
"time": "2023-09-20T19:00:00.123456Z",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "max.mustermann",
"user_agent": "Examplecorp Spacedeck-web/99.9 (Hackintosh; ARM Cortex A1000)"
}
-
Name: User logged in without MFA
ExpectedResult: false
Log:
{
"addr.remote": "[2001:db8:face:face:face:face:face:face]:65123",
"cluster_name": "teleport.example.com",
"code": "T1000I",
"ei": 0,
"event": "user.login",
"method": "local",
"success": true,
"time": "2023-09-20T19:00:00.123456Z",
"uid": "88888888-4444-4444-4444-222222222222",
"user": "max.mustermann",
"user_agent": "Examplecorp Spacedeck-web/99.9 (Hackintosh; ARM Cortex A1000)"
}
10 changes: 10 additions & 0 deletions rules/gravitational_teleport_rules/teleport_lock_created.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def rule(event):
return event.get("event") == "lock.created"


def title(event):
return (
f"A Teleport Lock was created by {event.get('updated_by', '<UNKNOWN_UPDATED_BY>')} "
f"to Lock out user {event.get('target', {}).get('user', '<UNKNOWN_USER>')} "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
40 changes: 40 additions & 0 deletions rules/gravitational_teleport_rules/teleport_lock_created.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
AnalysisType: rule
Filename: teleport_lock_created.py
RuleID: Teleport.LockCreated
DisplayName: A Teleport Lock was created
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Info
Description: A Teleport Lock was created
DedupPeriodMinutes: 60
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
A Teleport Lock was created; this is an unusual administrative action. Investigate to understand why a Lock was created.
SummaryAttributes:
- event
- code
- time
- identity
Tests:
-
Name: A Lock was created
ExpectedResult: true
Log:
{
"cluster_name": "teleport.example.com",
"code": "TLK00I",
"ei": 0,
"event": "lock.created",
"expires": "0001-01-01T00:00:00Z",
"name": "88888888-4444-4444-4444-222222222222",
"target": {
"user": "user-to-disable"
},
"time": "2023-09-21T00:00:00.000000Z",
"uid": "88888888-4444-4444-4444-222222222222",
"updated_by": "[email protected]",
"user": "[email protected]"
}
79 changes: 79 additions & 0 deletions rules/gravitational_teleport_rules/teleport_long_lived_certs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from datetime import timedelta, datetime
from typing import Dict, Tuple

from panther_base_helpers import (
golang_nanotime_to_python_datetime,
panther_nanotime_to_python_datetime,
)

PANTHER_TIME_FORMAT = r"%Y-%m-%d %H:%M:%S.%f"
# Tune this to be some Greatest Common Denominator of session TTLs for your
# environment
MAXIMUM_NORMAL_VALIDITY_INTERVAL = timedelta(hours=12)
# To allow some time in between when a request is submitted and authorized
# vs when the certificate actually gets generated. In practice, this is much
# less than 5 seconds.
ISSUANCE_GRACE_PERIOD = timedelta(seconds=5)

# You can audit your logs in Panther to try and understand your role/validity
# patterns from a known-good period of access.
# A query example:
# ```sql
# SELECT
# cluster_name,
# identity:roles,
# DATEDIFF('HOUR', time, identity:expires) AS validity
# FROM
# panther_logs.public.gravitational_teleportaudit
# WHERE
# p_occurs_between('2023-09-01 00:00:00','2023-10-06 21:00:00Z')
# AND event = 'cert.create'
# GROUP BY cluster_name, identity:roles, validity
# ORDER BY validity DESC
# ```

# A dictionary of:
# cluster names: to a dictionary of:
# role names: mapping to a tuple of:
# ( maximum usual validity, expiration datetime for this rule )
CLUSTER_ROLE_MAX_VALIDITIES: Dict[str, Dict[str, Tuple[timedelta, datetime]]] = {
# "teleport.example.com": {
# "example_role": (timedelta(hours=720), datetime(2023, 12, 01, 01, 02, 03)),
# "other_example_role": (timedelta(hours=720), datetime.max),
# },
}


def rule(event):
if not event.get("event") == "cert.create":
return False
max_validity = MAXIMUM_NORMAL_VALIDITY_INTERVAL + ISSUANCE_GRACE_PERIOD
for role in event.deep_get("identity", "roles", default=[]):
validity, expiration = CLUSTER_ROLE_MAX_VALIDITIES.get(event.get("cluster_name"), {}).get(
role, (None, None)
)
if validity and expiration:
# Ignore exceptions that have passed their expiry date
if datetime.utcnow() < expiration:
max_validity = max(max_validity, validity)
return validity_interval(event) > max_validity


def validity_interval(event):
event_time = panther_nanotime_to_python_datetime(event.get("time"))
expires = golang_nanotime_to_python_datetime(
event.deep_get("identity", "expires", default=None)
)
if not event_time and expires:
return False
interval = expires - event_time
return interval


def title(event):
identity = event.deep_get("identity", "user", default="<Cert with no User!?>")
return (
f"A Certificate for [{identity}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}] "
f"has been issued for an unusually long time: {validity_interval(event)!r} "
)
92 changes: 92 additions & 0 deletions rules/gravitational_teleport_rules/teleport_long_lived_certs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
AnalysisType: rule
Filename: teleport_long_lived_certs.py
RuleID: Teleport.LongLivedCerts
DisplayName: A long-lived cert was created
Enabled: true
LogTypes:
- Gravitational.TeleportAudit
Tags:
- Teleport
Severity: Medium
Description: An unusually long-lived Teleport certificate was created
DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0003:T1098
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Teleport certificates are usually issued for a short period of time. Alert if long-lived certificates were created.
SummaryAttributes:
- event
- code
- time
- identity
Tests:
-
Name: A certificate was created for the default period of 1 hour
ExpectedResult: false
Log:
{
"cert_type": "user",
"cluster_name": "teleport.example.com",
"code": "TC000I",
"ei": 0,
"event": "cert.create",
"time": "2023-09-17 21:00:00.000000",
"identity": {
"disallow_reissue": true,
"expires": "2023-09-17T22:00:00.444444428Z",
"impersonator": "bot-application",
"kubernetes_cluster": "staging",
"kubernetes_groups": [
"application"
],
"logins": [
"-teleport-nologin-88888888-4444-4444-4444-222222222222",
"-teleport-internal-join"
],
"prev_identity_expires": "0001-01-01T00:00:00Z",
"roles": [
"application"
],
"route_to_cluster": "teleport.example.com",
"teleport_cluster": "teleport.example.com",
"traits": {},
"user": "bot-application"
},
"uid": "88888888-4444-4444-4444-222222222222"
}
-
Name: A certificate was created for longer than the default period of 1 hour
ExpectedResult: true
Log:
{
"cert_type": "user",
"cluster_name": "teleport.example.com",
"code": "TC000I",
"ei": 0,
"event": "cert.create",
"time": "2023-09-17 21:00:00.000000",
"identity": {
"disallow_reissue": true,
"expires": "2043-09-17T22:00:00.444444428Z",
"impersonator": "bot-application",
"kubernetes_cluster": "staging",
"kubernetes_groups": [
"application"
],
"logins": [
"-teleport-nologin-88888888-4444-4444-4444-222222222222",
"-teleport-internal-join"
],
"prev_identity_expires": "0001-01-01T00:00:00Z",
"roles": [
"application"
],
"route_to_cluster": "teleport.example.com",
"teleport_cluster": "teleport.example.com",
"traits": {},
"user": "bot-application"
},
"uid": "88888888-4444-4444-4444-222222222222"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ def rule(event):
def title(event):
return (
f"User [{event.get('user', '<UNKNOWN_USER>')}] has issued a network scan with "
f"[{event.get('program', '<UNKNOWN_PROGRAM>')}]"
f"[{event.get('program', '<UNKNOWN_PROGRAM>')}] "
f"on [{event.get('cluster_name', '<UNKNOWN_CLUSTER>')}]"
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ DedupPeriodMinutes: 60
Reports:
MITRE ATT&CK:
- TA0007:T1046
Reference: https://gravitational.com/teleport/docs/admin-guide/
Reference: https://goteleport.com/docs/management/admin/
Runbook: >
Find related commands within the time window and determine if the command was invoked legitimately. Examine the arguments to determine how the command was used.
SummaryAttributes:
Expand Down
Loading

0 comments on commit 1f8841b

Please sign in to comment.