You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/db/schema/migrations/postgres/2/04_oidc.up.sql

423 lines
15 KiB

begin;
-- auth_oidc_method entries are the current oidc auth methods configured for
-- existing scopes.
create table auth_oidc_method (
public_id wt_public_id
primary key,
scope_id wt_scope_id
not null,
name wt_name,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
state text not null
constraint auth_oidc_method_state_enm_fkey
references auth_oidc_method_state_enm(name)
on delete restrict
on update cascade,
disable_discovered_config_validation bool not null default false,
api_url wt_url, -- an address prefix at which the boundary api is reachable.
issuer wt_url,
client_id text -- oidc client identifier issued by the oidc provider.
constraint client_id_not_empty
check(length(trim(client_id)) > 0),
client_secret bytea, -- encrypted oidc client secret issued by the oidc provider.
client_secret_hmac text
constraint client_secret_hmac_not_empty
check(length(trim(client_secret_hmac)) > 0),
key_id wt_private_id not null -- key used to encrypt entries via wrapping wrapper.
constraint kms_database_key_version_fkey
references kms_database_key_version(private_id)
on delete restrict
on update cascade,
constraint key_id_not_empty
check(length(trim(key_id)) > 0),
max_age int -- the allowable elapsed time in secs since the last time the user was authenticated. A value -1 basically forces the IdP to re-authenticate the End-User. Zero is not a valid value.
constraint max_age_not_equal_zero
check(max_age != 0)
constraint max_age_not_less_then_negative_one
check(max_age >= -1),
constraint auth_method_fkey
foreign key (scope_id, public_id)
references auth_method (scope_id, public_id)
on delete cascade
on update cascade,
constraint auth_oidc_method_scope_id_name_uq
unique(scope_id, name),
constraint auth_oidc_method_scope_id_public_id_uq
unique(scope_id, public_id),
constraint auth_oidc_method_scope_id_issuer_client_id_unique
unique(scope_id, issuer, client_id) -- a client_id must be unique for a provider within a scope.
);
comment on table auth_oidc_method is
'auth_oidc_method entries are the current oidc auth methods configured for existing scopes.';
-- auth_oidc_signing_alg entries are the signing algorithms allowed for an oidc
-- auth method. There must be at least one allowed alg for each oidc auth method.
create table auth_oidc_signing_alg (
create_time wt_timestamp,
oidc_method_id wt_public_id
constraint auth_oidc_method_fkey
references auth_oidc_method(public_id)
on delete cascade
on update cascade,
signing_alg_name text
constraint auth_oidc_signing_alg_enm_fkey
references auth_oidc_signing_alg_enm(name)
on delete restrict
on update cascade,
primary key(oidc_method_id, signing_alg_name)
);
comment on table auth_oidc_signing_alg is
'auth_oidc_signing_alg entries are the signing algorithms allowed for an oidc auth method. There must be at least one allowed alg for each oidc auth method';
-- auth_oidc_aud_claim entries are the audience claims for a specific oidc auth
-- method. There can be 0 or more for each parent oidc auth method. If an auth
-- method has any aud claims, an ID token must contain one of them to be valid.
create table auth_oidc_aud_claim (
create_time wt_timestamp,
oidc_method_id wt_public_id
constraint auth_oidc_method_fkey
references auth_oidc_method(public_id)
on delete cascade
on update cascade,
aud_claim text not null
constraint aud_claim_must_not_be_empty
check(length(trim(aud_claim)) > 0)
constraint aud_claim_must_be_less_than_1024_chars
check(length(trim(aud_claim)) < 1024),
primary key(oidc_method_id, aud_claim)
);
comment on table auth_oidc_aud_claim is
'auth_oidc_aud_claim entries are the audience claims for a specific oidc auth method. There can be 0 or more for each parent oidc auth method. If an auth method has any aud claims, an ID token must contain one of them to be valid.';
-- auth_oidc_certificate entries are optional PEM encoded x509 certificates.
-- Each entry is a single certificate. An oidc auth method may have 0 or more
-- of these optional x509s. If an auth method has any cert entries, they are
-- used as trust anchors when connecting to the auth method's oidc provider
-- (instead of the host system's cert chain).
create table auth_oidc_certificate (
create_time wt_timestamp,
oidc_method_id wt_public_id
constraint auth_oidc_method_fkey
references auth_oidc_method(public_id)
on delete cascade
on update cascade,
certificate bytea not null,
primary key(oidc_method_id, certificate)
);
comment on table auth_oidc_certificate is
'auth_oidc_certificate entries are optional PEM encoded x509 certificates. Each entry is a single certificate. An oidc auth method may have 0 or more of these optional x509s. If an auth method has any cert entries, they are used as trust anchors when connecting to the auth methods oidc provider (instead of the host system cert chain)';
-- auth_oidc_account entries are subtypes of auth_account and represent an
-- oidc account.
create table auth_oidc_account (
public_id wt_public_id
primary key,
auth_method_id wt_public_id
not null,
-- NOTE(mgaffney): The scope_id type is not wt_scope_id because the domain
-- check is executed before the insert trigger which retrieves the scope_id
-- causing an insert to fail.
scope_id text not null,
name wt_name,
description wt_description,
create_time wt_timestamp,
update_time wt_timestamp,
version wt_version,
issuer wt_url not null, -- case-sensitive URL that maps to an id_token's iss claim,
subject text not null -- case-sensitive string that maps to an id_token's sub claim
constraint subject_must_not_be_empty
check (
length(trim(subject)) > 0
)
constraint subject_must_be_less_than_256_chars
check(
length(trim(subject)) <= 255 -- length limit per OIDC spec
),
full_name wt_full_name, -- may be null and maps to an id_token's name claim
email wt_email, -- may be null and maps to the id_token's email claim
constraint auth_oidc_method_fkey
foreign key (scope_id, auth_method_id)
references auth_oidc_method (scope_id, public_id)
on delete cascade
on update cascade,
constraint auth_account_fkey
foreign key (scope_id, auth_method_id, public_id)
references auth_account (scope_id, auth_method_id, public_id)
on delete cascade
on update cascade,
constraint auth_oidc_account_auth_method_id_name_uq
unique(auth_method_id, name),
-- ###############################################################
-- any change to this constraints name must be aligned with the
-- acctUpsertQuery const in internal/auth/oidc/query.go
-- ###############################################################
constraint auth_oidc_account_auth_method_id_issuer_subject_uq
unique(auth_method_id, issuer, subject), -- subject must be unique for a provider within specific auth method
constraint auth_oidc_account_auth_method_id_public_id_uq
unique(auth_method_id, public_id)
);
comment on table auth_oidc_method is
'auth_oidc_account entries are subtypes of auth_account and represent an oidc account.';
-- auth_oidc_method column triggers
create trigger
insert_auth_method_subtype
before insert on auth_oidc_method
for each row execute procedure insert_auth_method_subtype();
create trigger
update_time_column
before
update on auth_oidc_method
for each row execute procedure update_time_column();
create trigger
immutable_columns
before
update on auth_oidc_method
for each row execute procedure immutable_columns('public_id', 'scope_id', 'create_time');
create trigger
default_create_time_column
before
insert on auth_oidc_method
for each row execute procedure default_create_time();
create trigger
update_version_column
after update on auth_oidc_method
for each row execute procedure update_version_column();
-- active_auth_oidc_method_must_be_complete() defines a function to be used in
-- a "before update" trigger for auth_oidc_method entries. Its intent: prevent
-- incomplete oidc methods from transitioning out of the "inactive" state.
create or replace function
active_auth_oidc_method_must_be_complete()
returns trigger
as $$
begin
-- validate signing alg
if old.state = 'inactive' and new.state != 'inactive' then
perform
from
auth_oidc_method am
join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id
where
new.public_id = am.public_id;
if not found then
raise exception 'an incomplete oidc auth method must remain inactive';
end if;
-- validate issuer
case
when new.issuer != old.issuer then
if length(trim(new.issuer)) = 0 then
raise exception 'empty issuer: an incomplete oidc auth method must remain inactive';
end if;
when new.issuer = old.issuer then
if length(trim(old.issuer)) = 0 then
raise exception 'empty issuer: an incomplete oidc auth method must remain inactive';
end if;
else
end case;
-- validate client_id
case
when new.client_id != old.client_id then
if length(trim(new.client_id)) = 0 then
raise exception 'empty client_id: an incomplete oidc auth method must remain inactive';
end if;
when new.client_id = old.client_id then
if length(trim(old.client_id)) = 0 then
raise exception 'empty client_id: an incomplete oidc auth method must remain inactive';
end if;
else
end case;
-- validate client_secret
case
when new.client_secret != old.client_secret then
if length(new.client_secret) = 0 then
raise exception 'empty client_secret: an incomplete oidc auth method must remain inactive';
end if;
when new.client_secret = old.client_secret then
if length(old.client_secret) = 0 then
raise exception 'empty client_secret: an incomplete oidc auth method must remain inactive';
end if;
else
end case;
end if;
return new;
end;
$$ language plpgsql;
comment on function active_auth_oidc_method_must_be_complete() is
'active_auth_oidc_method_must_be_complete() will raise an error if the oidc auth method is not complete';
create trigger
update_active_auth_oidc_method_must_be_complete
before
update on auth_oidc_method
for each row execute procedure active_auth_oidc_method_must_be_complete();
-- new_auth_oidc_method_must_be_inactive() defines a function to be used in
-- a "before insert" trigger for auth_oidc_method entries. Its intent:
-- only allow "inactive" auth methods to be inserted. Why? there's no way
-- you can insert an entry that's anything but incomplete, since we have a
-- chicken/egg problem: you need the auth method id to create the required
-- signing algs value objects.
create or replace function
new_auth_oidc_method_must_be_inactive()
returns trigger
as $$
begin
if new.state != 'inactive' then
raise exception 'an incomplete oidc method must be inactive';
end if;
end;
$$ language plpgsql;
comment on function new_auth_oidc_method_must_be_inactive() is
'new_auth_oidc_method_must_be_inactive ensures that new incomplete oidc auth methods must remain inactive';
create trigger
new_auth_oidc_method_must_be_inactive
before
insert on auth_oidc_method
for each row execute procedure active_auth_oidc_method_must_be_complete();
-- auth_oidc_account column triggers
create trigger
update_time_column
before
update on auth_oidc_account
for each row execute procedure update_time_column();
create trigger
immutable_columns
before
update on auth_oidc_account
for each row execute procedure immutable_columns('public_id', 'auth_method_id', 'scope_id', 'create_time', 'issuer', 'subject');
create trigger
default_create_time_column
before
insert on auth_oidc_account
for each row execute procedure default_create_time();
create trigger
update_version_column
after update on auth_oidc_account
for each row execute procedure update_version_column();
-- insert_auth_oidc_account_subtype is intended as a before insert
-- trigger on auth_oidc_account. Its purpose is to insert a base
-- auth_account for new oidc accounts. It's a bit different than the
-- standard trigger for this, because it will have conflicting PKs
-- and we just want to "do nothing" on those conflicts, deferring the
-- raising on an error to insert into the auth_oidc_account table.
-- this is all necessary because of we're using predictable public ids
-- for oidc accounts.
create or replace function
insert_auth_oidc_account_subtype()
returns trigger
as $$
begin
select auth_method.scope_id
into new.scope_id
from auth_method
where auth_method.public_id = new.auth_method_id;
insert into auth_account
(public_id, auth_method_id, scope_id)
values
(new.public_id, new.auth_method_id, new.scope_id)
on conflict do nothing;
return new;
end;
$$ language plpgsql;
create trigger
insert_auth_oidc_account_subtype
before insert on auth_oidc_account
for each row execute procedure insert_auth_oidc_account_subtype();
-- triggers for auth_oidc_method children tables: auth_oidc_aud_claim,
-- auth_oidc_certificate, auth_oidc_signing_alg
-- on_delete_active_auth_oidc_method_must_be_complete() defines a function
-- to be used in an "after delete" trigger for auth_oidc_signing_alg
-- Its intent: prevent deletes that would result in an "active" oidc
-- auth method which is incomplete.
create or replace function
on_delete_active_auth_oidc_method_must_be_complete()
returns trigger
as $$
declare am_state text;
declare alg_cnt int;
begin
select
am.state,
count(alg.oidc_method_id) as alg_cnt
from
auth_oidc_method am
left outer join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id
where
new.oidc_method_id = am.public_id
group by am.public_id
into am_state, alg_cnt;
if not found then
return new; -- auth method was deleted, so we're done
end if;
if am_state != inactive then
case
when alg_cnt = 0 then
raise exception 'delete would have resulted in an incomplete active oidc auth method with no signing algorithms';
end case;
end if;
return new;
end;
$$ language plpgsql;
comment on function on_delete_active_auth_oidc_method_must_be_complete() is
'on_delete_active_auth_oidc_method_must_be_complete() will raise an error if the oidc auth method is not complete after a delete on algs';
create trigger
default_create_time_column
before
insert on auth_oidc_aud_claim
for each row execute procedure default_create_time();
create trigger
default_create_time_column
before
insert on auth_oidc_certificate
for each row execute procedure default_create_time();
create trigger
default_create_time_column
before
insert on auth_oidc_signing_alg
for each row execute procedure default_create_time();
create trigger
on_delete_active_auth_oidc_method_must_be_complete
after
delete on auth_oidc_signing_alg
for each row execute procedure on_delete_active_auth_oidc_method_must_be_complete();
insert into oplog_ticket (name, version)
values
('auth_oidc_method', 1), -- auth method is the root aggregate itself and all of its value objects.
('auth_oidc_account', 1);
commit;