Repository: edgedb/edgedb

Issue:
EdgeDB server FIPS incompatibility
<!-- Please search existing issues to avoid creating duplicates. -->

<!--
For the EdgeDB Version: run `edgedb query 'select sys::get_version_as_str()'` from your project directory (or run `select sys::get_version_as_str();` in the EdgeDB interactive shell).
For the EdgeDB CLI Version: Run `edgedb --version` from anywhere
-->

- EdgeDB Version: `3.4+75c51ce`
- EdgeDB CLI Version: `3.4.0+97cad0e`
- OS Version: RHEL 8.8 with FIPS enabled

Steps to Reproduce:

1. Init a project with a local instance with the "Initial schema" below. Modify the schema to the "Changed schema" below, and `edgedb migration create` to create a migration. Then `edgedb project unlink` to unlink the project from the local instance.
2. Follow the EdgeDB bare metal deployment instructions on a RHEL machine with FIPS enabled. We're using an AWS EC2 instance for this.
3. Now back on your local machine, run `edgedb project init` and connect to the remote instance.
4. You should get an error `ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS`

There's probably an easier way to reproduce but I'm new to EdgeDB and this is the way I was able to do it. It seems to be related to constraints and long names.

We're evaluating EdgeDB for an enterprise use case where EdgeDB server will need to run on a FIPS-compliant host. The issue is that `_edgedb_name_to_pg_name` uses MD5 to hash the given name to ensure it's small enough to fit in a Postgres column name. MD5 is disabled on FIPS-compliant systems, even when you're not using it for something security related. Very annoying. On that note, the comment in the function mentions that Postgres doesn't have a sha1 implementation in all versions. SHA-1 is not currently disabled, but [will be](https://www.nist.gov/news-events/news/2022/12/nist-retires-sha-1-cryptographic-algorithm) at some point in the near future. I understand the desire to maintain backwards compatibility with older Postgres versions, but would it be possible to detect the Postgres version and use non-MD5 hash if supported? Given that SHA-1 won't be supported soon, ideally there would be a way to opt in to e.g. SHA-224. I realize that, given the objective of this function, it's counterproductive to use something like SHA-224 where (after base 64 encoding) you'll use up 38 characters vs. 27 for SHA-1 and 22 for MD5, but it's the (annoying) reality of FIPS compliance.

Edit: My reading of [NIST's guidance](https://csrc.nist.gov/projects/hash-functions) is that SHAKE-128 and SHAKE-256 are also acceptable for non-security related applications (like this one). Perhaps they could be used with a chosen digest length to limit the size of the hash digests.

<!-- If the issue is about a query error, please also provide your schema -->

Initial schema:

```
module default {
  type Person {
    required name: str;
  }

  type Movie {
    title: str;
    multi actors: Person;
  }
};
```

Changed schema:
```
module default {
  type Person {
    required nameasdfgluhasdlfiuhsdafkjlndfkjlsadhflksdanfksdabfnljksdabfljkdsa: str {
      constraint exclusive;
    };
  }

  type Movie {
    title: str;
    multi actors: Person;
  }
};
```

The full traceback:
```
  Server traceback:
      Traceback (most recent call last):
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler_pool/worker.py", line 186, in compile_in_tx
          units, cstate = COMPILER.compile_in_tx(cstate, *args, **kwargs)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/compiler.py", line 913, in compile_in_tx
          return compile(ctx=ctx, source=source), ctx.state
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/compiler.py", line 2068, in compile
          return _try_compile(ctx=ctx, source=source)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/compiler.py", line 2136, in _try_compile
          comp, capabilities = _compile_dispatch_ql(
                               ^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/compiler.py", line 1975, in _compile_dispatch_ql
          query = ddl.compile_dispatch_ql_migration(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/ddl.py", line 379, in compile_dispatch_ql_migration
          return compile_and_apply_ddl_stmt(ctx, ql)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/ddl.py", line 209, in compile_and_apply_ddl_stmt
          block, new_types, config_ops = _process_delta(ctx, delta)
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/server/compiler/ddl.py", line 345, in _process_delta
          pgdelta.generate(block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/delta.py", line 7210, in generate
          op.generate(block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/delta.py", line 211, in generate
          op.generate(block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/delta.py", line 211, in generate
          op.generate(block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/delta.py", line 211, in generate
          op.generate(block)
        [Previous line repeated 1 more time]
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/dbops/base.py", line 296, in generate
          self_block = self.generate_self_block(block)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/dbops/base.py", line 335, in generate_self_block
          cmd.generate(self_block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/dbops/base.py", line 296, in generate
          self_block = self.generate_self_block(block)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/dbops/base.py", line 335, in generate_self_block
          cmd.generate(self_block)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/deltadbops.py", line 550, in generate
          self.create_constraint(self._constraint)
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/deltadbops.py", line 489, in create_constraint
          cr_trigger = self.create_constr_trigger(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/deltadbops.py", line 410, in create_constr_trigger
          ins_trigger, upd_trigger = self._get_triggers(
                                     ^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/deltadbops.py", line 389, in _get_triggers
          ins_trigger_name = common.edgedb_name_to_pg_name(cname + '_instrigger')
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/common.py", line 195, in edgedb_name_to_pg_name
          return _edgedb_name_to_pg_name(name, prefix_length)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/usr/lib64/edgedb-server-3/lib/python3.11/site-packages/edb/pgsql/common.py", line 168, in _edgedb_name_to_pg_name
          hashlib.md5(name.encode()).digest()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
      ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
```



Discussion:

Resolving patch:
diff --git a/edb/pgsql/common.py b/edb/pgsql/common.py
--- a/edb/pgsql/common.py
+++ b/edb/pgsql/common.py
@@ -165,7 +165,7 @@ def _edgedb_name_to_pg_name(name: str, prefix_length: int = 0) -> str:
     # md5 (and it doesn't matter which function is better cryptographically
     # in this case.)
     hashed = base64.b64encode(
-        hashlib.md5(name.encode()).digest()
+        hashlib.md5(name.encode(), usedforsecurity=False).digest()
     ).decode().rstrip('=')
 
     return (


Test patch: