mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(schemas): use restrictive policies
for enhanced security and scalability. restrictive policies can be combined with permissive policies for advanced use cases.
This commit is contained in:
parent
1f0bc8f3f4
commit
bdd7374d31
4 changed files with 122 additions and 3 deletions
|
@ -74,6 +74,35 @@ const queryDatabaseManifest = async (database) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const { rows: triggers } = await pool.query(/* sql */`select * from information_schema.triggers;`);
|
const { rows: triggers } = await pool.query(/* sql */`select * from information_schema.triggers;`);
|
||||||
|
const { rows: policies } = await pool.query(/* sql */`select * from pg_policies order by tablename, policyname;`);
|
||||||
|
const { rows: columnGrants } = await pool.query(/* sql */`
|
||||||
|
select * from information_schema.role_column_grants
|
||||||
|
where table_schema = '${schema}'
|
||||||
|
and grantee != 'postgres'
|
||||||
|
order by grantee, table_name, column_name, privilege_type;
|
||||||
|
`);
|
||||||
|
const { rows: tableGrants } = await pool.query(/* sql */`
|
||||||
|
select * from information_schema.role_table_grants
|
||||||
|
where table_schema = '${schema}'
|
||||||
|
and grantee != 'postgres'
|
||||||
|
order by grantee, table_name, privilege_type;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// This function removes the last segment of grantee since Logto will use 'logto_tenant_fresh/alteration' for the role name.
|
||||||
|
const normalizeGrantee = ({ grantee, ...rest }) => {
|
||||||
|
if (grantee.startsWith('logto_tenant_')) {
|
||||||
|
return { ...rest, grantee: 'logto_tenant' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { grantee, ...rest };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ditto.
|
||||||
|
const normalizeRoles = ({ roles: raw, ...rest }) => {
|
||||||
|
const roles = raw.slice(1, -1).split(',').map((name) => name.startsWith('logto_tenant_') ? 'logto_tenant' : name);
|
||||||
|
|
||||||
|
return { roles, ...rest };
|
||||||
|
};
|
||||||
|
|
||||||
// Omit generated ids and values
|
// Omit generated ids and values
|
||||||
return {
|
return {
|
||||||
|
@ -100,6 +129,9 @@ const queryDatabaseManifest = async (database) => {
|
||||||
indexes,
|
indexes,
|
||||||
funcs,
|
funcs,
|
||||||
triggers: omitArray(triggers, 'trigger_catalog', 'event_object_catalog'),
|
triggers: omitArray(triggers, 'trigger_catalog', 'event_object_catalog'),
|
||||||
|
policies: policies.map(normalizeRoles),
|
||||||
|
columnGrants: omitArray(columnGrants, 'table_catalog').map(normalizeGrantee),
|
||||||
|
tableGrants: omitArray(tableGrants, 'table_catalog').map(normalizeGrantee),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import type { CommonQueryMethods } from 'slonik';
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||||
|
|
||||||
|
const tables = [
|
||||||
|
'applications_roles',
|
||||||
|
'applications',
|
||||||
|
'connectors',
|
||||||
|
'custom_phrases',
|
||||||
|
'logs',
|
||||||
|
'logto_configs',
|
||||||
|
'oidc_model_instances',
|
||||||
|
'passcodes',
|
||||||
|
'resources',
|
||||||
|
'roles_scopes',
|
||||||
|
'roles',
|
||||||
|
'scopes',
|
||||||
|
'sign_in_experiences',
|
||||||
|
'users_roles',
|
||||||
|
'users',
|
||||||
|
'verification_statuses',
|
||||||
|
'hooks',
|
||||||
|
];
|
||||||
|
|
||||||
|
const getDatabaseName = async (pool: CommonQueryMethods) => {
|
||||||
|
const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
|
||||||
|
select current_database();
|
||||||
|
`);
|
||||||
|
|
||||||
|
return currentDatabase.replaceAll('-', '_');
|
||||||
|
};
|
||||||
|
|
||||||
|
const alteration: AlterationScript = {
|
||||||
|
up: async (pool) => {
|
||||||
|
await Promise.all(
|
||||||
|
tables.map(async (tableRaw) => {
|
||||||
|
const table = sql.identifier([tableRaw]);
|
||||||
|
const tenantIdPolicy = sql.identifier([`${tableRaw}_tenant_id`]);
|
||||||
|
const modificationPolicy = sql.identifier([`${tableRaw}_modification`]);
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
drop policy ${tenantIdPolicy} on ${table};
|
||||||
|
create policy ${tenantIdPolicy} on ${table}
|
||||||
|
as restrictive
|
||||||
|
using (tenant_id = (select id from tenants where db_user = current_user));
|
||||||
|
create policy ${modificationPolicy} on ${table}
|
||||||
|
using (true);
|
||||||
|
`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await pool.query(sql`
|
||||||
|
drop policy tenants_tenant_id on tenants;
|
||||||
|
create policy tenants_tenant_id on tenants
|
||||||
|
using (db_user = current_user);
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
const role = sql.identifier([`logto_tenant_${await getDatabaseName(pool)}`]);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
tables.map(async (tableRaw) => {
|
||||||
|
const table = sql.identifier([tableRaw]);
|
||||||
|
const tenantIdPolicy = sql.identifier([`${tableRaw}_tenant_id`]);
|
||||||
|
const modificationPolicy = sql.identifier([`${tableRaw}_modification`]);
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
drop policy ${tenantIdPolicy} on ${table};
|
||||||
|
drop policy ${modificationPolicy} on ${table};
|
||||||
|
create policy ${tenantIdPolicy} on ${table}
|
||||||
|
to ${role}
|
||||||
|
using (tenant_id = (select id from tenants where db_user = current_user));
|
||||||
|
`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await pool.query(sql`
|
||||||
|
drop policy tenants_tenant_id on tenants;
|
||||||
|
create policy tenants_tenant_id on tenants
|
||||||
|
to ${role}
|
||||||
|
using (db_user = current_user);
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
|
@ -11,7 +11,7 @@ revoke all privileges
|
||||||
on table tenants
|
on table tenants
|
||||||
from logto_tenant_${database};
|
from logto_tenant_${database};
|
||||||
|
|
||||||
/* Allow limited select to perform RLS query in `after_each` (using select ... from tenants ...) */
|
/* Allow limited select to perform the RLS policy query in `after_each` (using select ... from tenants ...) */
|
||||||
grant select (id, db_user)
|
grant select (id, db_user)
|
||||||
on table tenants
|
on table tenants
|
||||||
to logto_tenant_${database};
|
to logto_tenant_${database};
|
||||||
|
@ -20,7 +20,6 @@ alter table tenants enable row level security;
|
||||||
|
|
||||||
/* Create RLS policy to minimize the privilege */
|
/* Create RLS policy to minimize the privilege */
|
||||||
create policy tenants_tenant_id on tenants
|
create policy tenants_tenant_id on tenants
|
||||||
to logto_tenant_${database}
|
|
||||||
using (db_user = current_user);
|
using (db_user = current_user);
|
||||||
|
|
||||||
-- End --
|
-- End --
|
||||||
|
|
|
@ -6,5 +6,8 @@ create trigger set_tenant_id before insert on ${name}
|
||||||
alter table ${name} enable row level security;
|
alter table ${name} enable row level security;
|
||||||
|
|
||||||
create policy ${name}_tenant_id on ${name}
|
create policy ${name}_tenant_id on ${name}
|
||||||
to logto_tenant_${database}
|
as restrictive
|
||||||
using (tenant_id = (select id from tenants where db_user = current_user));
|
using (tenant_id = (select id from tenants where db_user = current_user));
|
||||||
|
|
||||||
|
create policy ${name}_modification on ${name}
|
||||||
|
using (true);
|
||||||
|
|
Loading…
Reference in a new issue