Skip to content

Password auth #32131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Apr 14, 2025
Merged

Password auth #32131

merged 40 commits into from
Apr 14, 2025

Conversation

DAlperin
Copy link
Member

@DAlperin DAlperin commented Apr 8, 2025

This is the bones of self managed password auth. This is missing integration tests and documentation which will come next. You can test this locally like so:

#this starts materialize with auth enabled on the external port but not the internal port
$ bin/environmentd --bazel --reset -- --all-features --unsafe-mode --enable-self-hosted-auth

#start a mz_system session, turn on sql support and create a user
$ psql -U mz_system -h localhost -p 6877 materialize
NOTICE:  connected to Materialize v0.139.0-dev.0
  Org ID: 1bd2c405-c638-44cc-b917-6d05dfb832ac
  Region: local/az1
  User: mz_system
  Cluster: mz_system
  Database: materialize
  Schema: public
  Session UUID: fe6d2dbe-3a94-430d-82e1-5e79cb14b91f

Issue a SQL query to get started. Need help?
  View documentation: https://materialize.com/s/docs
  Join our Slack community: https://materialize.com/s/chat
    
psql (14.17 (Homebrew), server 9.5.0)
Type "help" for help.

materialize=> alter system set enable_self_managed_auth=true;
NOTICE:  variable "enable_self_managed_auth" was updated for the system, this will have no effect on the current session
ALTER SYSTEM
materialize=> create role foo with superuser password 'bar';
CREATE ROLE

# Now connect over the port with auth enabled
$ psql -U foo -h localhost -p 6875 materialize
Password for user foo: 

This begins implementing #32005

Motivation

Tips for reviewer

Checklist

  • This PR has adequate test coverage / QA involvement has been duly considered. (trigger-ci for additional test/nightly runs)
  • This PR has an associated up-to-date design doc, is a design doc (template), or is sufficiently small to not require a design.
  • If this PR evolves an existing $T ⇔ Proto$T mapping (possibly in a backwards-incompatible way), then it is tagged with a T-proto label.
  • If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label (example).
  • If this PR includes major user-facing behavior changes, I have pinged the relevant PM to schedule a changelog post.

@DAlperin DAlperin force-pushed the dov/password-auth-scratch branch from a80a26b to 39260a0 Compare April 8, 2025 18:14
return;
}
let password = password.expect("a password, we don't support other auth modes for now");
if let Some(role) = self.catalog().try_get_role_by_name(role_name.as_str()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

holy nesting, batman!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could reduce some of the nesting by using let else with an early return.


fn hash_password_inner(opts: &HashOpts, password: &[u8]) -> [u8; SHA256_OUTPUT_LEN] {
let mut salted_password = [0u8; SHA256_OUTPUT_LEN];
pbkdf2::derive(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not technically constant time, but I don't think we are really subject to timing attacks here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there another document where we outline the set of attacks we are defending against? I can't see anything in the design

@ptravers ptravers self-requested a review April 10, 2025 15:35
Comment on lines 15 to 17
use ring::hmac::{self, Key, HMAC_SHA256};
use ring::pbkdf2::{self, PBKDF2_HMAC_SHA256 as SHA256};
use ring::rand::SecureRandom;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are using BoringSSL here or a repackaging of it[1]. BoringSSL is not FIPS certified[2]. in past positions we have declined to use ring/BoringSSL. it's worth noting that neither ring nor BoringSSL recommend the usage of their code. in past positions we have declined to use both these libraries. what security guarantees are we planning to give to our customers?

[1] https://crates.io/crates/ring
[2] https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md

Copy link
Contributor

@ptravers ptravers Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I retract part of this as it looks like the algorithms in boringssl are FIPS certified. so I guess this is fine?

see: https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md

Copy link
Contributor

@ptravers ptravers Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spoke offline with @DAlperin we are migrating to aws-lc-rs which has passed FIPs certification with an approved lab at least for a specific branch of the repo.

it has not been approved by NIST as of 04/10/2025 for current status, see: https://csrc.nist.gov/Projects/cryptographic-module-validation-program/modules-in-process/Modules-In-Process-List

having said that it seems to be far and away the best option. good find @DAlperin.


/// The default iteration count as suggested by
/// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
const DEFAULT_ITERATIONS: NonZeroU32 = NonZeroU32::new(600_000).unwrap();
Copy link
Contributor

@ptravers ptravers Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're going for 600,000 then we should update the technical design to reflect the new value as last I checked we were planning to use 400,000. there should also be a test showing that we meet that bench criteria of 0.1 seconds per hash.

see: https://github.com/MaterializeInc/materialize/pull/32005/files#r2025880535

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still reading through NIST and references in that website to try to understand why they picked that value.

Copy link
Contributor

@ParkMyCar ParkMyCar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me! Don't forget to "Squash and Merge" :)

A few comments that would be nice to address but aren't blocking. The only thing we need to do is remove "OpenSSL" from our list of approved licenses, once I see that I'll approve!!

deny.toml Outdated
@@ -296,6 +297,7 @@ allow = [
"Zlib",
"Unicode-3.0",
"Unicode-DFS-2016",
"OpenSSL",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging this we need legal's approval. Have you reached out to #operations? If so, can you copy the Slack thread here?

I'm curious where this additional license requirement comes from?

@@ -175,6 +175,11 @@ pub enum AdapterError {
Unstructured(anyhow::Error),
/// The named feature is not supported and will (probably) not be.
Unsupported(&'static str),
/// Some
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left behind comment?

Comment on lines 239 to 240
/// Authentication error.
AuthenticationError,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you include some more context, like what kind of authentication error? Is this from Frontegg or something else?

If you're new to the code and trying to track down why this error is being returned, what would be helpful for you to see here?

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Arbitrary)]
pub struct Password(String);

assert_not_impl_all!(Password: Display);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to assert it doesn't impl Debug as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does impl Debug, but we implement it manually so it always shows Password(****). This is due to the fact that Password gets carried around in lots of structs with #[derive(Debug)]


///Password is a String wrapper type that does not implement Display or Debug
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Arbitrary)]
pub struct Password(String);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!! Thanks for adding this!

Comment on lines +212 to +217
/// Self hosted auth
#[clap(long, env = "ENABLE_SELF_HOSTED_AUTH")]
enable_self_hosted_auth: bool,
/// Self hosted auth over internal port
#[clap(long, env = "ENABLE_SELF_HOSTED_AUTH_INTERNAL")]
enable_self_hosted_auth_internal: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind tossing a TODO(auth) here so we can track the follow-up?

Comment on lines +257 to +258
// No frontegg check, so auth session lasts indefinitely.
let auth_session = pending().right_future();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking!

@@ -47,11 +47,14 @@ COMPLETE 0
statement error non inherit roles not yet supported
CREATE ROLE foo NOINHERIT

statement error LOGIN attribute is not supported, for more information consult the documentation at
statement error db error: ERROR: SUPERUSER, PASSWORD, and LOGIN attributes is not supported in this environment. For more information consult the documentation at https://materialize.com/docs/sql/create-role/#details
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very slick! 💯

Comment on lines +168 to +172
# You might think to yourself "why is creating a role with PASSWORD NULL
# allowed? Especially when self hosted auth isn't enabled?"
# The answer is unsatisfying: it's a legacy behavior from Postgres.
# Creating a role with a null password is the same as not specifying a password at all.
# So, uh, sure...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I was wondering, so thanks for adding this comment!

@DAlperin DAlperin enabled auto-merge (squash) April 14, 2025 18:25
@DAlperin DAlperin merged commit d37c5be into MaterializeInc:main Apr 14, 2025
83 checks passed
def- added a commit to def-/materialize that referenced this pull request Apr 15, 2025
@def- def- mentioned this pull request Apr 15, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants