Posts about security

Authentik Gotifiy Login Notifications

SSO all the things

Continuing with my journy of utilising Authentik for my SSO. After reading a rather good comment by /u/internallogictv over on the reddit /r/selfhosted, I wanted to add a few more protections. The simplest of which is to send myself a notification whenever a login or a failed login occurs.

Step 1

First things first we create a new application in gotify in order to generate a token for authentik use. Select the Apps tab and press the Create Application button.

Gotify create an application

Step 2

Create a new gotify property mapping in the Admin Interface -> Customisation -> Property Mappings.

I've built this so a login failed is set to the maximum gotify priority level regardless of the user group. For successful logins I divide the levels based on the group gotify-users. I algo create a geo uri for mapping applications on android. You will be able to click the notification and it will open the city co-ordinates, although you may have to skip this if you don't have the geoipupdate container configured.

try:
    # Get the login failed username
    event_user = notification.event.context["username"]
except:
    # Get the login succeeded username
    event_user = notification.event.user["username"]

if notification.event.action == "login_failed":
    priority = 7
    severity = "warning"
elif ak_is_group_member( ak_user_by(username=event_user), name="gotify-users" ): # Check if the user belongs to group
    priority = 1
    severity = notification.severity
else: # default notification settings
    priority = 0
    severity = notification.severity

# Build a geo uri for opening a mapping applications from the gotify notification.
geo_uri = f"geo:{notification.event.context['geo']['lat']},{notification.event.context['geo']['long']}?q={notification.event.context['geo']['lat']},{notification.event.context['geo']['long']}"

title = f"{severity} from authentik {notification.event.action.replace('_', ' ')}".capitalize()

message = f"New {notification.event.action.replace('_', ' ')} for {event_user} was detected coming from {notification.event.context['geo']['city']} {notification.event.context['geo']['country']} from the IP address: {str(notification.event.client_ip)}".capitalize()

# Build the gotify payload
gotify_payload = {
    "title": title,
    "message": message,
    "priority": priority,
    "extras": { "client::notification": { "click": { "url": geo_uri } }},
}

return gotify_payload

Step 3

Create a new notification transport Admin Interface -> Events -> Notification Transports using Webhook (generic) your gotify message url with the token created in step one https://example.tld/gotify/message?token=yourtokenhere

Step 4

Finally we create the notification rule that actually calls the Notification transport. Admin Interface -> Events -> Notification Rules Create a new rule login-notification sending to the group of your choice (This dosn't really matter but it will display an ugly json string as notification on the web UI). Select the Gotify notification transport you created and set the Severity to Notice.

Now we have to create the policies authentik-core-login and authentik-core-login-failed to the event. Expand the login-notification event and press Create Policy. Select Event Matcher Policy, name it authentik-core-login enable the Execution Logging option, select the Login action and authentik Core App. Finish and repeat for the Login Failed action.

Now you should be receiving Login and Login Failed notifications from your Authentik instance over Gotify. I Hope I'll be able to update this to pull different tokens from the user/group attributes in the future to better separate notifications to individual users/admins.


Node-Red SSO with Authentik

Node-RED is a flow-based programming tool, originally developed by IBM’s Emerging Technology Services team and now a part of the JS Foundation.

Following my last post regarding SSO with Authentik I thought I should post my passportjs configuration for Node-Red and OpenidConnect. Currently User accounts work, however I haven't gotten group based permissions setup yet.

Note This guide is based off the Gitea integration guide from the Authentik docs.

Preparation

The following placeholders will be used:

authentik.company is the FQDN of authentik.

nodered.company is the FQDN of nodered.

Step 1

In authentik, create an OAuth2/OpenID Provider (under Resources/Providers) with these settings:

note

Only settings that have been modified from default have been listed.

Protocol Settings

Name: nodered
Signing Key: Select any available key

note

Take note of the Client ID and Client Secret, you'll need to give them to nodered in Step 3.

Step 2

In authentik, create an application (under Resources/Applications) which uses this provider. Optionally apply access restrictions to the application using policy bindings. note

Only settings that have been modified from default have been listed.

Name: nodered
Slug: nodered-slug
Provider: nodered

Step 3

note

We are assuming node-red is installed under docker

Navigate to the node-red data volume data/node_modules/. Alternatively enter the docker container sudo docker exec -it nodered bash and cd /data/node_modules

Use npm to install passport-openidconnect npm install passport-openidconnect

Edit the node-red settings.js file /data/settings.js

adminAuth: {
type:"strategy",
strategy: {
        name: "openidconnect",
        label: 'Sign in with authentik',
        icon:"fa-cloud",
        strategy: require("passport-openidconnect").Strategy,
        options: {
                issuer: 'https://authentik.company/application/o/<application-slug>/',
                authorizationURL: 'https://authentik.company/application/o/authorize/',
                tokenURL: 'https://authentik.company/application/o/token/',
                userInfoURL: 'https://authentik.company/application/o/userinfo/',
                clientID: '<Client ID (Key): Step 2>',
                clientSecret: '<Client Secret: Step 2>',
                callbackURL: 'https://nodered.company/auth/strategy/callback/',
                scope: ['email', 'profile', 'openid'],
                proxy: true,
        verify: function(issuer, profile, done) {
                done(null, profile)
        }
      }
    },
    users: function(user) {
        return Promise.resolve({ username: user, permissions: "*" });
    }
},

SSO with Authentik

SSO all the things

A while back I wrote about minimising my attack surface by utilising default deny and whitelists in Nginx. Now I've gotten into the weeds with authentication and deployed an SSO (Signle sign-on) service on my selfhosted infrastructure.

What is Authentik?

Authentik is a SSO (Single Sign on) provider, much like with Google's services you sign in once and then you can access all your services. This has been a big bugbear with selfhosted applications, with Roundcubemail TTRSS plugin, auto authentication for Tiny Tiny RSS against an IMAP Server and Codiad External Authentication via IMAP to name a few work arounds to the issue I have hacked together over the years.

Most importantly for my use case is the single pane of glass to access my services:

A nice dashboard really brings it all together

The Issues

Introducing a SSO system introduces complexity and potential problems so it's not all smooth sailing, passwords are a thing still as they are simple and reliable and understandable.

New Project new problems, limited reviews

Authentik's first beta release was in Jan 2020 so it's very new and has had a few teething issues and quite a few bugs. I highly recommend utilising additional security methods in front of authentik (IDS/IPS, Geo Blocking and ideally using a VPN to access) until it reaches maturity.

Poor Documentation

Quite frankly the documentation isn't great if you are attempting to figure out HOW it’s supposed to work. Thankfully they have integration guides included in the docs that covers the gaps, so some reading between the lines is needed for a while yet.

Limited compatibility

Not everything has SSO support (SAML, Oauth/OpenidConnect or reverse Proxy Authentication), thankfully this isn't as hard to deal with as it once was:

The main issue I have faced is with HomeAssistant. The developers have been reluctant/resistant to adding additional authentication methods to the project. There is the hass-auth-header project created by the developer of Authentik, however the HomeAssistant Android app is frustratingly a major sticking point.


I shouldn't use sudo nano

Over on /r/linux a user going by /u/AlternOSx posted a short You should Know: YSK : Do not use 'sudo vim/nano/emacs..' to edit a file. Instead, set your $EDITOR and use sudoedit or sudo -e.

Long story short sudoedit copies the file you want to edit to /tmp/file.xxx and then opens it with an unprivileged instance of your editor of choice. It then overwrites the source file when you are finished editing, protecting from accidental privilege escalation of commands through your text editor.

Knowing this I came up with a quick way to enforce this best practice by added this function into my .bashrc file. Hopefully I can retrain myself not to use sudo nano all the time.

# Define the default editor in this case nano.
EDITOR=nano

# Catch calls to sudo.
function sudo() {
  if [[ $1 == "$EDITOR" ]]; then
    # The editor has been called

    if [ -w "$2" ]; then
      # If the file is writable by the current user just use the editor as normal.

      command $EDITOR "$2"
    else
      # The file is not writable use sudoedit.
      command sudoedit "$2"
    fi
  else
    # Use sudo as normal.
    command /usr/bin/sudo "$@"
  fi
}

Minimizing my selfhosted attack surface

Tweeking my linux server

I'm always fairly wary of opening my selfhosted services up to the internet, just how sure am I that the developer has done the right due-diligence? Thankfully it's relatively simple to at least limit parts of a service accessible to the open internet with Nginx and allow and deny options.


Update

Note: If you want a docker container to access a protected service you will have to set the subnet in your docker-compose file as below:

networks:
  node-red-network:
    ipam:
      config:
        - subnet: "172.16.0.0/24"

Update 2

A more generic change you can do is set the default address pools in docker's /etc/docker/daemon.json file. You then just have to whitelist 172.16.0.0/16 subnets

{
  "default-address-pools":
  [
    {"base":"172.16.0.0/16","size":24}
  ]
}

First you should store this in a file, that way you can then include it multiple times, this will make it trivial to update in the future. Create the file include_whitelist in your nginx folder, adding your own allow options between satisfy any; and deny all;.

satisfy any;

# allow localhost
allow 127.0.0.1;

# allow a single address
# allow 000.000.000.000;

# allow LAN network
allow 192.168.0.0/24;

# allow WLAN network
allow 192.168.2.0/24;

# allow VPN network
allow 10.1.4.0/24;

# drop rest of the world
deny all;

You then have to include the file in your nginx config. Here I am using TT-RSS as an example, Note that I'm excluding the API and the public.php by having it in a separate location block without including the include_whitelist. This allows me to keep accessing TT-RSS on my mobile phone through the mobile application.

  location ^~ /tt-rss/ {
      include /etc/nginx/include_whitelist;

      access_log off;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/;

  }

  location ^~ /tt-rss/api {

      access_log off;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/api;

  }

  location ^~ /tt-rss/public.php {

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/public.php;

  }

For Node-Red I wanted an API endpoint for Tasker on my phone. Thankfully this is just as easy to define in Node-red as it is in nginx. In Node-Red open your GET node and just add another folder.

Add and extra folder to your Node-Red endpoints

An example of the Node-Red nginx configuration. Again just like the TT-RSS example above, I have excluded an api subdirectory by having a separate location block.

  location ^~ /node-red/ {
    include /etc/nginx/include_whitelist;

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:1880/node-red/;
  }

  location ^~ /node-red/api/ {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:1880/node-red/api/;
  }

Now only your API endpoints are globally available. If like me you use a firewall, throwing a convenient geo-block up in front you can lower the exposure a bit more.


TOTP with sudo (Google Auth)

I was reading the posts over on lobste.rs and saw this post: Is sudo almost useless?. Typically I see sudo as a safety belt to protect you from doing something stupid with administrator privileges rather than a security shield. But that doesn't mean it can't be both

As with ssh, outlined in my previous post TOTP with SSH (Google Auth), you can certainly boost your sudo usefulness security wise by throwing 2FA via google-authenticator-libpam on top of it.

Install google-authenticator-libpam

On debian/ubuntu:

    sudo apt update && sudo apt install google-authenticator-libpam

Set-up your secret keys

We now need to create the secret key, this should not be kept in the user folder, after all what is the point of 2FA if the user we are authenticating can just read the secret files. In my case I keep them in the root dir

Replace the variable ${USER} if/when you create a key for a user other than the active one.

sudo google-authenticator -s /root/.sudo_totp/${USER}/.google_authenticator
sudo chmod 600 -R /root/.sudo_totp/

You will see a QR code/secret key that you can scan with a TOTP app like andotp, authy, google authenticator or in my case I added it to my yubikey. There are also your emergency scratch codes that you should record somewhere safe.

Enable in PAM

You now need to let PAM know it should be checking the codes. There are two ways to do this, Mandatory and Only if secret key exists. I have it as Mandatory any user using sudo MUST have a secret key

In /etc/pam.d/sudo add the following configuration lines to the end of the file.

# Use Google Auth -- Mandatory
auth required pam_google_authenticator.so secret=/root/.sudo_totp/${USER}/.google_authenticator user=root

# Use Google Auth -- Only if secret key exists
# auth required pam_google_authenticator.so secret=/root/.sudo_totp/${USER}/.google_authenticator user=root nullok

Bonus do this for su as well

You can do the same thing for su as well however obviously the user variable will be root rather than the user attempting to elevate their privilege's.

Setup the key as before, just for the root user

sudo google-authenticator -s /root/.google_authenticator
sudo chmod 600 -R /root/.google_authenticator

In /etc/pam.d/su add the following configuration lines to the end of the file.

# Use Google Auth -- Mandatory
auth required pam_google_authenticator.so secret=/root/.google_authenticator user=root

Nextcry and Nextcloud security

About a month ago there was an urgent security notice from the Nexcloud devs regarding a flaw in Nginx php-fpm and the associated Nextcloud config. Unfortunately we are now seeing it being exploited in the wild.

The Nextcloud devs have confirmed that it doesn't appear to be an issue with Nextcloud itself and that patching and updating is highly advised.

This brings to mind some extra security measures I do for Nextcloud on top of my standard server checklist

Have extra security advice? Let me know in the comments down below!


SSH Login Notifications with Gotify

Gotify is a simple server for sending and receiving messages

Inspired by this post I decided to add a notification on my phone every time an ssh session began on my servers. Seeing as I make use of Gotify for selfhosted push notifications I used that rather than signal.

First I created created the file /usr/local/bin/sshnotif. At the top you can add your own token and Gotify url

Update: I had to push the current time back a full minute in order to improve consistency. I'll defiantly want to revisit this at a later date

#!/bin/bash

exec &> /dev/null #Hide output

Gotify_URL='https://example.tld/gotify'
Gotify_Token='gotify-app-token'

notify()
{

        now=$(date -d "-60 seconds" +%s) #Get current time minus 60 seconds
        end=$((SECONDS+30)) #Set 30s Timeout for loop

        while [ $SECONDS -lt $end ]; do

                SSHdate=$(date -d "$(who |grep pts|tail -1 | awk '{print $3, $4}')" +%s) #Check for the latest SSH session

                if [ $SSHdate -ge $now ]; then #Once who is updated continue with sending Notification

                        title="SSH Login for $(/bin/hostname -f)"
                        message="$(/usr/bin/who | grep pts)"

                        /usr/bin/curl -X POST -s \
                                -F "title=${title}" \
                                -F "message=${message}" \
                                -F "priority=5" \
                                "${Gotify_URL}/message?token=${Gotify_Token}"

                        break
                fi
        done

}

notify & #Run in background to prevent holding up the login process

Run the command chmod +x /usr/local/bin/sshnotif

In the file /etc/pam.d/sshd add the following line

# note optional is set to prevent ssh login failure
session optional pam_exec.so /usr/local/bin/sshnotif

I now get a nice notification with all the open SSH sessions listed. Unlike the post on 8192.one I didn't want any IP address resolution using an online service. I plan on integrating the MaxMind GeoLite2 database at some point. However as I already have Graylog set up to do this it's not a high priority for me.

Thanks for the shoutout: https://zerosec.xyz/posts/gotify-notifications/


Server set-up Checklist

Configure your linux server

I often see questions on /r/selfhosted on how to secure a server. Here is a quick checklist of things you might want to look into.

Follow best practices for the basics

Lock down the Server

  • Disable root login via SSH
  • Close all unused incoming ports via UFW/iptables
  • Limit outgoing ports as well as incoming using UFW/iptables
  • Watch for credential stuffing/brute force attacks with Fail2ban

Backup your configs/files

  • Securely encrypted backup via Duplicity
  • External Backup to external drive.
  • Remote backup, either via a regularly swapped out external drive or via the cload

Set up monitoring services to let you know when something goes wrong

Here are a few extra things you can do to bolster your ssh security

Useful resources


Dugite is this video of you?

Ohhh a virus!

This morning I received a link in Facebook messenger from an old friend, someone I haven't spoken to in years. It was obviously a phishing link, the title was wrong "Dugite is this a video of you?" and the preview image was blurry, like it was stuck not loading.

Needless to say I didn't click and messaged them back saying "Probably should change your password?"

What Interests me is the reaction when this friend posted on their wall, within minuets, not to open any messages from them and they "HAVE A VIRUS ON MY PHONE". Out of 7 people 3 opened the obviously shady link.

Not one of these people who opened this link mentioned they will now need to change their password, in fact one person even said it all should be ok once it's gone through their entire address book. The non-technical people, to me at least, seem to be treating phishing and malware like you would the common cold. It'll pass, fact of life and not a real concern.

They of course should be very concerned with the majority of web browsing occurring on the mobile the amount of data that is potentially stored on a phone is astronomically large. From your banking app to your photos and everything you can gain access through your email accounts is at risk. Mobile operating systems are, thankfully, very locked down with each app operating somewhat isolated from the others, but that's not a guarantee.

I fear the next decade of tech breaches are going to get ugly and there just isn't a technical solution to user apathy.