go-tiny-mfa: a multifactor solution written in go

go-tiny-mfa

a tinymfa implementation written in go. See https://tinymfa.parzival.link for more information.

Our repository on github: https://github.com/ghmer/go-tiny-mfa.

Find a docker repository at https://hub.docker.com/r/tinymfa/go-tiny-mfa

Attention This is a hobby project to get more used to go-programming. It is not intended to be used in a production environment without making further security related steps.

How it works

  1. tinymfa connects to a postgres database and creates the required table structures. Then, it generates a root encryption key and access token. The encryption key is stored on the filesystem.
  2. when creating an issuer, a new encryption key is generated, encrypted with the root encryption key and then stored to the database. Also, an access token unique to this issuer is generated as well.
  3. when creating a user below an issuer, a new secret key is generated and encrypted with the issuer encryption key.
  4. The api offers an endpoint to generate a QRCode for a user. Use this to let the user register their secret key in an Authenticator App
  5. The api offers an endpoint to validate a token. Send the token using a http post request to the api interface. The resulting json object contains the boolean result of the validation.

Access tokens

tinymfa can be configured to validate access to its resources. Once activated, tinymfa checks for presence of the http header key ‚tiny-mfa-access-token‘. This must be either the root token created on installation, or the issuer token presented upon issuer creation.

API Endpoints

System Configuration and Audit

EndpointMethodDescription
/api/v1/system/auditGETReturn audit entries
/api/v1/system/configurationGETReturn current system configuration
/api/v1/system/configurationPOSTUpdates the system configuration

payload: Update system configuration

keytypedescription
http_portintegerthe port to run on. Requires a restart!
deny_limitintegerhow many times is a user allowed to input a wrong token before we don’t allow validation for the given message. This is to defeat brute force attacks
veriy_tokenbooleanwhether to verify if the tiny-mfa-access-token is set and contains a valid token
{
    "http_port" : 57687,
    "deny_limit": 3,
    "verify_tokens": true
}

Issuer handling

EndpointMethodDescription
/api/v1/issuerGETReturn all registered issuers
/api/v1/issuerPOSTCreate a new issuer using a POST request
/api/v1/issuer/{issuer}GETReturn a distinct issuer by its name
/api/v1/issuer/{issuer}POSTUpdates a distinct issuer using a POST request
/api/v1/issuer/{issuer}DELETEDeletes a distinct issuer using a DELETE request

payload: create a new issuer

keytypedescription
namestringthe name of this issuer
contactstringa mail adress of the responsible person
enabledbooleanwhether this issuer is active
{
    "name": "issuer.local",
    "contact": "demo@issuer.local",
    "enabled": true
}

payload: update a new issuer

keytypedescription
contactstringa mail adress of the responsible person
enabledbooleanwhether this issuer is active
{
    "contact": "demo@issuer.local",
    "enabled": true
}

Access token handling

EndpointMethodDescription
/api/v1/issuer/{issuer}/tokenGETReturn all registered access tokens for a given issuer
/api/v1/issuer/{issuer}/tokenPOSTCreates a new access token for the given issuer using a PUT request
/api/v1/issuer/{issuer}/token/{tokenid}DELETEDeletes a distinct access token in the scope of a distinct issuer

payload: create a new issuer access token

keytypedescription
descriptionstringa description for the new token
{
    "description" : "my access token"
}

User handling

EndpointMethodDescription
/api/v1/issuer/{issuer}/usersGETReturn all users belonging to the scope of a distinct issuer
/api/v1/issuer/{issuer}/usersPOSTCreate a new user in the scope of a distinct issuer
/api/v1/issuer/{issuer}/users/{user}GETReturn a distinct user in the scope of a distinct issuer
/api/v1/issuer/{issuer}/users/{user}POSTUpdate a distinct user in the scope of a distinct issuer
/api/v1/issuer/{issuer}/users/{user}DELETEDeletes a distinct user in the scope of a distinct issuer

payload: create a new user

keytypedescription
namestringthe name this user
emailstringa mail adress of the user
enabledbooleanwhether this user is active
{
    "name" : "demo",
    "email": "demo@issuer.local",
    "enabled": true
}

payload: update an existing user

keytypedescription
emailstringa mail adress of the user
enabledbooleanwhether this user is active
{
    "email": "demo.address@issuer.local",
    "enabled": true
}

User token handling

EndpointMethodDescription
/api/v1/issuer/{issuer}/users/{user}/totpGETGenerates and returns a PNG image of a QRCode in the scope of a distinct user and issuer
/api/v1/issuer/{issuer}/users/{user}/totpPOSTValidates a given token in the scope of a distinct user and issuer

payload: validate a totp token

keytypedescription
tokenstringthe token to validate
{
    "token": "123456"
}

docker-compose

This will result in a working tiny-mfa instance:

version: "3"
services:
    database:
        image: postgres:latest
        networks: 
            - tiny-mfa-net
        volumes:
            - data:/var/lib/postgresql/data
        environment: 
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=postgres
            - POSTGRES_DB=tinymfa
    
    tinymfa:
        image: tinymfa/go-tiny-mfa
        networks:
            - tiny-mfa-net
        ports:
            - "57687:57687"
        volumes:
            - tinysecret:/opt/go-tiny-mfa/secrets
        environment:
            - POSTGRES_HOST=database
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=postgres
            - POSTGRES_DB=tinymfa
        restart: unless-stopped
volumes: 
    data:
    tinysecret:

networks: 
    tiny-mfa-net:

quickstart

  • create a tiny-mfa instance using the docker-compose script from above
  • create an issuer:
curl --location --request POST 'http://localhost:57687/api/v1/issuer' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "issuer.local",
    "contact": "contact@issuer.local",
    "enabled": true
}'
  • create a user:
curl --location --request POST 'http://localhost:57687/api/v1/issuer/issuer.local/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name" : "demo",
    "email": "demo@issuer.local",
    "enabled": true
}'
  • get the QRCode for the Authenticator App:
curl --location --request GET 'http://localhost:57687/api/v1/issuer/issuer.local/users/demo/totp'
  • validate a token
curl --location --request POST 'http://localhost:57687/api/v1/issuer/issuer.local/users/demo/totp' \
--header 'Content-Type: application/json' \
--data-raw '{
    "token" : "123456"
}'

Already working

  • v1 api to CRUD issuers and users
  • validate tokens
  • limit validation attempts to defeat brute force attacks
  • generate QRCode png images
  • basic authorization via http header

Todo

  • authorization model
  • administrative UI