Understanding FPSL with example patterns
Following are some examples of FPSL patterns that Forcepoint ONE SSE have created that you can either use in your own tenants or that you can use a template for creating your own.
FPSL is a powerful tool with limitless capabilities. As such, customers can use this to create any policy they might need to control inline actions and activity across any cloud service. Forcepoint ONE SSE's eventual goal will be to publish the data patterns created and tested as out of the box policies that customers can use. Then moving forward as customers create their own (since the majority of Lua code creations will not have customer info) we will take common use case ones, test them, and then add them to our out of the box pattern library. In the meantime, below are some examples of FPSL patterns that we have created that you can either use in your own tenants or that you can use a template for creating your own.
LinkedIn Message
This pattern will identify when someone tries to post a message on LinkedIn.
[[LUA_AF_REQUEST_SCOPE]]
if ((BG.method == "POST")
and BG.domain:find("www%.linkedin%.com")
and BG.uri:find("^/voyager/api/messaging/conversations/")
and BG.qs:find("create")) then
BGResult.match = 1
BGResult.log = "LinkedIn Messaging"
end
LinkedIn Posts
This pattern will identify when someone tries to make a post on LinkedIn.
[[LUA_AF_REQUEST_SCOPE]]
if BG.uri then
local qs = BG.qs
if not qs then qs = "" end
local uri = string.lower(BG.uri)
if (((BG.method == "POST") or (BG.method == "GET")) and
(string.find(uri, "/api/contentcreation/") or
(uri == "/") or
string.find(qs, "commentaryV%d+"))) then
BGResult.match = 1
BGResult.log = "LinkedIn Posting"
end
end
LinkedIn Comments
This pattern will identify when someone tries to reply to a LinkedIn post in the comments.
[[LUA_AF_REQUEST_SCOPE]]
if BG.uri then
local qs = BG.qs
if not qs then qs = "" end
local uri = string.lower(BG.uri)
if (((BG.method == "POST") or (BG.method == "GET")) and
(string.find(uri, "/api/feed/comments") or
(uri == "/") or
string.find(qs, "comment"))) then
BGResult.match = 1
BGResult.log = "LinkedIn Comment"
end
end
LinkedIn Reactions
This pattern is separate to identify reactions such as Likes, Celebrates, Support, etc on LinkedIn
[[LUA_AF_REQUEST_SCOPE]]
if BG.uri then
local qs = BG.qs
if not qs then qs = "" end
local uri = string.lower(BG.uri)
if (((BG.method == "POST") or (BG.method == "GET")) and
(string.find(uri, "/api/feed/reactions") or
(uri == "/") or
string.find(qs, "react-to-feed-post"))) then
BGResult.match = 1
BGResult.log = "LinkedIn Reactions"
end
end
Slack Login Control via Email and Password
This pattern will distinguish between personal and corporate slack logins to block personal accounts. For patterns like this, you will need to enter your own email domain to help distinguish when a match is found (that is, in the below you are calling it a match when it does not see your email domain so you match on personal accounts to block them).
For Forcepoint ONE SSE <YourEmailDomain>, below would look like bitglass%.com$
[[LUA_AF_REQUEST_SCOPE]]
local function NonBitglassLogin(keyValueTable)
if not keyValueTable["email"] then
return false
end
if string.find(keyValueTable["email"], "<YourEmailDomain>")
then
return false
end
return true
end
if ((BG.method == "POST") and
string.find(BG.domain, "slack%.com$") and
BG.MatchFormFields(NonBitglassLogin))
then
BGResult.match = 1
BGResult.log = "Non-Bitglass Login"
end
Slack Email Login Control via Email Confirmation
This pattern will help you block personal logins when users click the email confirmation option for logging in which sends the user a 2FA code to the email they enter instead of having them login via their configured password (see above).
[[LUA_AF_REQUEST_SCOPE]]
if ((BG.method == "POST") and
BG.domain:find("slack%.com$") and
BG.uri:find("/api/signup.checkEmail")) then
BGResult.match = 1
end
Slack Message and Attachment Control
This policy will control if users can post messages or attachments to slack (does not matter if it's a channel or direct message).
[[LUA_AF_REQUEST_SCOPE]]
if ((BG.method == "POST") and
BG.domain:find("%.slack%.com$") and
(BG.uri:find("/api/chat.postMessage") or BG.uri:find("/upload")))
then
BGResult.match = 1
BGResult.log = "Block Channel"
end
If you wish to only control messages or attachments, you can remove the corresponding URI field above.
- Message Posting: /api/chat.postMessage
- Attachment Upload: /upload
Slack Combined Channel Control
Slack assigns every channel a unique identifier. Customers can create advanced patterns to identify and block posts and/or attachments to those channels or conversely restrict access to only specific channels. This action will require 3 data patterns.
- First you will need to create a pattern with the message and attachment control above.
-
Then create a simple pattern with the keyword set to the identifier of the channel. You can easily find this identifier by accessing slack in a web browser and looking at the end of the URL when you are clicked into a channel (for example, this for one of our channels you use)
-
Finally, you will need a combined pattern adding the two
Count("<Name of Slack Channel ID Pattern>") and Count("<Name of Slack Post/Message Control Pattern>")
Facebook Likes
This pattern will allow you to control Facebook likes.
[[LUA_AF_REQUEST_SCOPE]]
local function FBLike(keyValueTable)
if keyValueTable["fb_api_req_friendly_name"] == "CometUFIFeedbackReactMutation" then
local v = keyValueTable["variables"]
if v and string.find(v, "feedback_reaction") then
return true
end
end
return false
end
if ((BG.method == "POST") and
string.find(BG.domain, "facebook%.com") and
string.find(BG.uri, "/api/graphql") and
BG.MatchFormFields(FBLike)) then
BGResult.match = 1
BGResult.log = "Facebook like"
end
AWS Personal Console Control
This pattern will identify if users are trying to access personal AWS consoles.
[[LUA_AF_REQUEST_SCOPE]]
function IsUnsanctionedUser()
if not BG.cookie then
return false
end
if string.find(string.lower(BG.cookie), "<Your Company Name") then
return false
end
return true
end
if ((BG.method == "POST") and
string.find(BG.domain, "console%.aws%.amazon%.com$") and
string.find(string.lower(BG.uri), "/batchopsservlet%-proxy") and
IsUnsanctionedUser()) then
BGResult.match = 1
BGResult.log = "Amazon S3 upload by unsanctioned user."
end
For <Your Company Name>, you do not need the full domain since you are trying to identify the specific AWS subdomain. For example for Forcepoint ONE SSE, you would simply enter bitglass for <Your Company Name>.
Microsoft 365 Identify Personal Login
This pattern will identify personal login attempts to M365 for admins to block or control. You will need to enter your domain where stated in order for the policy to match when your domain is not seen during a user's login attempt.
[[LUA_AF_REQUEST_SCOPE]]
local corp = "<Your Domain>"
local function NonCorpLogin(keyValueTable)
local un = keyValueTable["username"]
if not un then
return false
end
if (string.find(un, "@" .. corp .. "$") or
string.find(un, "40" .. corp .. "$")) then
return false
end
return true
end
if ((BG.method == "POST")
and BG.domain:find("^login%.microsoftonline%.com$")
and BG.uri:find("^/common/GetCredentialType")
and BG.MatchFormFields(NonCorpLogin)) then
BGResult.match = 1
BGResult.log = "Non-Corporate Login"
end
Microsoft Prevent Sharing Controls
This pattern will help identify the action of sharing a file with an external user.
[[LUA_AF_REQUEST_SCOPE]]
if ((BG.method == "POST")
and BG.domain:find("%.sharepoint%.com$")
and BG.uri:find("/ShareLink")) then
result = ""
owner = BG.uri:match(".*/(%S+)/_api/")
if (owner) then
owner = owner:gsub("_", "@", 1)
if (owner) then
owner = owner:gsub("_", ".")
end
end
loc = BG.domain:find("%.sharepoint%.com")
if (loc) then
sp_or_od = BG.domain:sub(1, loc-1)
if (sp_or_od:find("%-my$")) then
result = "OneDrive link shared by: " .. owner .. ", "
else
result = result .. "SharePoint link shared: "
end
result = result .. BG.domain .. BG.uri .. "?" .. BG.qs
end
BGResult.match = 1
BGResult.log = result
end