<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://theadamwright.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://theadamwright.github.io/" rel="alternate" type="text/html" /><updated>2026-06-10T14:25:16+00:00</updated><id>https://theadamwright.github.io/feed.xml</id><title type="html">Adam Wright</title><subtitle>Notes on distributed databases, security, and Kubernetes from Adam Wright, Product Manager for EDB Postgres Distributed (PGD).</subtitle><author><name>Adam Wright</name></author><entry><title type="html">Transparent Data Encryption, the missing security layer for enterprise-grade Postgres</title><link href="https://theadamwright.github.io/2023/02/24/TDE-The-Missing-Security-Layer.html" rel="alternate" type="text/html" title="Transparent Data Encryption, the missing security layer for enterprise-grade Postgres" /><published>2023-02-24T00:00:00+00:00</published><updated>2023-02-24T00:00:00+00:00</updated><id>https://theadamwright.github.io/2023/02/24/TDE-The-Missing-Security-Layer</id><content type="html" xml:base="https://theadamwright.github.io/2023/02/24/TDE-The-Missing-Security-Layer.html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Today, data is regarded as the most important asset an organization has. As businesses and government agencies realize the importance of data ownership and portability, there’s been a massive increase in enterprise adoption of open source databases such as PostgreSQL. However, maintaining data security and integrity in your database can be a complex task.</p>

<p>Securing data at rest is largely a solved problem, with various options that provide coverage at the disk or within databases at the transaction level. These options provide flexibility but are meant to address different types of threats an organization may face. As data security concerns rise amongst organizations that have accelerated their cloud journey, data encryption needs to be a best practice. It can help safeguard confidential data and other cloud data assets from accidental exposure and unauthorized access, and it allows organizations to create a security architecture that mitigates numerous threats that could otherwise contribute to a security breach.</p>

<p>Database security is just one of many security layers an organization needs to consider. But it’s the deepest one – without which organizations are likely to find themselves at greater risk in a challenging cyberspace.</p>

<h2 id="the-importance-of-securing-your-data">The importance of securing your data</h2>

<p>Whether or not you’re familiar with the field of cryptography and its effect on world history, you rely on cryptography daily to safeguard communications with your banking, internet search queries and online healthcare providers. When making an online purchase, you rightfully assume the party you are interacting with has gone to great lengths to ensure your connection is secured and your information can’t be read by a third party in transit. But, when you log back onto the site, chances are the merchant has stored your credit card information for future use. And when you log into your health care portal with your personally identifiable information, details about yourself are visible on your device.</p>

<p>Fortunately, internet standards and programming frameworks have made communication between devices easy to secure. HTTP Secure (HTTPS) is the default protocol nowadays and chances are you pay little attention to it. However, the place where your data persists, like the credit card stored for your convenience with an online merchant, or your healthcare information, often goes unprotected.</p>

<p>If you have a basic understanding of database technology, you might assume that someone has gone through the exercise of locking down the database and establishing rules such as who can connect, where a database connection can be made from, database table and column permissions, restricted views and so on. But these safeguards are database-level protections, which only address one layer of an overall security strategy.</p>

<p>The other layer of your database security strategy must protect against the inevitable event that the malicious actor has access to the operating system where your database runs. This breach can occur if a skilled hacker or nation-state has exploited a vulnerability in one of the many defenses. If a malicious actor has access to the operating system, they don’t need to go through a database connection.</p>

<h2 id="protecting-your-database-backups">Protecting your database backups</h2>

<p>Once hackers or insiders have access to the operating system, they have plenty of tools to bypass database controls and read data from the binary files on the storage disk that holds your company’s persistent data. These database files can contain a customer’s personally identifiable information such as credit card and banking details and medical history. And if you are a merchant whose database system is needed to keep sales coming in, you likely have copies of the database in the form of backups ready to be restored quickly to avoid revenue loss. And in healthcare, restoring a database from a backup is critical to preventing a single point of failure that could result in delayed life-saving medical care.</p>

<p>Database backups are critical to avoiding revenue loss and, sometimes, vital in providing timely medical care. Those database backups contain the same information as your primary database serving customers. And in many cases, those database backups can be soft targets without all of the protections and tripwires put in place on the primary database system. The number of people who have access to your database files has expanded with teams responsible for the administration of backups, for example: storage, system, and backup administrators. Since these files are stored on different systems, hackers now have new entry points for breach attempts.</p>

<h2 id="transparent-data-encryption-for-postgresql">Transparent Data Encryption for PostgreSQL</h2>

<p>Over the last 5 years, PostgreSQL has become a critical component in the most sensitive environments like payment processors, banking, and health care. To ensure their customers’ information doesn’t end up for sale on internet forums, Transparent Data Encryption (TDE) is essential for enterprises using Postgres. Using TDE, organizations can enable AES encryption for their Postgres database system. AES encryption is the de facto encryption standard for sensitive applications. TDE means all user data is automatically encrypted when written to disk. And since the database files are encrypted, so are the database backups. The data on disk is unintelligible whether it’s on the live database system or in an organization’s backup storage.</p>

<p>Most people comfortably send sensitive data across the internet, trusting internet communication standards used by the largest online retailers, banks, and search engines. TDE helps ensure confidentiality by encrypting data stored in databases, protecting sensitive data even if the database is accessed by unauthorized individuals. It also helps organizations comply with privacy and security regulations, such as GDPR, PCI DSS, and HIPAA, which require the protection of sensitive data. Additionally, investing in transparent data encryption can improve an organization’s reputation by demonstrating a commitment to security and privacy, which increases customer trust.</p>

<p>The attraction of TDE is clear. Developers, users, admins, and applications can proceed without thinking twice about encryption. However, an encryption key is required to access the data, so proper management of those keys is essential.</p>

<p>As data security concerns rise amongst large businesses that have accelerated their cloud journey, especially those in financial services, data encryption needs to be a best practice. Now organizations can trust storing sensitive data in Postgres using de facto encryption standards and a well-established method in TDE to protect database information.</p>]]></content><author><name>Adam Wright</name></author><summary type="html"><![CDATA[Why encrypting data at rest matters for Postgres, what TDE protects you from, and how it covers the soft targets most teams forget — like backups.]]></summary></entry><entry><title type="html">EPAS security feature you should know about - User Profiles</title><link href="https://theadamwright.github.io/2023/01/08/User-Profiles.html" rel="alternate" type="text/html" title="EPAS security feature you should know about - User Profiles" /><published>2023-01-08T00:00:00+00:00</published><updated>2023-01-08T00:00:00+00:00</updated><id>https://theadamwright.github.io/2023/01/08/User-Profiles</id><content type="html" xml:base="https://theadamwright.github.io/2023/01/08/User-Profiles.html"><![CDATA[<h2 id="overview-of-role-management-in-postgres">Overview of role management in Postgres</h2>

<p>EDB Postgres Advanced Server (EPAS) is built on PostgreSQL and has been merging changes from the upstream project for over 15 years. Every new major release of PostgreSQL results in a new major release of EPAS.</p>

<p>You can initialize EPAS in one of two modes: Berkeley/Postgres and Redwood/Oracle. Initializing in Redwood Mode helps developers familiar with Oracle by setting 
date/time Postgres GUCs to behave like Oracle Database plus adds many <code class="language-plaintext highlighter-rouge">DBMS_</code> and <code class="language-plaintext highlighter-rouge">UTL_</code> subprograms that Oracle Database developers are likely familiar with. This post focuses on the User Profiles capabilities under the advanced security additions to EPAS.</p>

<p><img src="/images/epas_advanced_security.png" alt="Advanced Security" /></p>

<p>Since EPAS <strong>is</strong> Postgres, all user/role concepts from PostgreSQL apply. As usual, the PostgreSQL <a href="https://www.postgresql.org/docs/current/user-manag.html">documentation</a> covers database users and privileges in depth. Plenty of bloggers have written on this topic with valuable perspectives, and you can find them with a simple <a href="https://duckduckgo.com/q=postgresql+user+management">search</a>. An additional and optional capability EPAS adds to user management in Postgres is <a href="https://www.enterprisedb.com/docs/epas/latest/epas_security_guide/04_profile_management/">User Profiles</a>. The concept of User Profiles will be familiar to many users of commercial database systems.</p>

<p>This blog post aims to remind people how User Profiles in EPAS help you meet the password requirements of your organization.</p>

<h2 id="password-complexity">Password Complexity</h2>

<p>The main benefit of password complexity is that it makes a password harder to crack than a dictionary word. Harder, meaning that it will theoretically take a computer a long time to break a reasonably long password with a combination of letters, numbers, and characters. To test this, you only need to set a password to a string of characters and use one of the many password crackers online that will quickly find a match.</p>

<p>Whether or not you are convinced that password complexity rules are meaningful security measures, it often doesn’t matter, as almost all organizations have password complexity rules that apply to database systems.</p>

<p>PostgreSQL has no internal mechanism to enforce password complexity, and the PostgreSQL catalog tables do not indicate how long or complex a user’s password could be. The following two commands that create roles with login privileges will succeed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create user dolores with password '&lt;BGZ8#qZ\O|XXll';
create user maeve with password 'abc';
</code></pre></div></div>

<p>If you look at the <code class="language-plaintext highlighter-rouge">pg_user</code> and <code class="language-plaintext highlighter-rouge">pg_shadow</code> tables, you cannot tell that one password only contains three letters and the other is longer with a mix of letters and numbers. The <code class="language-plaintext highlighter-rouge">pg_user</code> table would reveal if no password has been set for a role. Not being able to glean more information about a user’s password likely has more upside than downside.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------
usename | dolores
passwd  | SCRAM-SHA-256$4096:Ym8P3kLM+65/KK7r+gHQ/g==$md1qpwP2TC+rlnty5wXEpL51Ba/vUphDUCRnrNgB0z0=:sY8sZ9OjnH4nutv1H+MoK9hH0ojvWM7w0ueM6jL0LUI=
passwd  | ********
-[ RECORD 2 ]----------------------------------------------------------------------------------------------------------------------------------
usename | maeve
passwd  | SCRAM-SHA-256$4096:fNidP3OzqTpnbFIGRqj6fw==$6sNo44R3VGarHOPtUA4FIMCYexoQyvDUG9mKrfS180Q=:EQ/3JMlHrAAQksHM6KPoVSohzI5lyB5Sf7j3C8mlEOY=
passwd  | ********
</code></pre></div></div>

<h3 id="enforcing-complexity-with-user-profiles">Enforcing complexity with User Profiles</h3>

<p>User Profiles are flexible in the way policies can be enforced. You can apply any or all of the several policy rules in EPAS. The actual flexibility provided to you is by allowing you to write database functions that enforce your organization’s unique policy.</p>

<p>Many organizations use the  National Institute of Standards and Technology (NIST) guidance when crafting policy. And since NIST guidance ranks password length above password complexity, let’s start with an example of enforcing password length with User Profiles in EPAS by writing a database function. We will create a Profile and password verification function using <code class="language-plaintext highlighter-rouge">edbspl</code> to associate with our newly created Profile.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE OR REPLACE FUNCTION sys.verify_password(user_name varchar, new_password varchar, old_password varchar)
RETURN boolean IMMUTABLE
IS

BEGIN
  IF (length(new_password) &lt; 15)
  THEN
   RAISE EXCEPTION 'Password must consist of 15 or more characters'; 
  END IF;

  RETURN true;
END;

CREATE PROFILE org_password_policy LIMIT PASSWORD_VERIFY_FUNCTION verify_password;
</code></pre></div></div>

<p>Now that we have our policy set, let’s create user maeve like above, but associate the newly created Profile with maeve:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create user maeve password 'abc' profile org_password_policy;
ERROR:  Password must consist of 15 or more characters
</code></pre></div></div>

<p>This time we cannot create a new login role for maeve using the same password because the password does not contain enough characters. Enforcing character length alone goes a long way, but your organization likely has more requirements. And your organization could have different policies for different groups. So, it’s best to future-proof with separate functions for checking and setting password rules. Doing so prevents you from forking multiple <code class="language-plaintext highlighter-rouge">verify_password</code> functions and allows you to add additional rules and fix bugs easily.</p>

<p>Let’s create a new password rule checker function and a function associated with a User Profile that defines the password rules. For portability, we will use a function that follows similar logic to Oracle’s and other commercial databases but works in both Redwood and non-Redwood modes in EPAS. Most of Oracle’s sample scripts for User Profiles work out of the box in Redwood Mode, and many DBAs are familiar with this pattern already. In our <a href="https://github.com/theadamwright/assets/blob/main/epas/epas_password_rule_check.sql">password_rule_checker</a>, we will create checks to escape the delimiters and password length and enforce a mix of letters and lower and upper case characters. Here are the possible exceptions thrown from our password rule check function.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  IF substring(new_password FROM old_password) IS NOT NULL
  THEN
    RAISE EXCEPTION 'New password cannot include old password';
  END IF;

   IF delimiter = TRUE THEN
      RAISE EXCEPTION 'Password cannot contain a double-quote character';
   END IF;

   IF chars IS NOT NULL AND len &lt; chars THEN
      RAISE EXCEPTION 'Password length less than % ', chars;                       
   END IF;

   IF letter IS NOT NULL AND cnt_letter &lt; letter THEN
      RAISE EXCEPTION 'Password must contain at least % letter(s)', letter; 
   END IF;

   IF digit IS NOT NULL AND cnt_digit &lt; digit THEN
      RAISE EXCEPTION 'Password must contain at least % digit(s)', digit;
   END IF;
</code></pre></div></div>

<p>Next, we will create a new function that sets the password complexity rules: at least eight characters long, must contain one number, and at least one lower and one upper case character.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE OR REPLACE FUNCTION sys.password_rules
(username varchar,new_password varchar, old_password varchar)
RETURN boolean IMMUTABLE 
IS
BEGIN 
   IF NOT password_rule_checker(new_password, old_password, chars =&gt; 8, letter =&gt; 1, digit =&gt; 1) THEN
      RETURN(FALSE);
   END IF;
   RETURN(TRUE);
END;
</code></pre></div></div>

<p>Now let’s go back through the previous scenario, creating a User Profile and new login role while testing several scenarios.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postgres=# create user maeve password 'abc"1234' profile org_password_policy;
ERROR:  Password cannot contain a double-quote character

postgres=# create user maeve password 'abc123' profile org_password_policy;
ERROR:  Password length less than 8 

postgres=# create user maeve password '12345678' profile org_password_policy;
ERROR:  Password must contain at least 1 letter(s)

postgres=# create user maeve password 'abcdefgh' profile org_password_policy;
ERROR:  Password must contain at least 1 digit(s)

postgres=# create user maeve password 'abcd1234' profile org_password_policy;
CREATE ROLE
</code></pre></div></div>

<p>These examples demonstrate how you can easily enforce any password policy with User Profiles in EPAS. One could easily add additional requirements like checking the password string for upper and lower characters and block setting a password to one that matches a password in a common password list.</p>

<h3 id="failed-login-attempts">Failed login attempts</h3>

<p>While the first defense strategy should control where users can connect via <code class="language-plaintext highlighter-rouge">pg_hba.conf</code> settings, you should always add additional defense layers and assume that, at some point, other areas of your infrastructure will be misconfigured or even compromised.</p>

<p>For this, let’s assume a malicious actor comes from an IP that matches a rule in your <code class="language-plaintext highlighter-rouge">pg_hba.conf</code>. Having a good complexity rule in place will help ensure that the malicious actor can’t guess the password quickly. To keep anyone from using a password cracker without disruption, we can follow the examples from <a href="https://www.enterprisedb.com/docs/epas/latest/epas_security_guide/04_profile_management/01_creating_a_new_profile/#examples">EDB Docs</a> and add <code class="language-plaintext highlighter-rouge">FAILED_LOGIN_ATTEMPTS</code> and <code class="language-plaintext highlighter-rouge">PASSWORD_LOCK_TIME</code>. If you’re familiar with <code class="language-plaintext highlighter-rouge">fail2ban</code>, think of these like <code class="language-plaintext highlighter-rouge">maxretry</code> and <code class="language-plaintext highlighter-rouge">bantime</code>.</p>

<p>We can demonstrate that it’s possible to keep trying different passwords without these rules until a person is alerted and intervenes. Let’s try five consecutive login attempts with bad passwords and then use the correct password on attempt six:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ for v_pw in "qwerty" "Qwerty123" "password" "password1" "qwertyuiop" "abcd1234"; do 
        export PGPASSWORD=${v_pw};
        psql -h 172.17.0.2 -d postgres -U maeve
done

psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"

psql (15.1.0-beta1.1)
Type "help" for help.

postgres=&gt; 
</code></pre></div></div>
<p>Even after five consecutive failed attempts, we have a connection to the Postgres database on the sixth attempt. Five attempts could have been 500 attempts, and the result would have been the same. You can configure PostgreSQL to log those failed attempts, which would appear in your Postgres logs: <code class="language-plaintext highlighter-rouge">2023-01-07 21:49:18 UTC FATAL:  password authentication failed for user "maeve"</code>. Many log management tools and services are available today that check log files and alert you on rules that you’ve established. The <a href="https://github.com/bucardo/tail_n_mail">tail_n_mail</a> logfile watcher is a well-known alternative in Postgres circles and detects interesting items in log files and alerts you via email. But, in our case, we want to stop a brute-force attack as it’s happening. To do this, we will set the number of failed login attempts to five and lock the login role for one day.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE PROFILE org_password_policy LIMIT 
    FAILED_LOGIN_ATTEMPTS 5
    PASSWORD_LOCK_TIME 1
    PASSWORD_VERIFY_FUNCTION password_rules;
</code></pre></div></div>

<p>With our new Profile rules in place, let’s try logging in as maeve with five passwords and then with the correct password on the sixth attempt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ for v_pw in "qwerty" "password" "password1" "qwertyuiop" "postgres" "abcd1234"; do 
        export PGPASSWORD=${v_pw};
        psql -h 172.17.0.2 -d postgres -U maeve
done

psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  password authentication failed for user "maeve"
psql: error: connection to server at "172.17.0.2", port 5444 failed: FATAL:  role "maeve" is locked
</code></pre></div></div>

<p>This time, the database server locks maeve out after five failed attempts. When this happens, you can check the <code class="language-plaintext highlighter-rouge">pg_user</code> table for the extra <code class="language-plaintext highlighter-rouge">uselockdate</code> column in EPAS to see when a user locked their account.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postgres=# select usename, uselockdate from pg_user where usename = 'maeve';
 usename |          uselockdate          
---------+-------------------------------
 maeve     | 2023-01-07 21:51:07.729187+00
</code></pre></div></div>

<p>Another common password rule is that a user of the system must change their password every x number of days, and a user cannot reuse a password within x number of days and must change their password so many times before using the password. We can use the <code class="language-plaintext highlighter-rouge">PASSWORD_LIFE_TIME</code> and <code class="language-plaintext highlighter-rouge">PASSWORD_REUSE_*</code> rules covered in <a href="https://www.enterprisedb.com/docs/epas/latest/epas_security_guide/04_profile_management/">EDB Docs</a> to enforce these.</p>

<h2 id="other-considerations">Other Considerations</h2>

<p>Most organizations have a single source of truth for users and prefer to authenticate database users through a Directory server like LDAP. Authenticating database users against LDAP is covered in <a href="https://www.postgresql.org/docs/current/auth-ldap.html">PostgreSQL docs</a> and usually works without much fuss, which allows you to streamline activities like enforcing password complexity. You no longer have to mimic rules in the database that match your Directory server.</p>

<p>There are some considerations when authenticating against a 3rd party identity service:</p>
<ul>
  <li>You can end up managing users in two places: the user gets added to LDAP, and now the DBA takes a second action to add the LDAP user to Postgres
    <ul>
      <li>A few good tools, such as <a href="https://ldap2pg.readthedocs.io/en/latest/#installation">ldap2pg</a> are available to reduce this burden: the user is added to an LDAP organizational unit by a Domain Administrator, and the synchronization runs at some interval to keep Postgres in sync with LDAP, either by adding the user to Postgres or removing the user from Postgres.</li>
    </ul>
  </li>
  <li>Another challenge is the authentication between Postgres and LDAP since the LDAP bind user in many organizations has its own password. In practice, your synchronization tool has to store the bind user ID and password, and Postgres needs to store the bind user ID and password. There are some less commonly used ways of avoiding this, such as having PAM read from LDAP, but the most common case in practice is storing the LDAP configuration with options for search+bind in the pg_hba.conf. Note: EPAS 15 (Feb 2023) will have a module that lets you securely obfuscate the LDAP bind password with an external module. However, it’s not yet possible for that same external module to be used by other synchronization tools or a 3rd party connection pool.</li>
  <li>LDAP support among the various connection poolers varies. You will want to check your application-level connection pooling documentation or database-level connection pooler documentation to verify it meets your requirements for external authentication.</li>
</ul>

<p>The challenges above may or may not be relevant to your use case. Hopefully, those challenges will be alleviated for everyone by the various tools soon. For now, EPAS users can meet organizational password policies with User Profiles in addition to the option of external identity services.</p>]]></content><author><name>Adam Wright</name></author><summary type="html"><![CDATA[How User Profiles in EDB Postgres Advanced Server enforce password complexity, lock out brute-force attempts, and help you meet organizational password policy.]]></summary></entry></feed>