~ekez/discord-notifier

df17a3f7d2468a12cafecdb882196846e6e8d001 — Zeke Medley 5 days ago 4c628c7
Add READMEs as needed
A README => README +38 -0
@@ 0,0 1,38 @@
# Discord Notifier

This repository contains the source code for a ssh login notification
service. When running this software will send discord messages to a
channel of your choice on shh logins.

In addition to the code needed to send these messages, this also
includes the code for a server and install script. When used together
these programs will automate the process of configuring a discord
webhook for a server of your choice and installing the notification
system on your machine.

If you would like you can actually pay money for this software. If you
do that I'll provide support over email if things do not work like you
might expect. [Here](https://buy.stripe.com/3cs4jc2q09ug90IbII) is a
link where you can do that.

The reccomended setup is to run this software on your syslogs server
so that notifications will be sent for logins on all machines
involved.

## Organization

- `/server` contains the source code for a server which is needed to
  communicate with the Discord API and configure a webhook
  automatically.
- `/client` contains the source code for a client side install script.
- `/workers-server` contains the source code for a Cloudflare Workers
  version of `/server`. This version is equally functional as
  `/server` but high latency for Workers KV write propogration makes
  `/server` preferable for most use cases.

## Deploying yourself

Each folder listed above has a README file which contains information
about deploying that component. You can read more about how this all
fits together in the initial functional spec for this work
[here](intro-spec.md).

A client/README => client/README +33 -0
@@ 0,0 1,33 @@
This folder contains an install script. When run will authenticate
with Discord and install the rsyslog changes needed to send Discord
messages on SSH logins.

## Deploying this yourself

So long as I continue to run discord.negativefour.com this ought to
work out of the box. In the event that I stop running that you will
need to do the following to self host:

1. Run a server using the code in `../server/`.
2. Modify the `baseurl` and `client_id` variables in `install.sh` to
   point towards your server and discord application respectively. See
   the documentation [here](https://discord.com/developers/docs/intro)
   for some information about how you might create a discord
   application.

## How this works

This uses the Discord oauth2 webhooks
[api](https://discord.com/developers/docs/topics/oauth2#webhooks) to
grant access to a discord application to send messages to a
server. The process via which this is done is as follows:

1. The user is prompted to open a URL in their browser to authorize
   the discord application using a random state url param which is
   generated locally.
2. After authorization the user is redirected to `../server` where the
   authorization code is stored in a KV store which maps `state ->
   code`.
3. `install.sh` polls the server asking if there is a code for the
   state it sent until the server either responds with a webhook URL
   or informs the script that an error occured.

A intro-spec.md => intro-spec.md +85 -0
@@ 0,0 1,85 @@
# Discord Webhook Creation

We can prompt the user to authorize a webhook. The process for this is
as follows:

1. Direct the user to a URL. The URL contains information about our
   application, that it wants to create a webhook, and where to send
   the user when the webhook is created.
2. Once the user authenticates the webhook they get redirected and a
   `?code=` querystring is included.
3. We then submit the code querystring to Discord and they return
   information about the webhook.

The fact that a redirect URL is required for authentication means that
we can't generate this script entirely on the user's machine.

## Generation Process

1. The script remains local.
2. The user makes a request to the Discord api with a redirect url
   that is controlled by us.
3. The user generates a random state paramater when they make that
   request.
4. The user authenticates the webhook.
5. Discord redirects the user to our page with the `?code=` url param.
   1. We make a request to Discord to get the webhook url.
   2. We store the webhook url in a KV store with a key equal to the
      state returned by Discord. The stored key should have a TTL that
      is slightly larger than 2* the poll interval of the local
      script.
6. At this point a GET request to `<our-url>/states?q=<generated
   state>` will return the webhook url. If the user authentication
   failed then it will return some error value. Upon a request for the
   state our server will delete the data.
7. If a GET request sees an error value the script informs the user of
   the problem and terminates.
8. If the GET request sees it now has the webhook url and can generate
   the `notify.sh` script.

## Security considerations

1. An attacker could crawl `<our-url>/states?q=*` and potentially see
   users codes.
   - We use a 64 bit number for states so odds of collision is low.
   - A reasonably aggressive TTL set for stored values further reduces
     this likelyhood of a collision.
   - Codes are deleted on first read.

2. An attacker that comprimizes our Cloudflare account could view the
   data stored in the KV store and send spam messages from the
   webhook.
   - We delete the webhook urls pretty agressively.
   - We have 2fa set up on our Cloudflare account.

## Technical details

### Server

#### `/states`

- Requires `?q=` querystring in GET requests.
- Looks up query in KV and returns the value as plaintext.
- Returned value is either a URL, a 404 if no such value exists, or
  `FAIL` if Discord indicated to us that the user authorization
  failed.
- Values are deleted on lookup.

#### `/codes`

- Accepts `?code=` and requires an accompanying `?state=` paramater.
- Stores `state -> code` in KV with a timeout of 14 seconds.
- On an error Discord will submit a `?error=` code and `state -> FAIL`
  will be stored in KV.

### Client

1. Generates a random state value.
2. Using that state value prints an authentication URL and requests
   that the user visits it in their browser.
3. Polls `discord.negativefour.com/states?q=<generated state>` every 5
   seconds for a maximum of 5 minutes.
4. If polling succedes uses the returned URL to generate a `notify.sh`
   script which for now we can just print to stdout.
5. If polling fails exists and informs the user that something has
   gone wrong.

M server/README => server/README +39 -2
@@ 1,2 1,39 @@
An express server. Workers KV has high latency for write propogation,
this does not
`../client/install.sh` interacts with this server to install discord
notifications on a target machine.

## Deploying this yourself

This is a reasonably standard issue express server. To run it:

```
npm install
bash run.sh
```

`run.sh` will start the express server and set up an iptables NAT rule
which will forward traffic on port 80 to the server running on
port 3000.  This prevents us from needing to run the server as root or
give it any privileges.

## How this works

### `/states`

- Requires `?q=` querystring in GET requests.
- Looks up the query in a KV store.
- If the value exists:
  - The value is deleted from the KV store.
  - The discord API is queried to create a webhook using the value.
  - A webhook URL is returned to the requester.
- If the value does not exist:
  - A 404 status code is returned.
- If the value exists but is `FAIL`:
  - The value is deleted from the KV store.
  - A 407 response is returned.

### `/codes`

- Accepts `?code=` and requires an accompanying `?state=` paramater.
- Stores `state -> code` in KV with a timeout of 14 seconds.
- On an error Discord will submit a `?error=` code and `state -> FAIL`
  will be stored in KV.

M server/containers/Dockerfile.install => server/containers/Dockerfile.install +0 -1
@@ 1,2 1,1 @@
FROM debian:11


A server/run.sh => server/run.sh +7 -0
@@ 0,0 1,7 @@
#!/bin/bash

# NAT traffic to the node app
sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3000

# Run the app
node src/app.js

M workers-server/README => workers-server/README +2 -1
@@ 1,2 1,3 @@
A cloudflare workers server. Workers KV has high latency for write
operations so this is being phased out.
\ No newline at end of file
operations so this is being phased out. You will very likely prefer to
use `../server/`.
\ No newline at end of file