Open-Source Tools: Pashword, {php}IPAM, Filebrowser

Published: Dec 7, 2023 by Isaac Johnson

Today we’ll look at a few useful containerized tools I find rather useful. Pashword for password hashing, {php}IPAM which has been around a long time, and lastly Filebrowser, a slick list OS tool that has been around at least 7 years. I only recently discovered this one and thoroughly enjoy it.

Pashword

Pashword is a nifty fully open-sourced password hash generator. It’s easy to run as a container.

First, let’s try just running the container on my dockerhost

builder@builder-T100:~$ docker run -p 3200:3000 --name pashwordtest peppershade/pashword:dev
Unable to find image 'peppershade/pashword:dev' locally
dev: Pulling from peppershade/pashword
213ec9aee27d: Already exists
4235e034d74f: Pull complete
33d6c3ba58fd: Pull complete
deebb336959c: Pull complete
1ff71f500ef1: Pull complete
e6a707c92640: Pull complete
Digest: sha256:958b99ec36b39cee96d5541e34c4a4f85ffaf6f3009fe58ef7a3b205840e311e
Status: Downloaded newer image for peppershade/pashword:dev

> start
> next start

ready - started server on 0.0.0.0:3000, url: http://localhost:3000

I can now test it locally

/content/images/2023/12/misctools-01.png

The idea of a Password Hash generator like this is that if you take a known destination, with a username, and then a seed key (secret key) you can remember, you can generate a secure password. Then when you need to use it again, you’ll remember the pieces to generate again.

Like you might expect, my big issue here is that it’s internal only and lacks TLS.

We can solve that quickly by adding an A record for ‘pashword’.

$ cat r53-pashword.json
{
    "Comment": "CREATE pashword fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "pashword.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-pashword.json
{
    "ChangeInfo": {
        "Id": "/change/C00944845714AI9ADLSG",
        "Status": "PENDING",
        "SubmittedAt": "2023-11-21T00:16:33.741Z",
        "Comment": "CREATE pashword fb.s A record "
    }
}

My next step is to create a Deployment, Service and Ingress object in Kubernetes YAML that will launch this:

$ cat pashword.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pashword-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  selector:
    matchLabels:
      app: pashword
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: pashword
    spec:
      containers:
      - image: peppershade/pashword:dev
        imagePullPolicy: IfNotPresent
        name: pashword-container
        ports:
        - containerPort: 3000
          protocol: TCP
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
  name: pashwordserver-service
spec:
  internalTrafficPolicy: Cluster
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 3000
  selector:
    app: pashword
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "3600"
    nginx.org/proxy-read-timeout: "3600"
    nginx.org/websocket-services: pashwordserver-service
  labels:
    app.kubernetes.io/name: pashword
  name: pashwordingress
spec:
  rules:
  - host: pashword.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: pashwordserver-service
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - pashword.freshbrewed.science
    secretName: pashword-tls

I can now create the namespace and then launch it

$ kubectl create ns pashword
namespace/pashword created

$ kubectl apply -f pashword.yaml -n pashword
deployment.apps/pashword-deployment created
service/pashwordserver-service created
ingress.networking.k8s.io/pashwordingress created

I can see it launched

$ kubectl get deployment -n pashword
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
pashword-deployment   2/2     2            2           93s

$ kubectl get svc -n pashword
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
pashwordserver-service   ClusterIP   10.43.194.222   <none>        80/TCP    2m20s

$ kubectl get ingress -n pashword
NAME              CLASS    HOSTS                          ADDRESS                                                PORTS     AGE
pashwordingress   <none>   pashword.freshbrewed.science   192.168.1.215,192.168.1.36,192.168.1.57,192.168.1.78   80, 443   2m25s

which came up without issue

/content/images/2023/12/misctools-02.png

I tested a password I had created earlier and it came up with the same password. However, this time I have it secured with TLS.

I wouldn’t expect others to use it. In fact, any random website that “generates passwords” should always be treated as suspect. But I trust myself and so it’s a nice secured self-hosted password creator I can reach from on-prem, remote or on secured shared machines.

PHPIPAM

Phpipam was created in 2014 by Miha Petkovsek. It’s clearly a passion project as he has phpipam as his Github username.

I’ve used it at my current job for one of our BUs for some time, but had not realized how easy it was to install.

To start, we need to create a database and app container in our Dockerhub

$ docker run --name phpipam-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -v /home/builder/phpiam/mysqlroot:/var/lib/mysql -d mysql:5.6
Unable to find image 'mysql:5.6' locally
5.6: Pulling from library/mysql
35b2232c987e: Pull complete
fc55c00e48f2: Pull complete
0030405130e3: Pull complete
e1fef7f6a8d1: Pull complete
1c76272398bb: Pull complete
f57e698171b6: Pull complete
f5b825b269c0: Pull complete
dcb0af686073: Pull complete
27bbfeb886d1: Pull complete
6f70cc868145: Pull complete
1f6637f4600d: Pull complete
Digest: sha256:20575ecebe6216036d25dab5903808211f1e9ba63dc7825ac20cb975e34cfcae
Status: Downloaded newer image for mysql:5.6
acd69511ada9c75cafec80425c418b6cdb0657cdeb0ad3132df1c596e05bff66

We can now launch the App container and link it to the MySQL

$ docker run -ti -d -p 8880:80 -e MYSQL_ENV_MYSQL_ROOT_PASS=my
-secret-pw --name ipam --link phpipam-mysql:mysql pierrecdn/phpipam
52c0289c7c735a1e269ad622af3bd67596f5dbdeb286168299933a50c00db627

I can now go to the App at http://192.168.1.100:8880

/content/images/2023/12/misctools2-01.png

I then created an admin user password

/content/images/2023/12/misctools2-02.png

One of the first things I wanted to do was add my Ethernet network.

By default, phpipam will pick up the subnet of itself, but as I’m running in docker, that doesn’t help too much. Once added, I can click the scan button:

/content/images/2023/12/misctools2-03.png

and tell it the type of scan (ping)

/content/images/2023/12/misctools2-04.png

When done, we can see results of our network

/content/images/2023/12/misctools2-05.png

The whole reason I wanted to do this was to catch any network reassignments.

Make sure when adding a subnet to flip the toggle on “Check hosts status”, “Discover New Hosts” and “Resolve DNS names”. For the first subnet, i also added the gateway (router) which serves as my local nameserver

/content/images/2023/12/misctools2-06.png

There are a couple utilities built in to PHPIPAM.

The first is the CIDR calculator:

/content/images/2023/12/misctools2-07.png

The other is a bandwidth calculator:

/content/images/2023/12/misctools2-08.png

The other tool built-in to PHPIPAM is a Vault for certs and passwords.

We can create a new password vault

/content/images/2023/12/misctools2-09.png

To open, we enter the password we used when we created it

/content/images/2023/12/misctools2-10.png

Once unlocked, I can create a password

/content/images/2023/12/misctools2-11.png

We add the name, value and an optional description

/content/images/2023/12/misctools2-12.png

We then can click the eyeball icon to view the password

/content/images/2023/12/misctools2-13.png

When done with the password vault, we will want to lock it

/content/images/2023/12/misctools2-14.png

Which will restore it to a locked state

/content/images/2023/12/misctools2-15.png

Certificate vaults are quite similar.

We create them in much the same way

/content/images/2023/12/misctools2-16.png

From there we can import certs

/content/images/2023/12/misctools2-17.png

Users

I’ll add an Operations user

/content/images/2023/12/misctools2-18.png

I’ll add them to specific groups with just read access

/content/images/2023/12/misctools2-19.png

Once, created, I can see the user details in the list

/content/images/2023/12/misctools2-20.png

Be aware that the password is just an initial password the user will need to change once they login

/content/images/2023/12/misctools2-21.png

Our user can search for names

/content/images/2023/12/misctools2-22.png

We can see that our user is rejected from running any actions such as a fresh scan

/content/images/2023/12/misctools2-23.png

We can also see that provided our user has the Vault unlock password, our user can view password, but not edit or add them

/content/images/2023/12/misctools2-24.png

FileBrowser

Another tool that has been on my list to checkout is “filebrowser”.

In Github, I can see releases going back to at least 2017 (1.0.0).

There are actually a fair number of blog articles already about it. I particularly liked the one on noted.lol which showed a lot of usage. I got a kickstart on this writeup from an easycode.page article.

First things first, we need a .filebrowser.json file that will have some settings. Note: we told it the DB file will be “/database.db”. I forgot that myself and spent way too long figuring out i was mounting “filebrowser.db” not “database.db”.

builder@builder-T100:~/projects$ ls -l /home/builder/.filebrowser.json
-rw-rw-r-- 1 builder builder 118 Nov 27 16:46 /home/builder/.filebrowser.json
builder@builder-T100:~/projects$ cat /home/builder/.filebrowser.json
{
  "port": 80,
  "baseURL": "",
  "address": "",
  "log": "stdout",
  "database": "/database.db",
  "root": "/srv"
}

Next, I created a folder that will house things and made a db file there.

builder@builder-T100:~/projects$ tree .
.
└── filebrowser
    └── filebrowser.db

1 directory, 1 file
builder@builder-T100:~/projects$ pwd
/home/builder/projects

I could now launch a docker version of the tool:

$ docker run -d -v /home/builder/projects/filebrowser:/srv -v /home/builder/projects/filebrowser/filebrowser.db:/database.db -v /home/builder/projects/filebrowser/.filebrowser.json:/.filebrowser.json --user $(id -u):$(id -g) -p 8899:80 filebrowser/filebrowser
fa20b4181ba6bb9ea9fb321dea6bbbdfcb00001d4d5dc0bcbc362f62a939bb14


$ docker ps
CONTAINER ID   IMAGE                                                            COMMAND                  CREATED         STATUS                            PORTS
                                             NAMES
fa20b4181ba6   filebrowser/filebrowser                                          "/filebrowser"           3 seconds ago   Up 2 seconds (health: starting)   0.0.0.0:8899->80/tcp, :::8899->80/tcp                                            silly_ptolemy

Since I’m exposing 8899, I logged in with the default “admin/admin” creds and immediately changed the password.

Now that I was in, I could easily see files, including the app’s db. That might not be a brilliant idea because i could easily trash the tool:

/content/images/2023/11/misctools-34.png

I killed that instance

builder@builder-T100:~/projects$ docker stop silly_ptolemy
silly_ptolemy
builder@builder-T100:~/projects$ docker rm silly_ptolemy
silly_ptolemy

It might actually be useful to use my Dockerhost to front my NAS.

Presently I am mounting my old NAS for DB backups

$ cat /etc/fstab  | tail -n1
192.168.1.129:/volume1/postgres-prod-dbbackups  /mnt/psqlbackups nfs    auto,nofail,noatime,nolock,intr,tcp,actimeo=1800        0       0

Instead, I’ll go to the new one and create a new shared folder

/content/images/2023/11/misctools-35.png

I’ll give it a name and uncheck the ‘restrict access to administrators only’ box

/content/images/2023/11/misctools-36.png

If I was sharing this to the world, I would very likely enable a shared quota lest I become a new anglefire (guessing not many remember that service, nor geocities which is why I had needed it).

/content/images/2023/11/misctools-37.png

Next, to use this for NFS, I’ll need to create some NFS permissions so we can mount it

/content/images/2023/11/misctools-38.png

I’ll make the directory, add an entry in /etc/fstab, and then mount it.

builder@builder-T100:~/projects$ sudo mkdir /mnt/filestation
builder@builder-T100:~/projects$ sudo vi /etc/fstab
builder@builder-T100:~/projects$ cat /etc/fstab | tail -n1
192.168.1.116:/volume1/docker-filestation  /mnt/filestation nfs    auto,nofail,noatime,nolock,intr,tcp,actimeo=1800        0       0

builder@builder-T100:~/projects$ sudo mount -a

I’ll launch a fresh filebrowser. I’m interested to see if it persisted my updated admin password even after I purged the old image (is it in the database.db)

$ docker run -d -v /mnt/filestation:/srv -v /home/builder/projects/filebrowser/filebrowser.db:/database.db -v /home/builder/projects/filebrowser/.filebrowser.json:/.filebrowser.json --user $(id -u):$(id -g) -p 8899:80 filebrowser/filebrowser
1fb429708cc7fcac174ee64d79d49a6407078a3d9851746029682a50e33ccd54

I can confirm it did save the password.

Once logged in, I can see files - and they don’t include the database.db file this time.

/content/images/2023/11/misctools-39.png

I chose file upload and uploaded a local image from a prior article that was still in my downloads folder

/content/images/2023/11/misctools-40.png

Heading back to the Synology interface, indeed I can see the file now safely tucked away on the volume

/content/images/2023/11/misctools-41.png

One of the handy features one can do is share a file for a duration. Here I shared it for an hour and used an incognito window to prove the URL worked

/content/images/2023/11/misctools-42.png

However, this isn’t too handy unless I’m in my network.

So let’s first add a quick A record in the normal way

builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-filebrowser.json
{
    "ChangeInfo": {
        "Id": "/change/C02575932ICQO87JD766R",
        "Status": "PENDING",
        "SubmittedAt": "2023-11-28T00:17:58.535Z",
        "Comment": "CREATE filebrowser fb.s A record "
    }
}
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ cat r53-filebrowser.json
{
    "Comment": "CREATE filebrowser fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "filebrowser.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }

With the A record in place, I’ll want to create a Endpoint, a Service and lastly an Ingress to use them:

$ cat filebrowser.ingress.yaml
apiVersion: v1
kind: Service
metadata:
  name: filebrowser-external-ip
spec:
  clusterIP: None
  internalTrafficPolicy: Cluster
  ports:
  - name: filebrowserp
    port: 80
    protocol: TCP
    targetPort: 8899
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  name: filebrowser-external-ip
subsets:
- addresses:
  - ip: 192.168.1.100
  ports:
  - name: filebrowserp
    port: 8899
    protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: filebrowser-external-ip
  labels:
    app.kubernetes.io/instance: filebrowseringress
  name: filebrowseringress
spec:
  rules:
  - host: filebrowser.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: filebrowser-external-ip
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - filebrowser.freshbrewed.science
    secretName: filebrowser-tls

$ kubectl apply -f filebrowser.ingress.yaml
service/filebrowser-external-ip created
endpoints/filebrowser-external-ip created
ingress.networking.k8s.io/filebrowseringress created

At first, I got a bad gateway error

/content/images/2023/11/misctools-43.png

Then it dawned on me; Doh! I forgot to start back up the service.

builder@builder-T100:~/projects$ docker stop sleepy_morse
sleepy_morse
builder@builder-T100:~/projects$
builder@builder-T100:~/projects$ docker start sleepy_morse
sleepy_morse

I saw stuck loading pages, but upon careful examination of the source, it seems it’s scrubbing a slash:

/content/images/2023/11/misctools-44.png

Even though I clearly have it there:

$ cat filebrowser/.filebrowser.json
{
  "port": 80,
  "baseURL": "https://filebrowser.freshbrewed.science",
  "address": "",
  "log": "stdout",
  "database": "/database.db",
  "root": "/srv"
}

I tried a variety of options. When I did finally just blank the value out, it worked fine again.

builder@builder-T100:~/projects$ cat filebrowser/.filebrowser.json
{
  "port": 80,
  "baseURL": "",
  "address": "",
  "log": "stdout",
  "database": "/database.db",
  "root": "/srv"
}

It must be for a subpath on a domain (e.g. “/filedrop”).

I updated to a strong password and tried a share

/content/images/2023/11/misctools-45.png

Uploading something of size was rejected

/content/images/2023/11/misctools-46.png

This is all too familiar. I need to set the proxy body size to 0

$ kubectl get ingress filebrowseringress -o yaml > filebrowser.ingressonly.yaml
$ cp filebrowser.ingressonly.yaml filebrowser.ingressonly.yaml.old
$ vi filebrowser.ingressonly.yaml
$ diff filebrowser.ingressonly.yaml filebrowser.ingressonly.yaml.old
5,11d4
<     ingress.kubernetes.io/proxy-body-size: "0"
<     ingress.kubernetes.io/ssl-redirect: "true"
<     nginx.ingress.kubernetes.io/proxy-body-size: "0"
<     nginx.ingress.kubernetes.io/ssl-redirect: "true"
<     nginx.org/client-max-body-size: "0"
<     nginx.org/proxy-connect-timeout: "3600"
<     nginx.org/proxy-read-timeout: "3600"

$ kubectl delete ingress filebrowseringress
ingress.networking.k8s.io "filebrowseringress" deleted
$ kubectl apply -f filebrowser.ingressonly.yaml
ingress.networking.k8s.io/filebrowseringress created

This time it worked

/content/images/2023/11/misctools-47.png

I tried a share and used my phone to test the download.

I can share for a month

/content/images/2023/11/misctools-48.png

even a year

/content/images/2023/11/misctools-49.png

If you trust me not to RickRole you, you can try it yourself with one of my older OSN presentations.

/content/images/2023/11/misctools-50.png

I found it worked fantastic from my mobile browser as well. In fact, I used it to upload and then download this screenshot:

/content/images/2023/11/misctools-51.jpg

Summary

We started off by exploring Pashword, a simple but useful tool for creating repeatable password hashes. We then moved on to setting up {php}IPAM. It’s a tool I find to be a pretty handy tool for tracking systems in my network. Lastly, we setup and tried out Filebrowser which, if for no other reason, is a great easy way to share files and upload from one’s mobile device.

I could have exposed {php}IPAM but fear the impact it might have without some kind of heavy security layer. I have been using Filebrowser on the regular since writing this post. It has so far proven to be exceedingly valuable for sharing files between devices easily.

Pashword PHPIPAM FileBrowser Opensource Container Docker

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes