I'm using a PostgreSQL database and postgres_fdw extension to query external data.
CREATE EXTENSION postgres_fdw;
CREATE SERVER foreign_fake_database
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '....', port '5432', dbname 'fake_database');
When I create the user mapping to query this external database, I must write in clear, username and password:
CREATE USER MAPPING FOR fake_user SERVER foreign_fake_database
OPTIONS ("user" 'fake_user', password 'fake_password');
This method seems fragile to me for obvious security reasons so I'm looking for users feedbacks.
What are the best practices to maintain a good level of security and not have the password stored in clear ? Can I encrypt this password? With multiple users, is it okay to use the same user to connect? Doesn't it overload the system or create conflict?
From a performance viewpoint, it doesn't matter if different users are mapped to the same or to different users on the remote server, this is purely a security consideration.
There is no way to hide or encrypt the password, but you can either use a password file to store the password on the server or use an authentication method that does not require a password at all, like certificate authentication (then you could use sslkey and sslcert in the user mapping).
Note that you must set password_required to false on the user mapping to allow a non-superuser to connect without an explicit password in the user mapping. This option was introduced in PostgreSQL v13.
Related
I have a PostgreSQL instance running on Cloud SQL in GCP. I am connecting to it via a psql client from another machine. The connection is working fine.
However I want to know what happens when you run the following command.
Cloud SQL already uses SCRAM-SHA-256 as a password encryption mechanism by default. I have verified it. So I don't know what this command is doing.
CREATE ROLE temprole NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN
NOREPLICATION NOBYPASSRLS PASSWORD
'SCRAM-SHA-256$4096:H45+UIZiJUcEXrB9SHlv5Q==$I0mc87UotsrnezRKv9Ijqn/zjWMGPVdy1zHPARAGfVs=:nSjwT9LGDmAsMo+GqbmC2X/9LMgowTQBjUQsl45gZzA=';
Is the string inside ' ' set as password? If not, then what is it?
Secondly, how do I login as the temprole user? Do I need to set the password for it again?
PostgreSQL does not store the clear text password, but a hash of it. The password_encryption parameter determines how the clear text password gets hashed. Now when you create or alter a role, you can not only set the clear text password (which the server will hash), but you also can set the already hashed password, like your example shows. The advantage is that you don't have to transfer the clear text password to the PostgreSQL server. (psql's command \password does it that way.)
To log into the server, you need to know the clear text password. Whoever created the hash in the statement you show will know that password, so you will have to ask there.
The system recognizes that string as already being a valid format for a scram verifier (AKA "encrypted" password) and so just stores it for future password verification work. If it had not recognized it as being a valid format, then it would have treated it as a plain password, digesting it into a verifier before storing it. It is infeasible to go from the verifier back to the password--that is one of the criteria that went into the design of the system. Whoever generated that string should know what password it represents. If they don't know (or won't tell you) then you will need to either guess and get lucky, or reset the password before you can log in as that user.
(And by the way, that password was extremely easy to guess, so if it this is a real situation you had better go change it immediately)
According to the documentation the dbname connection parameter defaults to the user name:
dbname
The database name. Defaults to be the same as the user name.
In certain contexts, the value is checked for extended formats;
see Section 31.1.1 for more details on those.
My first question is: what happens if there is no database that corresponds to the user name - and is there a way to define a default other than the database that has the same name as the user name? (How can this be achieved?)
My seconds question is: once connected to a database can a default schema be defined for the user? (How can this be achieved?)
Users cannot be set with a default database, because the decision of which database to connect to is a client-side one made before the server is first connected to.
It's not the PostgreSQL server with that default, it's libpq and the client tools like psql. Those tools examine the PGDATABASE environment variable, so you can set that, but you have no control over the behaviour from server-side.
You can set a default for the schema search path, as #a_horse_with_no_name notes, with
ALTER ROLE rolename SET search_path = ....
I have a rest api which talks to Postgres, right now in the properties file of the api we are hardcoding the DB password.
so we thought when a user role is created in postgres we can use Md5 hash value(or any other encrypted value which should be decrypted by postgres) for the password...and we can use that value(hased value) in api property file instead of hardcoded one.
My question is can we use that Md5 hash value in api dev property file and when the password is sent over network and tries to connect to postgres Will it (postgres) decrypt to actual password and allows the user to connect to DB without authentication failed?????
TL;DR: you can't store the hashed password in a properties file and use it to authenticate unless the client application can recognise that it's pre-hashed and avoid the second hashing pass.
If the client library does recognise pre-hashed passwords (libpq doesn't), the hashed password can be used as a proxy for the real password. You don't need to know the real password if you know the hash. this means it's also no more secure to store the hashed password in the properties file than it is to store the original password.
The password is salted and hashed again before being sent on the wire so you can't sniff what you see on the wire and use that to authenticate.
Looking at the source code, sendAuthRequest in src/backend/libpq/auth.c:
/* Add the salt for encrypted passwords. */
if (areq == AUTH_REQ_MD5)
pq_sendbytes(&buf, port->md5Salt, 4);
port is struct Port in src/include/libpq/libpq-be.h, which has:
char md5Salt[4]; /* Password salt */
This is set by ConnCreate in src/backend/postmaster/postmaster.c:
/*
* Precompute password salt values to use for this connection. It's
* slightly annoying to do this long in advance of knowing whether we'll
* need 'em or not, but we must do the random() calls before we fork, not
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt);
Now, passwords are verified in md5_crypt_verify in src/backend/libpq/crypt.c. There we see that passwords already stored as md5 are hashed again with the session salt:
if (isMD5(shadow_pass))
{
/* stored password already encrypted, only do salt */
if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
port->md5Salt,
sizeof(port->md5Salt), crypt_pwd))
{
pfree(crypt_pwd);
return STATUS_ERROR;
}
}
Thus the hashed password sent on the wire is protected against replay attacks by the session salt.
Whether the client app can recognise a pre-hashed password and the format it expects them to be in depends on the client library.
According to pg_password_sendauth in src/interfaces/libpq/fe-auth.c the libpq front-end doesn't seem to check for pre-hashed password input. Other clients may vary.
Just to be clear - md5 hashing is not encryption.
19.3.2. Password authentication
The password-based authentication methods are md5 and password. These
methods operate similarly except for the way that the password is sent
across the connection, namely MD5-hashed and clear-text respectively.
If you are at all concerned about password "sniffing" attacks then md5
is preferred. Plain password should always be avoided if possible.
However, md5 cannot be used with the db_user_namespace feature. If the
connection is protected by SSL encryption then password can be used
safely (though SSL certificate authentication might be a better choice
if one is depending on using SSL).
PostgreSQL database passwords are separate from operating system user
passwords. The password for each database user is stored in the
pg_authid system catalog. Passwords can be managed with the SQL
commands CREATE USER and ALTER USER, e.g., CREATE USER foo WITH
PASSWORD 'secret'. If no password has been set up for a user, the
stored password is null and password authentication will always fail
for that user.
If you configure your Postgres client authentication file (pg_hba.conf) for md5 password-based authentication, you don't need to explicitly use md5() function to keep database password in your property file.
For encrypting purposes - you can configure database connection to work over SSL. Please check Secure TCP/IP Connections with SSL.
I'm building a system that has a Java Swing front end accessed a postgres database. Prior to discovering Jasypt this week, I had originally planned to use Postgres' own encryption mechanism. It worked fine, but I also now wanted the passwords over the network to be encrypted, hence why I turned to Jasypt.
Problem is, I need a fixed password to be entered into my Postgres stored function. I.e. If the input password is 'aaa' then any other inputed password into the Postgres stored function (other than 'aaa') will not match.
Is there a way to get these two encryption mechanisms to work in tandem together or do I have to dump Postgres'?
My user table:
CREATE TABLE "user"
(
id serial NOT NULL,
cryptpwd text NOT NULL,
md5pwd text NOT NULL,
...
)
Encrypting password:
cryptedPassword = crypt(passwordIn, gen_salt('md5'));
md5Password = md5(passwordIn);
INSERT INTO "user"(username, cryptpwd, md5pwd, ...)
VALUES (usernameIn, cryptedPassword, md5Password, ...);
Decrypting password:
select ..... from "user" .... where username = usernameIn and cryptpwd = crypt(passwordIn, cryptpwd);
If I cannot get the two of them to work together then I would have to dump Postgres' mechanism as I need to have encryption over the network.
Also, with regards to the database connection string and database username and password (not using any framework ... plain old jdbc connection hopefully with SSL - yet to implement), I don't think I'll be able to use Jasypt because I'd need to decrypt it at database level. Would SSL alone be sufficient for this case?
Thanks.
I think SSL alone, on every piece of the path, would be sufficient. In LedgerSMB (although we are Perl-based) we do something different and rely on SSL protected links between servers and between servers and clients. There are a few things to think about with your approach though.
We actually pass the db username and password to the middleware from the client in re-usable format (plain text) over an SSL connection, and then use another SSL connection to log into PostgreSQL to authenticate this way. This works fine, but the problem areas we face are somewhat similar to the problem areas you will. These include:
Logging. Is it possible passwords will get accidently logged? This is a concern with LedgerSMB and we take what steps we can but a badly configured server or a tampered-with program could log usernames and passwords. In our case this comes primarily on the middleware level, but in your case, query logging could do this too, right?
Is it possible credentials can be re-used unintentionally? We prevent this in a couple of ways, but it is worth considering.
On the whole, we trust SSL. There isn't much to be gained from adding additional encryption beyond that, and key management adds a lot of complexity that is not worth the marginal gains IMO.
In my Scala/Play application i have to work with database but there's no default user and password because every authenticated application user is bound to different DB user. So i'd like to specify user name and password on obtaining connection, smth like:
DB.withConnection(user = "James", password = "secret") { ... }
For now i can't find such capabilities in docs (and honestly saying i'm not sure how to specify a search query for my question).
And another question: is it safe to store user password in session taking into account that session is stored on user side? Or are there any best practices for such case when different DB users work with app?
Answer to Question 1
In Play, you obtain datasources by name:
def getDataSource(name: String): DataSource
You'd have to do some heavy hacking of the stuff in package play.api.db to get the functionality you require.
That, or you can predefine a bunch of datasources if the number of users of your app is small, and retrieve the connection by their login name, e.g.:
db.bob.url="jdbc:h2:mem:db_for_bob"
db.bob.driver=org.h2.Driver
db.alice.url="jdbc:h2:mem:db_for_alice"
db.alice.driver=org.h2.Driver
And
DB.withConnection("bob") { implicit connection =>
Or
DB.withConnection(userNameKnownAtRuntime) { implicit connection =>
Answer to Question 2
Even though the data in sessions are heavily encrypted using the application secret, I would recommend not to store these client-side. Instead, implement something along the lines of Resource Owner Password Credentials Grant according to this section in the OAuth 2 spec. That would give your client a token which is only valid for a set period, and could be invalidated server-side if need be.