From f4d81e00268b3d60f3baa1e3651ab7f0a806ac53 Mon Sep 17 00:00:00 2001 From: Andrew Gaffney Date: Wed, 9 Jul 2025 18:19:08 -0400 Subject: [PATCH] feat(db): LDAP Credential Library operational entities --- .../01_credential_vault_ldap_library.up.sql | 253 ++++++++++++++++++ ...ssh_cert_to_vault_cred_library_view.up.sql | 1 + ...1_credential_vault_library_refactor.up.sql | 1 + .../db/sqltest/initdb.d/01_colors_persona.sql | 7 + .../vault/credential_vault_ldap_library.sql | 64 +++++ .../credential_vault_library_refactor.sql | 21 +- 6 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql create mode 100644 internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql diff --git a/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql b/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql new file mode 100644 index 000000000..f20733f26 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/100/01_credential_vault_ldap_library.up.sql @@ -0,0 +1,253 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + create table credential_vault_ldap_library ( + public_id wt_public_id primary key, + project_id wt_public_id not null, + store_id wt_public_id not null + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + name wt_name, + description wt_description, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + vault_path text not null + constraint vault_path_must_not_be_empty + check(length(trim(vault_path)) > 0) + constraint vault_path_must_have_staticcred_or_creds + check(vault_path ~ '^.+\/(static-cred|creds)\/(?:[^\/\\\s]+\/)*[^\/\\\s]+$'), + credential_type text not null + constraint credential_type_enm_fkey + references credential_type_enm (name) + on delete restrict + on update cascade, + constraint credential_vault_ldap_library_store_id_public_id_uq + unique(store_id, public_id), + constraint credential_vault_ldap_library_store_id_name_uq + unique(store_id, name), + constraint credential_vault_library_fkey + foreign key (project_id, store_id, public_id, credential_type) + references credential_vault_library (project_id, store_id, public_id, credential_type) + on delete cascade + on update cascade + ); + + comment on table credential_vault_ldap_library is + 'credential_vault_ldap_library is a credential library that issues credentials from a vault ldap secret backend.'; + + create trigger insert_credential_vault_library_subtype before insert on credential_vault_ldap_library + for each row execute procedure insert_credential_vault_library_subtype(); + + create trigger update_credential_vault_library_table_update_time before update on credential_vault_ldap_library + for each row execute procedure update_credential_vault_library_table_update_time(); + + create trigger immutable_columns before update on credential_vault_ldap_library + for each row execute procedure immutable_columns('public_id', 'store_id', 'project_id', 'credential_type', 'create_time'); + + create trigger delete_credential_vault_library_subtype after delete on credential_vault_ldap_library + for each row execute procedure delete_credential_vault_library_subtype(); + + create trigger update_version_column after update on credential_vault_ldap_library + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_ldap_library + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_ldap_library + for each row execute procedure default_create_time(); + + create trigger before_insert_credential_vault_library before insert on credential_vault_ldap_library + for each row execute procedure before_insert_credential_vault_library(); + + create function default_vault_ldap_credential_type() returns trigger + as $$ + begin + if new.credential_type is distinct from 'username_password_domain' then + new.credential_type = 'username_password_domain'; + end if; + return new; + end; + $$ language plpgsql; + comment on function default_vault_ldap_credential_type is + 'default_vault_ldap_credential_type ensures the credential_type is set to username_password_domain'; + + create trigger default_vault_ldap_credential_type before insert on credential_vault_ldap_library + for each row execute procedure default_vault_ldap_credential_type(); + + -- Replaces view from 99/01_credential_vault_library_refactor.up.sql + drop view credential_vault_library_issue_credentials; + create view credential_vault_library_issue_credentials as + with + password_override (library_id, username_attribute, password_attribute) as ( + select library_id, + nullif(username_attribute, wt_to_sentinel('no override')), + nullif(password_attribute, wt_to_sentinel('no override')) + from credential_vault_generic_library_username_password_mapping_ovrd + ), + ssh_private_key_override (library_id, username_attribute, private_key_attribute, private_key_passphrase_attribute) as ( + select library_id, + nullif(username_attribute, wt_to_sentinel('no override')), + nullif(private_key_attribute, wt_to_sentinel('no override')), + nullif(private_key_passphrase_attribute, wt_to_sentinel('no override')) + from credential_vault_generic_library_ssh_private_key_mapping_ovrd + ), + password_domain_override (library_id, username_attribute, password_attribute, domain_attribute) as ( + select library_id, + nullif(username_attribute, wt_to_sentinel('no override')), + nullif(password_attribute, wt_to_sentinel('no override')), + nullif(domain_attribute, wt_to_sentinel('no override')) + from credential_vault_generic_library_usern_pass_domain_mapping_ovrd + ) + select library.public_id as public_id, + library.store_id as store_id, + library.name as name, + library.description as description, + library.create_time as create_time, + library.update_time as update_time, + library.version as version, + library.vault_path as vault_path, + library.http_method as http_method, + library.http_request_body as http_request_body, + library.credential_type as credential_type, + null as key_type, + null as key_bits, + null as username, + null as ttl, + null as key_id, + null as critical_options, + null as extensions, + store.project_id as project_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.worker_filter as worker_filter, + store.ct_token as ct_token, -- encrypted + store.token_hmac as token_hmac, + store.token_status as token_status, + store.token_key_id as token_key_id, + store.client_cert as client_cert, + store.ct_client_key as ct_client_key, -- encrypted + store.client_key_id as client_key_id, + coalesce(upasso.username_attribute, sshpk.username_attribute, pd.username_attribute) + as username_attribute, + coalesce(upasso.password_attribute, pd.password_attribute) + as password_attribute, + pd.domain_attribute as domain_attribute, + sshpk.private_key_attribute as private_key_attribute, + sshpk.private_key_passphrase_attribute as private_key_passphrase_attribute, + 'generic' as cred_lib_type, -- used to switch on + null as additional_valid_principals + from credential_vault_generic_library library + join credential_vault_store_client store + on library.store_id = store.public_id + left join password_override upasso + on library.public_id = upasso.library_id + left join ssh_private_key_override sshpk + on library.public_id = sshpk.library_id + left join password_domain_override pd + on library.public_id = pd.library_id + union + select library.public_id as public_id, + library.store_id as store_id, + library.name as name, + library.description as description, + library.create_time as create_time, + library.update_time as update_time, + library.version as version, + library.vault_path as vault_path, + null as http_method, + null as http_request_body, + library.credential_type as credential_type, + library.key_type as key_type, + library.key_bits as key_bits, + library.username as username, + library.ttl as ttl, + library.key_id as key_id, + library.critical_options as critical_options, + library.extensions as extensions, + store.project_id as project_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.worker_filter as worker_filter, + store.ct_token as ct_token, -- encrypted + store.token_hmac as token_hmac, + store.token_status as token_status, + store.token_key_id as token_key_id, + store.client_cert as client_cert, + store.ct_client_key as ct_client_key, -- encrypted + store.client_key_id as client_key_id, + null as username_attribute, + null as password_attribute, + null as domain_attribute, + null as private_key_attribute, + null as private_key_passphrase_attribute, + 'ssh-signed-cert' as cred_lib_type, -- used to switch on + additional_valid_principals as additional_valid_principals + from credential_vault_ssh_cert_library library + join credential_vault_store_client store + on library.store_id = store.public_id + union + select library.public_id as public_id, + library.store_id as store_id, + library.name as name, + library.description as description, + library.create_time as create_time, + library.update_time as update_time, + library.version as version, + library.vault_path as vault_path, + null as http_method, + null as http_request_body, + library.credential_type as credential_type, + null as key_type, + null as key_bits, + null as username, + null as ttl, + null as key_id, + null as critical_options, + null as extensions, + store.project_id as project_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.worker_filter as worker_filter, + store.ct_token as ct_token, -- encrypted + store.token_hmac as token_hmac, + store.token_status as token_status, + store.token_key_id as token_key_id, + store.client_cert as client_cert, + store.ct_client_key as ct_client_key, -- encrypted + store.client_key_id as client_key_id, + pd.username_attribute as username_attribute, + pd.password_attribute as password_attribute, + pd.domain_attribute as domain_attribute, + null as private_key_attribute, + null as private_key_passphrase_attribute, + 'ldap' as cred_lib_type, -- used to switch on + null as additional_valid_principals + from credential_vault_ldap_library library + join credential_vault_store_client store + on library.store_id = store.public_id + left join password_domain_override pd + on library.public_id = pd.library_id; + + comment on view credential_vault_library_issue_credentials is + 'credential_vault_library_issue_credentials is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. ' + 'This view should only be used when issuing credentials from a Vault credential library. Each row may contain encrypted data. ' + 'This view should not be used to retrieve data which will be returned external to boundary.'; + +insert into oplog_ticket (name, version) +values + ('credential_vault_ldap_library', 1); + +commit; diff --git a/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql b/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql index 564bd1847..c70acf944 100644 --- a/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql +++ b/internal/db/schema/migrations/oss/postgres/63/02_add_ssh_cert_to_vault_cred_library_view.up.sql @@ -4,6 +4,7 @@ begin; -- Replaces view from 56/02_add_data_key_foreign_key_references.up.sql + -- Replaced in 78/01_ssh_signed_certs_additional_valid_principals drop view credential_vault_library_issue_credentials; create view credential_vault_library_issue_credentials as with diff --git a/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql b/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql index 501af20cf..7326e1f22 100644 --- a/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql +++ b/internal/db/schema/migrations/oss/postgres/99/01_credential_vault_library_refactor.up.sql @@ -378,6 +378,7 @@ begin; from final; -- Replaces view defined in 98/02_username_password_domain_vault.up.sql + -- Replaced in 100/01_credential_vault_ldap_library.up.sql drop view credential_vault_library_issue_credentials; create view credential_vault_library_issue_credentials as with diff --git a/internal/db/sqltest/initdb.d/01_colors_persona.sql b/internal/db/sqltest/initdb.d/01_colors_persona.sql index f792a10e4..301da03b0 100644 --- a/internal/db/sqltest/initdb.d/01_colors_persona.sql +++ b/internal/db/sqltest/initdb.d/01_colors_persona.sql @@ -493,6 +493,13 @@ begin; ('cvs__rcolors', 'cvl__ssh__r1', 'red vault ssh library', '/ssh/issue/red', 'webdev', 'ecdsa', 521), ('cvs__gcolors', 'cvl__ssh__g1', 'green vault ssh library', '/ssh/issue/green', 'dba', 'rsa', 4096); + insert into credential_vault_ldap_library + (store_id, public_id, name, vault_path, credential_type) + values + ('cvs__bcolors', 'cvl__ldap_b1', 'blue vault ldap library', '/ldap/static-cred/blue', 'username_password_domain'), + ('cvs__rcolors', 'cvl__ldap_r1', 'red vault ldap library', '/ldap/creds/red', 'username_password_domain'), + ('cvs__gcolors', 'cvl__ldap_g1', 'green vault ldap library', '/ldap/static-cred/org-colors-r-us/group-green-color/green','username_password_domain'); + insert into credential_static_store (project_id, public_id, name, description) values diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql new file mode 100644 index 000000000..c2f36757e --- /dev/null +++ b/internal/db/sqltest/tests/credential/vault/credential_vault_ldap_library.sql @@ -0,0 +1,64 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(4); + select wtt_load('widgets', 'iam', 'kms', 'auth', 'hosts', 'targets', 'credentials'); + + select lives_ok( + $$ + insert into credential_vault_ldap_library + (store_id, public_id, vault_path, credential_type) + values + ('vs_______wvs', 'vl______vlsc1', '/ldap/static-cred/foo', 'username_password_domain'), + ('vs_______wvs', 'vl______vlsc2', '/ldap/static-cred/foo/bar', 'username_password_domain'), + ('vs_______wvs', 'vl______vlsc3', '/ldap/static-cred/foo/bar/baz', 'username_password_domain'), + ('vs_______wvs', 'vl______vlsc4', '/ldap/static-cred/foo/bar/baz/more/paths', 'username_password_domain'), + ('vs_______wvs', 'vl______vldyn5', '/ldap/creds/foo', 'username_password_domain'), + ('vs_______wvs', 'vl______vldyn6', '/ldap/creds/foo/bar/baz/more/paths', 'username_password_domain'), + ('vs_______wvs', 'vl______vldyn7', '/ldap/creds/foo/bar/baz', 'something_that_doesnt_exist'); + $$ + ); + + select results_eq( + $$ + select public_id, project_id, store_id, name, description, vault_path, credential_type + from credential_vault_ldap_library + where public_id like 'vl______vlsc%' + or public_id like 'vl______vldyn%' + order by public_id; + $$, + $$ + values + ('vl______vldyn5'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo', 'username_password_domain'), + ('vl______vldyn6'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo/bar/baz/more/paths', 'username_password_domain'), + ('vl______vldyn7'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/creds/foo/bar/baz', 'username_password_domain'), + ('vl______vlsc1'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo', 'username_password_domain'), + ('vl______vlsc2'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar', 'username_password_domain'), + ('vl______vlsc3'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar/baz', 'username_password_domain'), + ('vl______vlsc4'::wt_public_id, 'p____bwidget'::wt_public_id, 'vs_______wvs'::wt_public_id, null::wt_name, null::wt_description, '/ldap/static-cred/foo/bar/baz/more/paths', 'username_password_domain'); + $$ + ); + + select row_eq( + $$ + select count(*) + from credential_vault_library + where public_id like 'vl______vlsc%' + or public_id like 'vl______vldyn%'; + $$, + row(7::bigint) + ); + + select row_eq( + $$ + select count(*) + from credential_library + where public_id like 'vl______vlsc%' + or public_id like 'vl______vldyn%'; + $$, + row(7::bigint) + ); + + select * from finish(); +rollback; diff --git a/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql b/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql index 5720404d9..f015f0efb 100644 --- a/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql +++ b/internal/db/sqltest/tests/credential/vault/credential_vault_library_refactor.sql @@ -555,14 +555,17 @@ begin; $$, $$ values - ( 'cvl_______b1'::wt_public_id, 'blue vault library'::wt_name), - ( 'cvl__ssh__b1'::wt_public_id, 'blue vault ssh library'::wt_name), - ( 'cvl_______g1'::wt_public_id, 'green vault library'::wt_name), - ( 'cvl__ssh__g1'::wt_public_id, 'green vault ssh library'::wt_name), - ( 'cvl_______r1'::wt_public_id, 'red vault library'::wt_name), - ( 'cvl__ssh__r1'::wt_public_id, 'red vault ssh library'::wt_name), - ( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name), - ('clvsclt_35iWGH12'::wt_public_id, 'vault_ssh_cert1'::wt_name) + ( 'cvl__ldap_b1'::wt_public_id, 'blue vault ldap library'::wt_name), + ( 'cvl_______b1'::wt_public_id, 'blue vault library'::wt_name), + ( 'cvl__ssh__b1'::wt_public_id, 'blue vault ssh library'::wt_name), + ( 'cvl__ldap_g1'::wt_public_id, 'green vault ldap library'::wt_name), + ( 'cvl_______g1'::wt_public_id, 'green vault library'::wt_name), + ( 'cvl__ssh__g1'::wt_public_id, 'green vault ssh library'::wt_name), + ( 'cvl__ldap_r1'::wt_public_id, 'red vault ldap library'::wt_name), + ( 'cvl_______r1'::wt_public_id, 'red vault library'::wt_name), + ( 'cvl__ssh__r1'::wt_public_id, 'red vault ssh library'::wt_name), + ( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name), + ('clvsclt_35iWGH12'::wt_public_id, 'vault_ssh_cert1'::wt_name); $$ ); @@ -578,7 +581,7 @@ begin; ( 'cvl_______b1'::wt_public_id, 'blue vault library'::wt_name), ( 'cvl_______g1'::wt_public_id, 'green vault library'::wt_name), ( 'cvl_______r1'::wt_public_id, 'red vault library'::wt_name), - ( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name) + ( 'clvlt_V134iOjA'::wt_public_id, 'vault_generic1_updated'::wt_name); $$ );