<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Tse</title>
    <description>The latest articles on DEV Community by Chris Tse (@christse).</description>
    <link>https://dev.to/christse</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F90211%2Ff6addb21-7e27-4b86-adc3-b115a959ba45.jpeg</url>
      <title>DEV Community: Chris Tse</title>
      <link>https://dev.to/christse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christse"/>
    <language>en</language>
    <item>
      <title>Oops, My Database Got Hacked</title>
      <dc:creator>Chris Tse</dc:creator>
      <pubDate>Mon, 24 Feb 2025 07:19:13 +0000</pubDate>
      <link>https://dev.to/christse/oops-my-database-got-hacked-3oo</link>
      <guid>https://dev.to/christse/oops-my-database-got-hacked-3oo</guid>
      <description>&lt;p&gt;I recently started self-hosting personal apps on my Unraid server, with a configured Postgres database for storage. I woke up one morning discovering that I could no longer connect to the subdomains I had reverse proxied to one of my self-hosted apps, so I log in to the VPN to connect to my server and check the logs. Immediately, I noticed that it was throwing some errors about not being able to find the specific database I had created for it.&lt;/p&gt;

&lt;p&gt;That's weird, I literally just created it last night and had it up and running. What could have happened?&lt;/p&gt;

&lt;p&gt;I fire up pgAdmin to take a look at my databases, of which I should have several... wait, what? Where did they all go?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghost.ctse.dev%2Fcontent%2Fimages%2F2025%2F02%2Fimage-1.png" class="article-body-image-wrapper"&gt;&lt;img alt="Screenshot of pgAdmin with expected databases missing" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghost.ctse.dev%2Fcontent%2Fimages%2F2025%2F02%2Fimage-1.png" width="800" height="287"&gt;&lt;/a&gt;&lt;br&gt;What is this database? Where did the rest go?
  &lt;/p&gt;

&lt;p&gt;I Googled the name of the database, looked through the Postgres container logs, and checked the contents of that unfamiliar database. Sure enough, I was compromised - I could see commands that dropped all my databases and added this one. Inside was a ransom note demanding cryptocurrency payment in exchange for restoring my data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did this happen?
&lt;/h2&gt;

&lt;p&gt;Needless to say, database security is important. And here, I learned a lesson that it's equally important even for a self-hosted container meant for fun. As someone still relatively green to self-hosting, having only had my Unraid server up for about a year and a half, I didn't quite have my bases covered. While I didn't lose much valuable data, it did give me a good lesson on what I did wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing the database to the public internet
&lt;/h3&gt;

&lt;p&gt;I had port forwarded the Postgres IP so that I could connect to it anywhere, even while I was out so that I could mess with it while sitting at a coffee shop without having to go through the VPN. My database now sat unprotected on the public internet, guarded only by a username and password. That was my first mistake.&lt;/p&gt;

&lt;p&gt;Now I admit, this is advice I've very commonly seen. If this were a proper production app, I of course would never have done this. I figured my small toy database wouldn't attract any attention. No one really knew my IP, nor that I would have Postgres running, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghost.ctse.dev%2Fcontent%2Fimages%2F2025%2F02%2Fimage-2.png" class="article-body-image-wrapper"&gt;&lt;img alt="Screenshot of pgAdmin with expected databases missing" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fghost.ctse.dev%2Fcontent%2Fimages%2F2025%2F02%2Fimage-2.png" width="563" height="192"&gt;&lt;/a&gt;&lt;br&gt;Search results as of February 2025
  &lt;/p&gt;

&lt;p&gt;Turns out, my experience wasn't unique. According to a &lt;a href="https://unit42.paloaltonetworks.com/pgminer-postgresql-cryptocurrency-mining-botnet/?ref=ghost.ctse.dev" rel="noopener noreferrer"&gt;threat research report&lt;/a&gt; from Palo Alto Networks, automated bots have been targeting exposed Postgres and MySQL databases since at least 2019. With over 600,000 databases exposed on Shodan alone as of writing this, attackers have plenty of targets - and they're constantly scanning for more.&lt;/p&gt;

&lt;p&gt;I've linked to other articles that talk about this issue more in-depth, such as the exact method and queries that ran on the database for the exact attack that I was a victim of at the end of this post if you are interested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not securing the root user
&lt;/h3&gt;

&lt;p&gt;Going off the same assumption as above, since no one should know or care that I have a database up, surely having the root &lt;code&gt;postgres&lt;/code&gt; user with a simple password shouldn't be that big of a deal, right?&lt;/p&gt;

&lt;p&gt;Obvious as it seems in hindsight, this was something I overlooked. I had the password set to something simple in the case that I wanted to log in and play around with it more easily. Of course, I've since recreated the container with a much more secure root user password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not having backups
&lt;/h3&gt;

&lt;p&gt;While I didn't have mission-critical data in there, the loss still hurt. I had a &lt;a href="https://linkding.link/?ref=ghost.ctse.dev" rel="noopener noreferrer"&gt;linkding&lt;/a&gt; instance, a self-hosted bookmark manager, where I had stored a couple dozen interesting articles to read later. All gone.&lt;/p&gt;

&lt;p&gt;I knew about security best practices for production databases, but I had never bothered with proper backups for my personal projects. It just hadn't seemed necessary - until now. Luckily, it's actually pretty simple to get started. Both Postgres and MySQL have dump commands that will create a big SQL file containing all the commands required to recreate the thing you just dumped.&lt;/p&gt;

&lt;p&gt;To mitigate losing any more data in the future, I whipped up a quick bash script to run an hourly &lt;a href="https://www.postgresql.org/docs/current/app-pg-dumpall.html?ref=ghost.ctse.dev" rel="noopener noreferrer"&gt;&lt;code&gt;pg_dumpall&lt;/code&gt;&lt;/a&gt; , copy the dump out of the container, gzip it, and put it in a backup folder on the user shares. I also have the script delete the oldest one each time once I have 30 days worth of hourly backups, giving me plenty of leeway for restoring data should I need it in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  A lesson on security, paid up front
&lt;/h2&gt;

&lt;p&gt;The main takeaways here were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Don't expose your database to the open internet if you can help it&lt;/li&gt;
&lt;li&gt;If you must, at least make sure your root user is secured with a strong password&lt;/li&gt;
&lt;li&gt;Make backups in the case of catastrophe&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These things likely would just be common sense for a seasoned database administrator or backend developer. But with the prevalence of the crypto ransom attacks on exposed databases, it might just not be common enough.&lt;/p&gt;

&lt;p&gt;Certainly wasn't for a hobby self-hoster like myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@border0/help-my-database-was-compromised-ec68ef15df65?ref=christse.dev" rel="noopener noreferrer"&gt;https://medium.com/@border0/help-my-database-was-compromised-ec68ef15df65?ref=christse.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hunters.security/en/blog/protecting-postgres?ref=christse.dev" rel="noopener noreferrer"&gt;https://www.hunters.security/en/blog/protecting-postgres?ref=christse.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.helpnetsecurity.com/2024/01/18/postgresql-mysql-ransomware-bot/?ref=christse.dev" rel="noopener noreferrer"&gt;https://www.helpnetsecurity.com/2024/01/18/postgresql-mysql-ransomware-bot/?ref=christse.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>selfhosting</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Use Heroku PostgreSQL for Local Development with Knex.js</title>
      <dc:creator>Chris Tse</dc:creator>
      <pubDate>Sun, 01 Dec 2019 20:45:23 +0000</pubDate>
      <link>https://dev.to/christse/how-to-use-heroku-postgresql-for-local-development-with-knex-js-54kp</link>
      <guid>https://dev.to/christse/how-to-use-heroku-postgresql-for-local-development-with-knex-js-54kp</guid>
      <description>&lt;h1&gt;
  
  
  Local vs. Hosted Databases
&lt;/h1&gt;

&lt;p&gt;Generally, folks tend to develop apps using a local database on their own machine. It can then be swapped out for a real one in production. This is usually done via the use of environment variables. But, sometimes you may want to have access to a database hosted online that you can access from many places. Or you just want a hosted database that you can use without having to worry about all the things that come with  maintaining your own database.&lt;/p&gt;

&lt;p&gt;Managed databases are great for this, but they can be costly. Most providers such as &lt;a href="https://www.digitalocean.com/pricing/#Databases" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt; and &lt;a href="https://aws.amazon.com/rds/postgresql/pricing/" rel="noopener noreferrer"&gt;Amazon RDS&lt;/a&gt; will run you about $15 (USD) per month to keep a database going. This can be fairly pricey for someone wanting to just get their feet wet with Postgres.&lt;/p&gt;

&lt;h1&gt;
  
  
  Heroku Postgres
&lt;/h1&gt;

&lt;p&gt;Heroku to the rescue! For every app hosted on Heroku, you have access to a free tier Postgres instance. You can read more about the &lt;a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#hobby-tier" rel="noopener noreferrer"&gt;limitations of the free tier&lt;/a&gt;, but it will be more than enough to get started with developing your app.&lt;/p&gt;

&lt;p&gt;When I was working on a personal project, I wanted to be have my database accessible regardless of which machine I was working on. This post aims to document the various steps I needed to get this to work, and hopefully it can be of some help to a few of you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need to have the Postgres client installed on your machine to connect to a database from your application.&lt;/p&gt;

&lt;p&gt;The installation method differs between each OS so I won't go into too much detail here, but it is well documented. Personally, I use Mac at home and Fedora at work.&lt;/p&gt;

&lt;p&gt;For Mac, I've found that &lt;a href="https://postgresapp.com/" rel="noopener noreferrer"&gt;Postgres.app&lt;/a&gt; was the least hassle to get everything running. &lt;/p&gt;

&lt;p&gt;For Fedora, you can follow the &lt;a href="https://fedoraproject.org/wiki/PostgreSQL" rel="noopener noreferrer"&gt;official docs from Fedora&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Here are the basic steps to get started with using the free Heroku Postgres for local development. In the later parts of this post, I will be using Node.js and the &lt;a href="https://knexjs.org" rel="noopener noreferrer"&gt;Knex.js&lt;/a&gt; query builder library for working with the database. The steps up to getting the connection URL should be applicable to any other kind of environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a Heroku App
&lt;/h3&gt;

&lt;p&gt;First, create a Heroku account if you don't already have one. &lt;/p&gt;

&lt;p&gt;Then, you can create a new Heroku app (or use an existing one). You can do this by logging into &lt;a href="https://dashboard.heroku.com/apps" rel="noopener noreferrer"&gt;the dashboard&lt;/a&gt; then at the top right, click &lt;code&gt;New &amp;gt; Create new app&lt;/code&gt;. Enter an app name and select the region closest to your location.&lt;/p&gt;

&lt;p&gt;Once created, click into it. Under the "Overview" tab, you should see the "Installed add-ons" section. Click &lt;code&gt;Configure add-ons&lt;/code&gt; and search for "Postgres" in the search bar, then click the "Heroku Postgres" result that comes up. A modal should come up. Make sure the "Hobby Dev - Free" option is selected and click the "Provision" button. &lt;/p&gt;

&lt;p&gt;You should now have a Heroku app created with a free Postgres instance attached to it. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Install the Heroku CLI
&lt;/h3&gt;

&lt;p&gt;For the next step, install the Heroku CLI. You can use this to get the database credentials and interact with the database via the terminal. You can find instructions on how to install it in their &lt;a href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once installed run the following to log in to your account via the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the on-screen prompts to log in with your account. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Getting the database credentials
&lt;/h3&gt;

&lt;p&gt;There are various ways to connect to a database from the app you are developing, depending on the language and library/framework. A common one is to use the connection string, which looks kind of like a URL that starts with &lt;code&gt;postgres://&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;First verify you have access to the app you created in the previous steps by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a list of all the apps on your account. Note down the name of the one you created in the previous step.&lt;/p&gt;

&lt;p&gt;You can then check to make sure Postgres is attached correctly to the app. To do this, run this command, where &lt;code&gt;&amp;lt;app-name&amp;gt;&lt;/code&gt; is the name of the app created in the first step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku pg:info -a &amp;lt;app-name&amp;gt;

=== DATABASE_URL
Plan:                  Hobby-dev
Status:                Available
Connections:           0/20
PG Version:            11.6
Created:               2019-11-20 16:58 UTC
Data Size:             8.4 MB
Tables:                2
Rows:                  1/10000 (In compliance)
Fork/Follow:           Unsupported
Rollback:              Unsupported
Continuous Protection: Off
Add-on:                postgresql-cubed-73038
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see some output like what is shown above. Now that we've verified Postgres is accessible on this app, we can now get the database connection information with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku pg:credentials:url -a &amp;lt;app-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a &lt;code&gt;Connection URL&lt;/code&gt; in the output that starts with &lt;code&gt;postgres://&lt;/code&gt;. That is what I will be using for the rest of this article. For other use cases, the connection info string should have the required info.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting via Knex.js
&lt;/h2&gt;

&lt;p&gt;I won't be going into the details on how to set up Knex.js. You can view it at their &lt;a href="http://knexjs.org/#Installation-node" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you try to use the connection URL as is via the exported &lt;code&gt;connection&lt;/code&gt; property and attempt to run a migration, you should see an error along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: no pg_hba.conf entry for host "&amp;lt;ip address&amp;gt;", user "&amp;lt;username&amp;gt;", database "&amp;lt;database name&amp;gt;", SSL off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of steps to be done:&lt;/p&gt;

&lt;p&gt;Open the pg_hba.conf file for your local Postgres installation, and add this line to it to allow postgres to make the connection this database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host    all             all             &amp;lt;ip-address&amp;gt;/32        trust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, you will need to add this line to your &lt;code&gt;knexfile.js&lt;/code&gt; where the &lt;code&gt;pg&lt;/code&gt; module is required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, set this environment variable so that Node.js will not check for a certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_TLS_REJECT_UNAUTHORIZED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be done in the code as shown above, via setting it before running your app, or using a dotenv file. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note that this is not secure for use in production, and to only use for local development.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Give your migration another try, and it should now succeed. &lt;/p&gt;




&lt;p&gt;Hopefully this post was able to help you get started with using Postgres on Heroku's free tier. Let me know in the comments if you have any questions or need clarifications.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>postgres</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Alternative terms for "Tech Talk"?</title>
      <dc:creator>Chris Tse</dc:creator>
      <pubDate>Fri, 05 Jul 2019 17:07:49 +0000</pubDate>
      <link>https://dev.to/christse/alternative-terms-for-tech-talk-44dd</link>
      <guid>https://dev.to/christse/alternative-terms-for-tech-talk-44dd</guid>
      <description>&lt;p&gt;For a community hosting events targeted towards beginners, many of whom who are looking to break into the industry and may have never even written code before, what is a good alternative to the term "tech talk"? While it's short and concise which makes it easy to put into an event title, it can seem intimidating. Any suggestions?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>beginners</category>
      <category>community</category>
      <category>meetups</category>
    </item>
  </channel>
</rss>
