Model credentials v7

A credential stores the secret a model sends to the model provider, such as an API key. A credential holds:

  • One secret, either {"api_key": "..."} or {"basic_auth": "..."}.
  • An endpoint pattern, which lists the hosts the secret may be sent to.
  • An owner, which is the role that created the credential.

One credential can serve many models. Each model uses exactly one credential. Any member of aidb_users can create credentials and models. No AIDB view or function returns a secret.

Creating a credential

Create a credential with aidb.create_credential(), then pass the credential name to aidb.create_model():

SELECT aidb.create_credential(
    name        => 'openai_prod',
    credentials => '{"api_key": "sk-..."}'::JSONB,
    endpoint    => 'api.openai.com'
);

SELECT aidb.create_model(
    name       => 'summarizer',
    provider   => 'openai_completions',
    config     => aidb.completions_config(model => 'gpt-4o-mini'),
    credential => 'openai_prod'
);

endpoint is required and takes an endpoint pattern that lists the hosts the secret may be sent to. The secret is optional. To set it later, see Changing a credential.

A model that needs no secret, such as a local model, doesn't need a credential. A model created without credential uses the built-in credential none, which sends no secret. Every role can use none. The none credential can't be changed or deleted.

Endpoint patterns

Before a model sends a secret, AIDB checks the host the model calls against the credential's endpoint. The pattern is a comma-separated list of entries:

EntryMatches
api.openai.comExactly that host.
.openai.azure.comThat domain and any subdomain.
192.168.1.5, 10.0.0.0/24, fd00::/8IP addresses in that range. Doesn't match host names.
host:8443, [fd00::1]:8443That host on that port. Write an IPv6 address with a port in brackets.
*Any host. Creating a credential with the * entry raises a NOTICE.

An entry without a port matches only the default port of the URL's scheme. *.example.com is rejected. Write .example.com instead.

If the host isn't allowed, the call fails before any request is sent:

ERROR:  credential "openai_prod" allows endpoints "api.openai.com" but model "alice.elsewhere" calls host "example.com"

To check a pattern, or to see which host a model would call:

SELECT aidb.validate_endpoint_pattern('api.openai.com, .openai.azure.com');
SELECT aidb.endpoint_host('openai_completions', 'https://myorg.openai.azure.com/v1/chat/completions');

A model that sends a secret refuses "tls_config": {"insecure_skip_verify": true}. A private certificate authority (CA) set with tls_config.ca_path is allowed.

Listing credentials

To list credentials, query the aidb.credentials view:

SELECT name, owner, endpoint, shared, has_secret, can_use FROM aidb.credentials WHERE name = 'openai_prod';
Output
    name     | owner | endpoint       | shared | has_secret | can_use
-------------+-------+----------------+--------+------------+---------
 openai_prod | alice | api.openai.com | f      | t          | t
(1 row)

Sharing a credential

Share a credential to give other roles access. A new credential is private. Only the credential owner can use the credential and the models that use the credential. Any other role that calls a model that uses the credential gets the following error:

ERROR:  role lacks USAGE on credential "openai_prod" used by model "alice.summarizer"

To let every member of aidb_users use the credential, share the credential:

SELECT aidb.update_credential('openai_prod', share => true);

Pass share => true to aidb.create_credential() to share the credential at creation. Pass share => false to make the credential private again.

A credential is stored as a Postgres foreign server, so the Postgres privilege commands use SERVER syntax. You can run these commands manually for more fine-grained control:

GRANT USAGE ON FOREIGN SERVER openai_prod TO bob;  -- GRANT just to "bob" instead of all "aidb_users"

A role that can use a credential can use every model that uses the credential.

Access checks

AIDB checks access to a credential for the role that runs the query, including a role set with SET ROLE. A SECURITY DEFINER function, and a pipeline with auto_processing set to Live, use their owner's access.

To see which credential a model uses, set client_min_messages to debug1 to show the message in your session, or set log_min_messages to debug1 to write it to the Postgres server log. The first call to the model in a session reports a line such as:

DEBUG:  model "public.summarizer" uses credential "openai_prod"

Changing a credential

Use aidb.update_credential() to change a credential. Arguments you leave out stay unchanged. Changes take effect on the next call in every session. Only the owner of a credential, or a role that has the owner role's privileges, can change the credential. The same restriction applies to superusers.

-- Rotate the key
SELECT aidb.update_credential('openai_prod', credentials => '{"api_key": "sk-new"}'::JSONB);

-- Allow another host
SELECT aidb.update_credential('openai_prod', endpoint => 'api.openai.com, .openai.azure.com');

-- Remove the secret
SELECT aidb.update_credential('openai_prod', credentials => '{}'::JSONB);

A model whose credential has no secret fails with:

ERROR:  credential "openai_prod" has no secret. Set one with aidb.update_credential('openai_prod', credentials => ...)

Credentials can't be renamed. To use another name, create a new credential and recreate the models with the new credential.

Changing the owner

To hand a credential to another role, change the credential owner:

ALTER SERVER openai_prod OWNER TO carol;

Deleting a credential

Delete a credential with aidb.delete_credential(). The function deletes the credential and the credential's secret. If models use the credential, pass cascade => true to delete those models as well, including models that other roles created on a shared credential. aidb.delete_model() doesn't delete the model's credential.

SELECT aidb.delete_credential('openai_prod', cascade => true);

Passing a secret to aidb.create_model()

Pass a secret directly to aidb.create_model() instead of a credential name:

SELECT aidb.create_model(
    name        => 'summarizer',
    provider    => 'openai_completions',
    config      => aidb.completions_config(model => 'gpt-4o-mini'),
    credentials => '{"api_key": "sk-..."}'::JSONB
);

The call creates a credential named <schema>.<model>, for example public.summarizer. The credential is owned by you, is private, and has the host the model calls as the credential endpoint. Pass share => true to share the credential. If the host the model calls can't be determined, the endpoint is *.

  • The call fails if the model or the credential already exists.
  • To change the secret, call aidb.create_model() again with the same arguments and replace_credentials => true.
  • Quote the name in SQL: GRANT USAGE ON FOREIGN SERVER "public.summarizer" TO bob;.
  • If <schema>.<model> is longer than 63 bytes, the call fails. Create a credential and pass credential instead.
Note

A secret written as a literal in SQL can appear in pg_stat_activity, statement logs, and telemetry. In production, read the secret from an environment variable or Kubernetes Secret or pass the secret as a bind parameter from your client library.

Reading a secret from an environment variable or Kubernetes Secret

Instead of storing the secret in the database, a credential can read the secret from the Postgres server each time the model is used. Only superusers and members of the aidb_admins role can create such a credential:

SELECT aidb.create_credential(
    name            => 'openai_env',
    credentials_env => 'AIDB_OPENAI_KEY',
    endpoint        => 'api.openai.com',
    share           => true
);

Use one of the following arguments:

  • credentials_env names an environment variable of the Postgres server. The name must start with the prefix set by aidb.env_var_allowed_prefix (AIDB_ by default).
  • credentials_k8s_secret names a file, or a directory of files, from a mounted Kubernetes Secret. The path must start with the prefix set by aidb.k8s_secret_allowed_path_prefix.

A credential that reads a server secret works only while the credential owner is a superuser or a member of aidb_admins.

Limiting hosts for a server secret

The server operator can limit the hosts a server secret is sent to. Put an endpoint pattern in one of the following places:

SecretHost pattern
Environment variable AIDB_OPENAI_KEYEnvironment variable AIDB_OPENAI_KEY_HOST
File api-keyFile api-key_HOST in the same directory
DirectoryFile host in that directory

When the host pattern is present, the model's host must match both the host pattern and the credential endpoint. Names and files that end in _HOST are never read as secrets.