Cleaned up some more wordpress issues

This commit is contained in:
Mike Conrad
2025-02-19 19:10:33 -05:00
parent 1fa31b4ace
commit 2ba204da15
14 changed files with 90 additions and 110 deletions

View File

@ -18,13 +18,13 @@ That system was in production for 5 years or so with minimal updates and maintan
This is the page listing all teachers in the system and whether or not they have admin privileges. Any admin user can grant other admin users this privilege. There is also a button to send the teacher a password reset email (via Postmark API integration) and an option that allows admin users to impersonate other users for troubleshooting and diagnostic purposes. This is the page listing all teachers in the system and whether or not they have admin privileges. Any admin user can grant other admin users this privilege. There is also a button to send the teacher a password reset email (via Postmark API integration) and an option that allows admin users to impersonate other users for troubleshooting and diagnostic purposes.
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hilger-portal-all-teachers-1024x514.webp)</figure>The data is all coming from the KeystoneJS backend GraphQL API. I am using [urql](https://www.npmjs.com/package/urql) for fetching the data and handling mutations. This is the page that displays students. It is filterable and searchable. Teachers also have the ability to mark a student as active or inactive for the semester as well as delete them from the system. ![](/wp-content/uploads/2024/03/hilger-portal-all-teachers-1024x514.webp)The data is all coming from the KeystoneJS backend GraphQL API. I am using [urql](https://www.npmjs.com/package/urql) for fetching the data and handling mutations. This is the page that displays students. It is filterable and searchable. Teachers also have the ability to mark a student as active or inactive for the semester as well as delete them from the system.
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hilger-portal-all-students-1-1024x513.webp)</figure>Clicking on a student takes the teacher/admin to an edit course screen where they can add and remove courses for each student. A teacher can add as many courses as they need. If multiple teachers have added courses for this student, the user will only see the courses they have entered. ![](/wp-content/uploads/2024/03/hilger-portal-all-students-1-1024x513.webp)Clicking on a student takes the teacher/admin to an edit course screen where they can add and remove courses for each student. A teacher can add as many courses as they need. If multiple teachers have added courses for this student, the user will only see the courses they have entered.
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hilger-portal-edit-student-1024x514.webp)</figure>There is another page that allows admin users to view and manage all of the parents in the system. It allows them to easily send a password reset email to the parents as well as to view the parent portal. ![](/wp-content/uploads/2024/03/hilger-portal-edit-student-1024x514.webp)There is another page that allows admin users to view and manage all of the parents in the system. It allows them to easily send a password reset email to the parents as well as to view the parent portal.
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hilger-portal-parents-1024x513.webp)</figure>--- ![](/wp-content/uploads/2024/03/hilger-portal-parents-1024x513.webp)---
## Technologies used ## Technologies used

View File

@ -12,11 +12,11 @@ title: Hoots Wings
While working for [Morrison](https://morrison.agency/) I had the pleasure of building a website for [Hoots Wings.](https://hootswings.com) The CMS was [Perch](https://grabaperch.com/) and it was mostly HTML, CSS, PHP and JavaScript on the frontend, however I built out a customer store locator using NodeJS and VueJS. While working for [Morrison](https://morrison.agency/) I had the pleasure of building a website for [Hoots Wings.](https://hootswings.com) The CMS was [Perch](https://grabaperch.com/) and it was mostly HTML, CSS, PHP and JavaScript on the frontend, however I built out a customer store locator using NodeJS and VueJS.
<figure class="wp-block-image size-full">![](https://hackanooga.com/wp-content/uploads/2024/03/hoots-home.webp)</figure>I was the sole frontend developer responsible for taking the designs from SketchUp and translating them to the site you see now. Most of the blocks and templates are built using a mix of PHP and HTML/SCSS. There was also some JavaScript for things like getting the users location and rendering popups/modals. ![](/wp-content/uploads/2024/03/hoots-home.webp)I was the sole frontend developer responsible for taking the designs from SketchUp and translating them to the site you see now. Most of the blocks and templates are built using a mix of PHP and HTML/SCSS. There was also some JavaScript for things like getting the users location and rendering popups/modals.
The store locator was a separate piece that was built in Vue2.0 with a NodeJS backend. For the backend I used [KeystoneJS](https://keystonejs.com/) to hold all of the store information. There was also some custom development that was done in order to sync the stores added via the CMS with [Yext](https://www.yext.com/) and vice versa. The store locator was a separate piece that was built in Vue2.0 with a NodeJS backend. For the backend I used [KeystoneJS](https://keystonejs.com/) to hold all of the store information. There was also some custom development that was done in order to sync the stores added via the CMS with [Yext](https://www.yext.com/) and vice versa.
<figure class="wp-block-image size-large">![hootswings.com screenshot](https://hackanooga.com/wp-content/uploads/2024/03/hoots-locations-min-1024x522.webp)</figure>For that piece I ended up having to write a custom integration in Perch that would connect to the NodeJS backend and pull the stores but also make sure that those were in sync with Yext. This required diving into the Yext API some and examining a similar integration that we had for another client site. ![hootswings.com screenshot](/wp-content/uploads/2024/03/hoots-locations-min-1024x522.webp)For that piece I ended up having to write a custom integration in Perch that would connect to the NodeJS backend and pull the stores but also make sure that those were in sync with Yext. This required diving into the Yext API some and examining a similar integration that we had for another client site.
Unfortunately I dont have any screen grabs of the admin side of things since that is proprietary but the system I built allowed a site admin to go in and add/edit store locations that would show up on the site and also show up in Yext with the appropriate information. Unfortunately I dont have any screen grabs of the admin side of things since that is proprietary but the system I built allowed a site admin to go in and add/edit store locations that would show up on the site and also show up in Yext with the appropriate information.
@ -26,8 +26,8 @@ Here are some full screenshots of the site.
Homepage Homepage
<figure class="wp-block-image size-large">![hootswings.com homepage screenshot](https://hackanooga.com/wp-content/uploads/2024/03/hootswings-home-full-635x1024.webp)</figure>Menu Page ![hootswings.com homepage screenshot](/wp-content/uploads/2024/03/hootswings-home-full-635x1024.webp)Menu Page
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hootswings-menu-page-550x1024.png)</figure>Locations Page ![](/wp-content/uploads/2024/03/hootswings-menu-page-550x1024.png)Locations Page
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/hootswings-locations-page-432x1024.png)</figure> ![](/wp-content/uploads/2024/03/hootswings-locations-page-432x1024.png)

View File

@ -15,7 +15,7 @@ title: Roll your own authenticator app with KeystoneJS and React
In this series of articles we are going to be building an authenticator app using KeystoneJS for the backend and React for the frontend. The concept is pretty simple and yes there are a bunch out there already but I recently had a need to learn some of the ins and outs of TOTP tokens and thought this project would be a fun idea. Lets get started. In this series of articles we are going to be building an authenticator app using KeystoneJS for the backend and React for the frontend. The concept is pretty simple and yes there are a bunch out there already but I recently had a need to learn some of the ins and outs of TOTP tokens and thought this project would be a fun idea. Lets get started.
##### Step 1: Init keystone app ## Step 1: Init keystone app
Open up a terminal and create a blank keystone project. We are going to call our app authenticator to keep things simple. Open up a terminal and create a blank keystone project. We are going to call our app authenticator to keep things simple.
@ -157,10 +157,10 @@ $ keystone dev
Our server should be running on localhost:3000 so lets check it out! The first time we open it up we will be greeted with the initialization screen. Go ahead and create an account to login: Our server should be running on localhost:3000 so lets check it out! The first time we open it up we will be greeted with the initialization screen. Go ahead and create an account to login:
<figure class="wp-block-image size-full">![](https://hackanooga.com/wp-content/uploads/2024/03/keystone-init-min.webp)</figure>Once you login you should see a dashboard similar to this: ![](/wp-content/uploads/2024/03/keystone-init-min.webp)Once you login you should see a dashboard similar to this:
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/keystone-dashboard-min-1024x607.webp)</figure>You can see we have Users and Tokens that we can manage. The beauty of KeystoneJS is that you get full CRUD functionality out of the box just by defining our schema! Go ahead and click on Tokens to add a token: ![](/wp-content/uploads/2024/03/keystone-dashboard-min-1024x607.webp)You can see we have Users and Tokens that we can manage. The beauty of KeystoneJS is that you get full CRUD functionality out of the box just by defining our schema! Go ahead and click on Tokens to add a token:
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/create-token-min-1024x572.webp)</figure>For this example I just entered some random text as an example. This is enough to start testing out our TOTP functionality. Click Create Token and you should see a list displaying existing tokens: ![](/wp-content/uploads/2024/03/create-token-min-1024x572.webp)For this example I just entered some random text as an example. This is enough to start testing out our TOTP functionality. Click Create Token and you should see a list displaying existing tokens:
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/03/display-token-dashboard-min-1024x275.webp)</figure>We are now ready to jump into the frontend. Stay tuned for pt 2 of this series. ![](/wp-content/uploads/2024/03/display-token-dashboard-min-1024x275.webp)We are now ready to jump into the frontend. Stay tuned for pt 2 of this series.

View File

@ -15,7 +15,7 @@ title: Roll your own authenticator app with KeystoneJS and React - pt 2
In part 1 of this series we built out a basic backend using KeystoneJS. In this part we will go ahead and start a new React frontend that will interact with our backend. We will be using Vite. Lets get started. Make sure you are in the `authenticator` folder and run the following: In part 1 of this series we built out a basic backend using KeystoneJS. In this part we will go ahead and start a new React frontend that will interact with our backend. We will be using Vite. Lets get started. Make sure you are in the `authenticator` folder and run the following:
``` ```shell
$ yarn create vite@latest $ yarn create vite@latest
yarn create v1.22.21 yarn create v1.22.21
[1/4] Resolving packages... [1/4] Resolving packages...
@ -43,7 +43,7 @@ Done in 10.20s.
Lets go ahead and go into our frontend directory and get started: Lets go ahead and go into our frontend directory and get started:
``` ```shell
$ cd frontend $ cd frontend
$ yarn $ yarn
yarn install v1.22.21 yarn install v1.22.21
@ -70,13 +70,13 @@ Port 5173 is in use, trying another one...
Next go ahead and open the project up in your IDE of choice. I prefer [VSCodium](https://vscodium.com/): Next go ahead and open the project up in your IDE of choice. I prefer [VSCodium](https://vscodium.com/):
``` ``` shell
codium frontend codium frontend
``` ```
Go ahead and open up `src/App.tsx` and remove all the boilerplate so it looks like this: Go ahead and open up `src/App.tsx` and remove all the boilerplate so it looks like this:
``` ```typescript
import './App.css' import './App.css'
function App() { function App() {
@ -92,17 +92,17 @@ export default App
Lets start by building a card component that will display an individual token. Our goal is something that looks like this: Lets start by building a card component that will display an individual token. Our goal is something that looks like this:
<figure class="wp-block-image size-full">![](https://hackanooga.com/wp-content/uploads/2024/03/ente-auth.webp)</figure>We will start by creating a Components folder with a Card component: ![](/wp-content/uploads/2024/03/ente-auth.webp)We will start by creating a Components folder with a Card component:
``` ```shell
$ mkdir src/Components $ mkdir src/Components
$ touch src/Components/Card.tsx $ touch src/Components/Card.tsx
``` ```
Lets go ahead and make a couple updates, we will create this simple card component, add some dummy tokens and some basic styling. Lets go ahead and make a couple updates, we will create this simple card component, add some dummy tokens and some basic styling.
``` ```typescript
# src/App.tsx // src/App.tsx
import './App.css' import './App.css'
import Card from './Components/Card'; import Card from './Components/Card';
@ -138,8 +138,8 @@ export default App
``` ```
``` ```typescript
# src/Components/Card.tsx // src/Components/Card.tsx
import { IToken } from "../App" import { IToken } from "../App"
function Card({ token }: { token: IToken }) { function Card({ token }: { token: IToken }) {
@ -157,8 +157,8 @@ function Card({ token }: { token: IToken }) {
export default Card export default Card
``` ```
``` ```typescript
# src/index.css // src/index.css
:root { :root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
@ -210,18 +210,18 @@ body {
Now you should have something that looks like this: Now you should have something that looks like this:
<figure class="wp-block-image size-full">![](https://hackanooga.com/wp-content/uploads/2024/03/initial-token-frontend.webp)</figure>Alright, we have some of the boring stuff out of the way, now lets start making some magic. If you arent familiar with how TOTP tokens work, basically there is an Algorithm that generates them. I would encourage you to read the [RFC](https://datatracker.ietf.org/doc/html/rfc6238) for a detailed explanation. Basically it is an algorithm that generates a one time password using the current time as a source of uniqueness along with the secret key. ![](/wp-content/uploads/2024/03/initial-token-frontend.webp)Alright, we have some of the boring stuff out of the way, now lets start making some magic. If you arent familiar with how TOTP tokens work, basically there is an Algorithm that generates them. I would encourage you to read the [RFC](https://datatracker.ietf.org/doc/html/rfc6238) for a detailed explanation. Basically it is an algorithm that generates a one time password using the current time as a source of uniqueness along with the secret key.
If we really wanted to we could implement this algorithm ourselves but thankfully there are some really simple libraries that do it for us. For our project we will be using one called `<a href="https://github.com/bellstrand/totp-generator">totp-generator</a>`. Lets go ahead and install it and check it out: If we really wanted to we could implement this algorithm ourselves but thankfully there are some really simple libraries that do it for us. For our project we will be using one called [totp-generator](https://github.com/bellstrand/totp-generator). Lets go ahead and install it and check it out:
``` ```shell
$ yarn add totp-generator $ yarn add totp-generator
``` ```
Now lets add it to our card component and see what happens. Using it is really simple. We just need to import it, instantiate a new `TokenGenerator` and pass it our Secret key: Now lets add it to our card component and see what happens. Using it is really simple. We just need to import it, instantiate a new `TokenGenerator` and pass it our Secret key:
``` ```typescript
# src/Components/card.tsx // src/Components/card.tsx
import { TOTP } from 'totp-generator'; import { TOTP } from 'totp-generator';
--- ---
function Card({ token }: { token: IToken }) { function Card({ token }: { token: IToken }) {
@ -240,4 +240,4 @@ function Card({ token }: { token: IToken }) {
Now save and go back to your browser and you should see that our secret keys are now being displayed as tokens: Now save and go back to your browser and you should see that our secret keys are now being displayed as tokens:
<figure class="wp-block-image size-full">![](https://hackanooga.com/wp-content/uploads/2024/03/token-display-1.png)</figure>That is pretty cool, the only problem is you need to refresh the page to refresh the token. We will take care of that in part 3 of this series as well as handling fetching tokens from our backend. ![](/wp-content/uploads/2024/03/token-display-1.png)That is pretty cool, the only problem is you need to refresh the page to refresh the token. We will take care of that in part 3 of this series as well as handling fetching tokens from our backend.

View File

@ -5,7 +5,7 @@ categories:
date: "2024-01-17T12:11:00Z" date: "2024-01-17T12:11:00Z"
enclosure: enclosure:
- | - |
https://hackanooga.com/wp-content/uploads/2024/03/otp-countdown.mp4 https:///wp-content/uploads/2024/03/otp-countdown.mp4
29730 29730
video/mp4 video/mp4
tags: tags:
@ -27,7 +27,7 @@ So if we know that the time is based on epoch time, we also need to know that mo
The first one is easy: The first one is easy:
``` ```javascript
let secondsSinceEpoch; let secondsSinceEpoch;
secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1; secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1;
@ -36,7 +36,7 @@ secondsSinceEpoch = Math.ceil(Date.now() / 1000) - 1;
For the second one we will need to do a little math but its pretty straightforward. We need to divide `secondsSinceEpoch` by 30 seconds and then subtract this number from 30. Here is what that looks like: For the second one we will need to do a little math but its pretty straightforward. We need to divide `secondsSinceEpoch` by 30 seconds and then subtract this number from 30. Here is what that looks like:
``` ```javascript
let secondsSinceEpoch; let secondsSinceEpoch;
let secondsRemaining; let secondsRemaining;
const period = 30; const period = 30;
@ -47,7 +47,7 @@ secondsRemaining = period - (secondsSinceEpoch % period);
Now lets put all of that together into a function that we can test out to make sure we are getting the results we expect. Now lets put all of that together into a function that we can test out to make sure we are getting the results we expect.
``` ```javascript
const timer = setInterval(() => { const timer = setInterval(() => {
countdown() countdown()
}, 1000) }, 1000)
@ -71,7 +71,7 @@ function countdown() {
Running this function should give you output similar to the following. In this example we are stopping the timer once it hits 1 second just to show that everything is working as we expect. In our application we will want this time to keep going forever: Running this function should give you output similar to the following. In this example we are stopping the timer once it hits 1 second just to show that everything is working as we expect. In our application we will want this time to keep going forever:
``` ```javascript
1710339348, 12 1710339348, 12
1710339349, 11 1710339349, 11
1710339350, 10 1710339350, 10
@ -93,7 +93,7 @@ We can go ahead and add this function to our Card component and get it wired up.
Here is what my updated `src/Components/Card.tsx` looks like: Here is what my updated `src/Components/Card.tsx` looks like:
``` ```typescript
import { useState } from "react"; import { useState } from "react";
import { IToken } from "../App" import { IToken } from "../App"
import { TOTP } from 'totp-generator'; import { TOTP } from 'totp-generator';
@ -134,7 +134,7 @@ export default Card
Pretty straightforward. I also updated my `src/index.css` and added a style for our progress bar: Pretty straightforward. I also updated my `src/index.css` and added a style for our progress bar:
``` ```css
.progressBar { .progressBar {
height: 10px; height: 10px;
position: absolute; position: absolute;
@ -147,7 +147,7 @@ Pretty straightforward. I also updated my `src/index.css` and added a style for
Here is what it all looks like in action: Here is what it all looks like in action:
<figure class="wp-block-video"><video controls="" src="https://hackanooga.com/wp-content/uploads/2024/03/otp-countdown.mp4"></video></figure>If you look closely you will notice a few interesting things. First is that the color of the progress bar changes from green to red. This is handled by our `timerStyle` variable. That part is pretty simple, if the timer is less than 10 seconds we set the background color as salmon otherwise we use light green. The width of the progress bar is controlled by `${100 (100 / 30 \* (30 secondsRemaining))}%` If you look closely you will notice a few interesting things. First is that the color of the progress bar changes from green to red. This is handled by our `timerStyle` variable. That part is pretty simple, if the timer is less than 10 seconds we set the background color as salmon otherwise we use light green. The width of the progress bar is controlled by `${100 (100 / 30 \* (30 secondsRemaining))}%`
The other interesting thing to note is that when the timer runs out it automatically restarts at 30 seconds with a new OTP. This is due to the fact that this component is re-rendering every 1/4 second and every time it re-renders it is running the entire function body including: `const { otp } = TOTP.generate(token.token);`. The other interesting thing to note is that when the timer runs out it automatically restarts at 30 seconds with a new OTP. This is due to the fact that this component is re-rendering every 1/4 second and every time it re-renders it is running the entire function body including: `const { otp } = TOTP.generate(token.token);`.

View File

@ -17,7 +17,7 @@ title: Traefik with Let's Encrypt and Cloudflare (pt 1)
Recently I decided to rebuild one of my homelab servers. Previously I was using Nginx as my reverse proxy but I decided to switch to Traefik since I have been using it professionally for some time now. One of the reasons I like Traefik is that it is stupid simple to set up certificates and when I am using it with Docker I dont have to worry about a bunch of configuration files. If you arent familiar with how Traefik works with Docker, here is a brief example of a `docker-compose.yaml` Recently I decided to rebuild one of my homelab servers. Previously I was using Nginx as my reverse proxy but I decided to switch to Traefik since I have been using it professionally for some time now. One of the reasons I like Traefik is that it is stupid simple to set up certificates and when I am using it with Docker I dont have to worry about a bunch of configuration files. If you arent familiar with how Traefik works with Docker, here is a brief example of a `docker-compose.yaml`
``` ```yaml
version: '3' version: '3'
services: services:
@ -87,23 +87,20 @@ services:
networks: networks:
traefik: traefik:
external: true external: true
``` ```
In part one of this series I will be going over some of the basics of Traefik and how dynamic routing works. If you want to skip to the good stuff and get everything configured with Cloudflare, you can skip to [part 2](https://wordpress.hackanooga.com/traefik-with-lets-encrypt-and-cloudflare-pt-2/). In part one of this series I will be going over some of the basics of Traefik and how dynamic routing works. If you want to skip to the good stuff and get everything configured with Cloudflare, you can skip to [part 2](/post/2024-02-15-traefik-with-lets-encrypt-and-cloudflare-pt-2.md).
This example sets up the primary Traefik container which acts as the ingress controller as well as a handy One Time Secret sharing [service](https://github.com/Luzifer/ots) I use. Traefik handles routing in Docker via labels. For this to work properly the services that Traefik is trying to route to all need to be on the same Docker network. For this example we created a network called traefik by running the following: This example sets up the primary Traefik container which acts as the ingress controller as well as a handy One Time Secret sharing [service](https://github.com/Luzifer/ots) I use. Traefik handles routing in Docker via labels. For this to work properly the services that Traefik is trying to route to all need to be on the same Docker network. For this example we created a network called traefik by running the following:
``` ```shell
docker network create traefik docker network create traefik
``` ```
Lets take a look at the labels we applied to the `ots` container a little closer: Lets take a look at the labels we applied to the `ots` container a little closer:
``` ```yaml
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.ots.rule=Host(`ots.example.com`) - traefik.http.routers.ots.rule=Host(`ots.example.com`)
@ -123,9 +120,8 @@ Lets take a look at the labels we applied to the `ots` container a little clo
We are using these three labels to tell our router that we want it to use the websecure entrypoint, and that it should use the letsencrypt certresolver to grab its certificates. websecure is an arbitrary name that we assigned to our :443 interface. There are multiple ways to configure this, I choose to use the cli format in my traefik config: We are using these three labels to tell our router that we want it to use the websecure entrypoint, and that it should use the letsencrypt certresolver to grab its certificates. websecure is an arbitrary name that we assigned to our :443 interface. There are multiple ways to configure this, I choose to use the cli format in my traefik config:
`
``` ```yaml
command: command:
- --api.insecure=true - --api.insecure=true
- --providers.docker=true - --providers.docker=true
@ -145,6 +141,6 @@ Heres how it works. Suppose you have a container that exposes multiple ports.
The service name is derived automatically from the definition in the docker compose file: The service name is derived automatically from the definition in the docker compose file:
``` ```shell
<br></br> ots: # This will become the service name<br></br> image: luzifer/ots<br></br> container_name: ots<br></br> ots: # This will become the service name image: luzifer/ots container_name: ots
``` ```

View File

@ -21,7 +21,7 @@ Sometime in the past I had set up some special rules on my router to force all c
Lets revisit our current configuration: Lets revisit our current configuration:
``` ```yaml
version: '3' version: '3'
services: services:
@ -115,8 +115,8 @@ Zone.Zone.Read
Zone.DNS.Edit Zone.DNS.Edit
``` ```
<figure class="wp-block-image size-full">![](https://wordpress.hackanooga.com/wp-content/uploads/2024/03/image-9.png)</figure>Also be sure to give it permission to access all zones in your account. Now simply provide that token when starting up the stack and you should be good to go: ![](/wp-content/uploads/2024/03/image-9.png)Also be sure to give it permission to access all zones in your account. Now simply provide that token when starting up the stack and you should be good to go:
``` ```shell
CF_DNS_API_TOKEN=[redacted] docker compose up -d CF_DNS_API_TOKEN=[redacted] docker compose up -d
``` ```

View File

@ -28,7 +28,7 @@ There are a number of reasons why you would want to self host a registry. For ex
Since the package registry is baked in and enabled by default, I will demonstrate how easy it is to push a docker image. We will pull the default `alpine` image, re-tag it and push it to our internal registry: Since the package registry is baked in and enabled by default, I will demonstrate how easy it is to push a docker image. We will pull the default `alpine` image, re-tag it and push it to our internal registry:
``` ```shell
# Pull the official Alpine image # Pull the official Alpine image
docker pull alpine:latest docker pull alpine:latest
@ -40,12 +40,10 @@ docker login git.hackanooga.com
# Push the image to our registry # Push the image to our registry
docker push git.hackanooga.com/mikeconrad/alpine:latest docker push git.hackanooga.com/mikeconrad/alpine:latest
``` ```
Now log into your Gitea instance, navigate to your user account and look for `packages`. You should see the newly uploaded alpine image. Now log into your Gitea instance, navigate to your user account and look for `packages`. You should see the newly uploaded alpine image.
<figure class="wp-block-image size-large">![](https://wordpress.hackanooga.com/wp-content/uploads/2024/03/image-6-1024x313.png)</figure>You can see that the package type is container. Clicking on it will give you more information: ![](/wp-content/uploads/2024/03/image-6-1024x313.png)You can see that the package type is container. Clicking on it will give you more information:
<figure class="wp-block-image size-large">![](https://wordpress.hackanooga.com/wp-content/uploads/2024/03/image-7-1024x437.png)</figure> ![](/wp-content/uploads/2024/03/image-7-1024x437.png)

View File

@ -18,13 +18,13 @@ title: Automating CI/CD with TeamCity and Ansible
In part one of this series we are going to explore a CI/CD option you may not be familiar with but should definitely be on your radar. I used Jetbrains TeamCity for several months at my last company and really enjoyed my time with it. A couple of the things I like most about it are: In part one of this series we are going to explore a CI/CD option you may not be familiar with but should definitely be on your radar. I used Jetbrains TeamCity for several months at my last company and really enjoyed my time with it. A couple of the things I like most about it are:
- Ability to declare global variables and have them be passed down to all projects - Ability to declare global variables and have them be passed down to all projects
- Ability to declare variables that are made up of other variables - Ability to declare variables that are made up of other variables
I like to use private or self hosted Docker registries for a lot of my projects and one of the pain points I have had with some other solutions (well mostly Bitbucket) is that they dont integrate well with these private registries and when I run into a situation where I am pushing an image to or pulling an image from a private registry it gets a little messy. TeamCity is nice in that I can add a connection to my private registry in my root project and them simply add that as a build feature to any projects that may need it. Essentially, now I only have one place where I have to keep those credentials and manage that connection. I like to use private or self hosted Docker registries for a lot of my projects and one of the pain points I have had with some other solutions (well mostly Bitbucket) is that they dont integrate well with these private registries and when I run into a situation where I am pushing an image to or pulling an image from a private registry it gets a little messy. TeamCity is nice in that I can add a connection to my private registry in my root project and them simply add that as a build feature to any projects that may need it. Essentially, now I only have one place where I have to keep those credentials and manage that connection.
Another reason I love it is the fact that you can create really powerful build templates that you can reuse. This became very powerful when we were trying to standardize our build processes. For example, most of the apps we build are `.NET` backends and `React` frontends. We built docker images for every project and pushed them to our private registry. TeamCity gave us the ability to standardize the naming convention and really streamline the build process. Enough about that though, the rest of this series will assume that you are using TeamCity. This post will focus on getting up and running using Ansible. Another reason I love it is the fact that you can create really powerful build templates that you can reuse. This became very powerful when we were trying to standardize our build processes. For example, most of the apps we build are `.NET` backends and `React` frontends. We built docker images for every project and pushed them to our private registry. TeamCity gave us the ability to standardize the naming convention and really streamline the build process. Enough about that though, the rest of this series will assume that you are using TeamCity. This post will focus on getting up and running using Ansible.
---
## Installation and Setup ## Installation and Setup
@ -34,14 +34,14 @@ For this I will assume that you already have Ansible on your machine and that yo
To get started go ahead and create a new directory to hold our configuration: To get started go ahead and create a new directory to hold our configuration:
``` ```shell
mkdir ~/projects/teamcity-configuration-ansible mkdir ~/projects/teamcity-configuration-ansible
touch install-teamcity-server.yml touch install-teamcity-server.yml
``` ```
Now open up `install-teamcity-server.yml` and add a task to install Java 17 as it is a prerequisite. You will need sudo for this task. \*\*\*As of this writing TeamCity does not support Java 18 or 19. If you try to install one of these you will get an error when trying to start TeamCity. Now open up `install-teamcity-server.yml` and add a task to install Java 17 as it is a prerequisite. You will need sudo for this task. \*\*\*As of this writing TeamCity does not support Java 18 or 19. If you try to install one of these you will get an error when trying to start TeamCity.
``` ```yaml
--- ---
- name: Install Teamcity - name: Install Teamcity
hosts: localhost hosts: localhost
@ -66,7 +66,7 @@ Now open up `install-teamcity-server.yml` and add a task to install Java 17 as i
The next step is to create a dedicated user account. Add the following task to `install-teamcity-server.yml` The next step is to create a dedicated user account. Add the following task to `install-teamcity-server.yml`
``` ```yaml
- name: Add Teamcity User - name: Add Teamcity User
ansible.builtin.user: ansible.builtin.user:
name: teamcity name: teamcity
@ -74,7 +74,7 @@ The next step is to create a dedicated user account. Add the following task to `
Next we will need to download the latest version of TeamCity. 2023.11.4 is the latest as of this writing. Add the following task to your `install-teamcity-server.yml` Next we will need to download the latest version of TeamCity. 2023.11.4 is the latest as of this writing. Add the following task to your `install-teamcity-server.yml`
``` ```yaml
- name: Download TeamCity Server - name: Download TeamCity Server
ansible.builtin.get_url: ansible.builtin.get_url:
url: https://download.jetbrains.com/teamcity/TeamCity-{{teamcity.version}}.tar.gz url: https://download.jetbrains.com/teamcity/TeamCity-{{teamcity.version}}.tar.gz
@ -85,7 +85,7 @@ Next we will need to download the latest version of TeamCity. 2023.11.4 is the l
Now to install TeamCity Server add the following: Now to install TeamCity Server add the following:
``` ```yaml
- name: Install TeamCity Server - name: Install TeamCity Server
ansible.builtin.shell: | ansible.builtin.shell: |
tar xfz /opt/TeamCity-{{teamcity.version}}.tar.gz tar xfz /opt/TeamCity-{{teamcity.version}}.tar.gz
@ -96,7 +96,7 @@ Now to install TeamCity Server add the following:
Now that we have everything set up and installed we want to make sure that our new `teamcity` user has access to everything they need to get up and running. We will add the following lines: Now that we have everything set up and installed we want to make sure that our new `teamcity` user has access to everything they need to get up and running. We will add the following lines:
``` ```yaml
- name: Update permissions - name: Update permissions
ansible.builtin.shell: chown -R teamcity:teamcity /opt/TeamCity ansible.builtin.shell: chown -R teamcity:teamcity /opt/TeamCity
``` ```
@ -109,7 +109,7 @@ This gives us a pretty nice setup. We have TeamCity server installed with a dedi
Go ahead and create a new templates folder with the following `teamcity.service.j2` file Go ahead and create a new templates folder with the following `teamcity.service.j2` file
``` ```shell
[Unit] [Unit]
Description=JetBrains TeamCity Description=JetBrains TeamCity
Requires=network.target Requires=network.target
@ -127,7 +127,7 @@ WantedBy=multi-user.target
Your project should now look like the following: Your project should now look like the following:
``` ```shell
$: ~/projects/teamcity-ansible-terraform $: ~/projects/teamcity-ansible-terraform
. .
├── install-teamcity-server.yml ├── install-teamcity-server.yml
@ -139,7 +139,7 @@ $: ~/projects/teamcity-ansible-terraform
Thats it! Now you should have a fully automated installed of TeamCity Server ready to be deployed wherever you need it. Here is the final playbook file, also you can find the most up to date version in my [repo](https://git.hackanooga.com/mikeconrad/teamcity-ansible-scripts.git): Thats it! Now you should have a fully automated installed of TeamCity Server ready to be deployed wherever you need it. Here is the final playbook file, also you can find the most up to date version in my [repo](https://git.hackanooga.com/mikeconrad/teamcity-ansible-scripts.git):
``` ```yaml
--- ---
- name: Install Teamcity - name: Install Teamcity
hosts: localhost hosts: localhost

View File

@ -18,7 +18,7 @@ Anyway, I set up a VM scaling set in Azure with 10 Ubuntu 22.04 vms and wrote so
Here is what that `docker-compose.yml` looks like: Here is what that `docker-compose.yml` looks like:
``` ```yaml
--- ---
services: services:
otel-collector: otel-collector:
@ -134,20 +134,18 @@ services:
networks: networks:
traefik: traefik:
external: true external: true
``` ```
Pretty straightforward so I proceed to deploy it into the swarm Pretty straightforward so I proceed to deploy it into the swarm
``` ```shell
docker stack deploy -c docker-compose.yml observability docker stack deploy -c docker-compose.yml observability
``` ```
Everything deploys properly but when I view the Traefik logs there is an issue with all the services except for the grafana service. I get errors like this: Everything deploys properly but when I view the Traefik logs there is an issue with all the services except for the grafana service. I get errors like this:
``` ```shell
traefik_traefik.1.tm5iqb9x59on@dockerswa2V8BY4 | 2024-05-11T13:14:16Z ERR error="service \"observability-prometheus\" error: port is missing" container=observability-prometheus-37i852h4o36c23lzwuu9pvee9 providerName=swarm traefik_traefik.1.tm5iqb9x59on@dockerswa2V8BY4 | 2024-05-11T13:14:16Z ERR error="service \"observability-prometheus\" error: port is missing" container=observability-prometheus-37i852h4o36c23lzwuu9pvee9 providerName=swarm
``` ```
It drove me crazy for about half a day or so. I couldnt find any reason why the grafana service worked as expected but none of the others did. Part of my love/hate relationship with Traefik stems from the fact that configuration issues like this can be hard to track and debug. Ultimately after lots of searching and banging my head against a wall I found the answer in the Traefik docs and thought I would share here for anyone else who might run into this issue. Again, this solution is specific to Docker Swarm mode. It drove me crazy for about half a day or so. I couldnt find any reason why the grafana service worked as expected but none of the others did. Part of my love/hate relationship with Traefik stems from the fact that configuration issues like this can be hard to track and debug. Ultimately after lots of searching and banging my head against a wall I found the answer in the Traefik docs and thought I would share here for anyone else who might run into this issue. Again, this solution is specific to Docker Swarm mode.
@ -156,4 +154,4 @@ It drove me crazy for about half a day or so. I couldnt find any reason why t
Expand that first section and you will see the solution: Expand that first section and you will see the solution:
<figure class="wp-block-image size-full is-resized">![](https://hackanooga.com/wp-content/uploads/2024/05/image.png)</figure>It turns out I just needed to update my `docker-compose.yml` and nest the labels under a deploy section, redeploy and everything was working as expected. ![](https:///wp-content/uploads/2024/05/image.png)It turns out I just needed to update my `docker-compose.yml` and nest the labels under a deploy section, redeploy and everything was working as expected.

View File

@ -16,7 +16,7 @@ For those of you who arent familiar with the concept of a network tarpit it i
> A **tarpit** is a service on a [computer system](https://en.wikipedia.org/wiki/Computer_system) (usually a [server](https://en.wikipedia.org/wiki/Server_(computing))) that purposely delays incoming connections. The technique was developed as a defense against a [computer worm](https://en.wikipedia.org/wiki/Computer_worm), and the idea is that [network](https://en.wikipedia.org/wiki/Computer_network) abuses such as [spamming](https://en.wikipedia.org/wiki/Spamming) or broad scanning are less effective, and therefore less attractive, if they take too long. The concept is analogous with a [tar pit](https://en.wikipedia.org/wiki/Tar_pit), in which animals can get bogged down and slowly sink under the surface, like in a [swamp](https://en.wikipedia.org/wiki/Swamp). > A **tarpit** is a service on a [computer system](https://en.wikipedia.org/wiki/Computer_system) (usually a [server](https://en.wikipedia.org/wiki/Server_(computing))) that purposely delays incoming connections. The technique was developed as a defense against a [computer worm](https://en.wikipedia.org/wiki/Computer_worm), and the idea is that [network](https://en.wikipedia.org/wiki/Computer_network) abuses such as [spamming](https://en.wikipedia.org/wiki/Spamming) or broad scanning are less effective, and therefore less attractive, if they take too long. The concept is analogous with a [tar pit](https://en.wikipedia.org/wiki/Tar_pit), in which animals can get bogged down and slowly sink under the surface, like in a [swamp](https://en.wikipedia.org/wiki/Swamp).
> >
> <cite>[https://en.wikipedia.org/wiki/Tarpit\_(networking)](https://en.wikipedia.org/wiki/Tarpit_(networking))</cite> > [https://en.wikipedia.org/wiki/Tarpit\_(networking)](https://en.wikipedia.org/wiki/Tarpit_(networking))
If you run any sort of service on the internet then you know as soon as your server has a public IP address and open ports, there are scanners and bots trying to get in constantly. If you take decent steps towards security then it is little more than an annoyance, but annoying all the less. One day when I had some extra time on my hands I started researching ways to mess with the bots trying to scan/attack my site. If you run any sort of service on the internet then you know as soon as your server has a public IP address and open ports, there are scanners and bots trying to get in constantly. If you take decent steps towards security then it is little more than an annoyance, but annoying all the less. One day when I had some extra time on my hands I started researching ways to mess with the bots trying to scan/attack my site.
@ -34,11 +34,11 @@ It turns out that this problem has been solved multiple times in multiple ways.
> wrappers to display an error message before disconnecting. > wrappers to display an error message before disconnecting.
> ``` > ```
> >
> <cite>SSH RFC</cite> > SSH RFC
Essentially this means that their is no limit to the amount of data that a server can send back to the client and the client must be able to wait and process all of this data. Now lets see it in action. Essentially this means that their is no limit to the amount of data that a server can send back to the client and the client must be able to wait and process all of this data. Now lets see it in action.
``` ```shell
git clone https://github.com/skeeto/endlessh.git git clone https://github.com/skeeto/endlessh.git
cd endlessh cd endlessh
make make
@ -47,13 +47,13 @@ make
By default this fake server listens on port 2222. I have a port forward set up that forwards all ssh traffic from port 22 to 2222. Now try to connect via ssh: By default this fake server listens on port 2222. I have a port forward set up that forwards all ssh traffic from port 22 to 2222. Now try to connect via ssh:
``` ```shell
ssh -vvv localhost -p 2222 ssh -vvv localhost -p 2222
``` ```
If you wait a few seconds you will see the server send back the version string and then start sending a random banner: If you wait a few seconds you will see the server send back the version string and then start sending a random banner:
``` ```shell
$:/tmp/endlessh$ 2024-06-24T13:05:59.488Z Port 2222 $:/tmp/endlessh$ 2024-06-24T13:05:59.488Z Port 2222
2024-06-24T13:05:59.488Z Delay 10000 2024-06-24T13:05:59.488Z Delay 10000
2024-06-24T13:05:59.488Z MaxLineLength 32 2024-06-24T13:05:59.488Z MaxLineLength 32
@ -107,15 +107,13 @@ debug1: kex_exchange_identification: banner line 0: V
2024-06-24T13:06:28.734Z write(4) = 25 2024-06-24T13:06:28.734Z write(4) = 25
2024-06-24T13:06:28.734Z poll(1, 10000) 2024-06-24T13:06:28.734Z poll(1, 10000)
debug1: kex_exchange_identification: banner line 1: 2I=ED}PZ,z T_Y|Yc]$b{R] debug1: kex_exchange_identification: banner line 1: 2I=ED}PZ,z T_Y|Yc]$b{R]
``` ```
This is a great way to give back to those bots and script kiddies. In my research into other methods I also stumbled across this brilliant program [fakessh](https://github.com/iBug/fakessh). While fakessh isnt technically a tarpit, its more of a honeypot but very interesting nonetheless. It creates a fake SSH server and logs the ip address, connection string and any commands executed by the attacker. Essentially it allows any username/password combination to connect and gives them a fake shell prompt. There is no actual access to any file system and all of their commands basically return gibberish. This is a great way to give back to those bots and script kiddies. In my research into other methods I also stumbled across this brilliant program [fakessh](https://github.com/iBug/fakessh). While fakessh isnt technically a tarpit, its more of a honeypot but very interesting nonetheless. It creates a fake SSH server and logs the ip address, connection string and any commands executed by the attacker. Essentially it allows any username/password combination to connect and gives them a fake shell prompt. There is no actual access to any file system and all of their commands basically return gibberish.
Here are some logs from an actual server of mine running fakessh Here are some logs from an actual server of mine running fakessh
``` ```shell
2024/06/24 06:51:20 [conn] ip=183.81.169.238:40430 2024/06/24 06:51:20 [conn] ip=183.81.169.238:40430
2024/06/24 06:51:22 [auth] ip=183.81.169.238:40430 version="SSH-2.0-Go" user="root" password="0" 2024/06/24 06:51:22 [auth] ip=183.81.169.238:40430 version="SSH-2.0-Go" user="root" password="0"
2024/06/24 06:51:23 [conn] ip=183.81.169.238:40444 2024/06/24 06:51:23 [conn] ip=183.81.169.238:40444
@ -228,20 +226,18 @@ Here are some logs from an actual server of mine running fakessh
2024/06/24 09:03:40 [conn] ip=218.92.0.107:57758 err="ssh: disconnect, reason 11: " 2024/06/24 09:03:40 [conn] ip=218.92.0.107:57758 err="ssh: disconnect, reason 11: "
2024/06/24 09:07:36 [conn] ip=218.92.0.56:21354 2024/06/24 09:07:36 [conn] ip=218.92.0.56:21354
2024/06/24 09:07:39 [conn] ip=218.92.0.56:21354 err="ssh: disconnect, reason 11: " 2024/06/24 09:07:39 [conn] ip=218.92.0.56:21354 err="ssh: disconnect, reason 11: "
``` ```
Those are mostly connections and disconnections. They probably connected, realized it was fake and disconnected. There are a couple that tried to execute some commands though: Those are mostly connections and disconnections. They probably connected, realized it was fake and disconnected. There are a couple that tried to execute some commands though:
``` ```shell
:~$ sudo grep head /var/log/fakessh/fakessh.log :~$ sudo grep head /var/log/fakessh/fakessh.log
2024/06/23 15:48:02 [shell] ip=184.160.233.163:45735 duration=0s bytes=15 head="ls 2>/dev/null\n" 2024/06/23 15:48:02 [shell] ip=184.160.233.163:45735 duration=0s bytes=15 head="ls 2>/dev/null\n"
2024/06/24 03:55:11 [shell] ip=14.46.116.243:43656 duration=20s bytes=0 head="" 2024/06/24 03:55:11 [shell] ip=14.46.116.243:43656 duration=20s bytes=0 head=""
``` ```
Fun fact: Cloudflares Bot Fight Mode uses a form of tarpitting: Fun fact: Cloudflares Bot Fight Mode uses a form of tarpitting:
> Once enabled, when we detect a bad bot, we will do three things: (1) were going to disincentivize the bot maker economically by tarpitting them, including requiring them to solve a computationally intensive challenge that will require more of their bots CPU; (2) for [Bandwidth Alliance partners](https://blog.cloudflare.com/bandwidth-alliance/), were going to hand the IP of the bot to the partner and get the bot kicked offline; and (3) were going to plant trees to make up for the bots carbon cost. > Once enabled, when we detect a bad bot, we will do three things: (1) were going to disincentivize the bot maker economically by tarpitting them, including requiring them to solve a computationally intensive challenge that will require more of their bots CPU; (2) for [Bandwidth Alliance partners](https://blog.cloudflare.com/bandwidth-alliance/), were going to hand the IP of the bot to the partner and get the bot kicked offline; and (3) were going to plant trees to make up for the bots carbon cost.
> >
> <cite><https://blog.cloudflare.com/cleaning-up-bad-bots></cite> > <https://blog.cloudflare.com/cleaning-up-bad-bots>

View File

@ -13,13 +13,13 @@ title: Debugging running Nginx config
I was recently working on project where a client had cPanel/WHM with Nginx and Apache. They had a large number of sites managed by Nginx with a large number of includes. I created a custom config to override a location block and needed to be certain that my changes where actually being picked up. Anytime I make changes to an Nginx config, I try to be vigilant about running: I was recently working on project where a client had cPanel/WHM with Nginx and Apache. They had a large number of sites managed by Nginx with a large number of includes. I created a custom config to override a location block and needed to be certain that my changes where actually being picked up. Anytime I make changes to an Nginx config, I try to be vigilant about running:
``` ```shell
nginx -t nginx -t
``` ```
to test my configuration and ensure I dont have any syntax errors. I was looking for an easy way to view the actual compiled config and found the `-T` flag which will test the configuration and dump it to standard out. This is pretty handy if you have a large number of includes in various locations. Here is an example from a fresh Nginx Docker container: to test my configuration and ensure I dont have any syntax errors. I was looking for an easy way to view the actual compiled config and found the `-T` flag which will test the configuration and dump it to standard out. This is pretty handy if you have a large number of includes in various locations. Here is an example from a fresh Nginx Docker container:
``` ```conf
root@2771f302dc98:/# nginx -T root@2771f302dc98:/# nginx -T
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful nginx: configuration file /etc/nginx/nginx.conf test is successful
@ -202,7 +202,6 @@ server {
# deny all; # deny all;
#} #}
} }
``` ```
As you can see from the output above, we get all of the various Nginx config files in use printed to the console, perfect for `grepping` or searching/filtering with other tools. As you can see from the output above, we get all of the various Nginx config files in use printed to the console, perfect for `grepping` or searching/filtering with other tools.

View File

@ -24,7 +24,7 @@ This is a very easy step to start securing your origin server but it still allow
We can achieve this pretty easily because Cloudflare provides a sort of API where they regular publish their network blocks. Here is the basic script we will use: We can achieve this pretty easily because Cloudflare provides a sort of API where they regular publish their network blocks. Here is the basic script we will use:
``` ```shell
for ip in $(curl https://www.cloudflare.com/ips-v4/); do iptables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT; done for ip in $(curl https://www.cloudflare.com/ips-v4/); do iptables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT; done
for ip in $(curl https://www.cloudflare.com/ips-v6/); do ip6tables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT; done for ip in $(curl https://www.cloudflare.com/ips-v6/); do ip6tables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT; done
@ -38,4 +38,4 @@ This will pull down the latest network addresses from Cloudflare and create `ipt
Now with this in place, here is the results: Now with this in place, here is the results:
<figure class="wp-block-image size-large">![](https://hackanooga.com/wp-content/uploads/2024/09/image-1024x320.png)</figure>This should cut down on some of the noise from attackers and script kiddies trying to find holes in your security. ![](/wp-content/uploads/2024/09/image-1024x320.png)This should cut down on some of the noise from attackers and script kiddies trying to find holes in your security.

View File

@ -24,7 +24,7 @@ I have written some very basic Terraform to get us started. The Terraform is ver
First create our main.tf with the following contents: First create our main.tf with the following contents:
``` ```t
# main.tf # main.tf
# Attach an SSH key to our droplet # Attach an SSH key to our droplet
resource "digitalocean_ssh_key" "default" { resource "digitalocean_ssh_key" "default" {
@ -49,7 +49,7 @@ output "droplet_output" {
Next create a terraform.tf file in the same directory with the following contents: Next create a terraform.tf file in the same directory with the following contents:
``` ```t
terraform { terraform {
required_providers { required_providers {
digitalocean = { digitalocean = {
@ -61,24 +61,23 @@ terraform {
provider "digitalocean" { provider "digitalocean" {
} }
``` ```
Now we will need to create the ssh key that we defined in our Terraform code. Now we will need to create the ssh key that we defined in our Terraform code.
``` ```shell
$ ssh-keygen -t rsa -C "WireguardVPN" -f ./tf-digitalocean -q -N "" $ ssh-keygen -t rsa -C "WireguardVPN" -f ./tf-digitalocean -q -N ""
``` ```
Next we need to set an environment variable for our DigitalOcean access token. Next we need to set an environment variable for our DigitalOcean access token.
``` ```shell
$ export DIGITALOCEAN_ACCESS_TOKEN=dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx $ export DIGITALOCEAN_ACCESS_TOKEN=dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
``` ```
Now we are ready to initialize our Terraform and apply it: Now we are ready to initialize our Terraform and apply it:
``` ```shell
$ terraform init $ terraform init
$ terraform apply $ terraform apply
@ -157,7 +156,7 @@ droplet_output = "159.223.113.207"
All pretty standard stuff. Nice! It only took about 30 seconds or so on my machine to spin up a droplet and start provisioning it. It is worth noting that the setup script will take a few minutes to run. Before we log into our new droplet, lets take a quick look at the setup script that we are running. All pretty standard stuff. Nice! It only took about 30 seconds or so on my machine to spin up a droplet and start provisioning it. It is worth noting that the setup script will take a few minutes to run. Before we log into our new droplet, lets take a quick look at the setup script that we are running.
``` ```shell
#!/usr/bin/env sh #!/usr/bin/env sh
set -e set -e
set -u set -u
@ -240,7 +239,7 @@ On the “client” side:
Now that we have our configs in place, we need to copy the client config to our local machine. The following command should work as long as you make sure to replace the IP address with the IP address of your newly created droplet: Now that we have our configs in place, we need to copy the client config to our local machine. The following command should work as long as you make sure to replace the IP address with the IP address of your newly created droplet:
``` ```shell
## Make sure you have Wireguard installed on your local machine as well. ## Make sure you have Wireguard installed on your local machine as well.
## https://wireguard.com/install ## https://wireguard.com/install
@ -250,7 +249,7 @@ $ ssh -i tf-digitalocean root@157.230.177.54 -- cat /root/wireguard-conf/client-
Before we try to connect, lets log into the server and make sure everything is set up correctly: Before we try to connect, lets log into the server and make sure everything is set up correctly:
``` ```shell
$ ssh -i tf-digitalocean root@159.223.113.207 $ ssh -i tf-digitalocean root@159.223.113.207
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-113-generic x86_64) Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-113-generic x86_64)
@ -280,13 +279,11 @@ Run 'do-release-upgrade' to upgrade to it.
Last login: Wed Sep 25 13:16:25 2024 from 74.221.191.214 Last login: Wed Sep 25 13:16:25 2024 from 74.221.191.214
root@wireguard:~# root@wireguard:~#
``` ```
Awesome! We are connected. Now lets check the wireguard interface using the `wg` command. If our config was correct, we should see an interface line and 1 peer line like so. If the peer line is missing then something is wrong with the configuration. Most likely a mismatch between public/private key.: Awesome! We are connected. Now lets check the wireguard interface using the `wg` command. If our config was correct, we should see an interface line and 1 peer line like so. If the peer line is missing then something is wrong with the configuration. Most likely a mismatch between public/private key.:
``` ```shell
root@wireguard:~# wg root@wireguard:~# wg
interface: do interface: do
public key: fTvqo/cZVofJ9IZgWHwU6XKcIwM/EcxUsMw4voeS/Hg= public key: fTvqo/cZVofJ9IZgWHwU6XKcIwM/EcxUsMw4voeS/Hg=
@ -300,7 +297,7 @@ root@wireguard:~#
So now we should be ready to go! On your local machine go ahead and try it out: So now we should be ready to go! On your local machine go ahead and try it out:
``` ```shell
## Start the interface with wg-quick up [interface_name] ## Start the interface with wg-quick up [interface_name]
$ sudo wg-quick up do $ sudo wg-quick up do
[sudo] password for mikeconrad: [sudo] password for mikeconrad:
@ -347,14 +344,11 @@ rtt min/avg/max/mdev = 27.991/27.991/27.991/0.000 ms
## Verify our traffic is actually going over the tunnel. ## Verify our traffic is actually going over the tunnel.
$ curl icanhazip.com $ curl icanhazip.com
157.230.177.54 157.230.177.54
``` ```
We should also be able to ssh into our instance over the VPN using the `10.66.66.1` address: We should also be able to ssh into our instance over the VPN using the `10.66.66.1` address:
``` ```shell
$ ssh -i tf-digitalocean root@10.66.66.1 $ ssh -i tf-digitalocean root@10.66.66.1
The authenticity of host '10.66.66.1 (10.66.66.1)' can't be established. The authenticity of host '10.66.66.1 (10.66.66.1)' can't be established.
ED25519 key fingerprint is SHA256:E7BKSO3qP+iVVXfb/tLaUfKIc4RvtZ0k248epdE04m8. ED25519 key fingerprint is SHA256:E7BKSO3qP+iVVXfb/tLaUfKIc4RvtZ0k248epdE04m8.
@ -389,7 +383,6 @@ Run 'do-release-upgrade' to upgrade to it.
root@wireguard:~# root@wireguard:~#
``` ```
Looks like everything is working! If you run the script from the repo you will have a fully functioning Wireguard VPN in less than 5 minutes! Pretty cool stuff! This article was not meant to be exhaustive but instead a simple primer to get your feet wet. The setup script I used is heavily inspired by [angristan/wireguard-install](https://github.com/angristan/wireguard-install). Another great resource is the [Unofficial docs repo](https://github.com/pirate/wireguard-docs). Looks like everything is working! If you run the script from the repo you will have a fully functioning Wireguard VPN in less than 5 minutes! Pretty cool stuff! This article was not meant to be exhaustive but instead a simple primer to get your feet wet. The setup script I used is heavily inspired by [angristan/wireguard-install](https://github.com/angristan/wireguard-install). Another great resource is the [Unofficial docs repo](https://github.com/pirate/wireguard-docs).