Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Define new grants tables #5486

Draft
wants to merge 15 commits into
base: llb-normalized-grants
Choose a base branch
from

Conversation

elimt
Copy link
Member

@elimt elimt commented Jan 28, 2025

Create new tables for grants:

  1. iam_role_global:
    Roles that are placed in the global scope will be persisted in the iam_role_global table. A global role has a grant_scope which must be one of:
  • descendants
  • children
  • individual

This enforces that a global role's grants either apply to:

  • All orgs and projects.
  • All orgs.
  • An individual set of orgs and/or projects.

When the grant_scope is set to individual, entries for the specific set of orgs and/or projects can be added to the
iam_role_global_individual_grant_scope table.

Separately, a global role can be set to also apply its grant to the global scope by setting grant_this_role_scope to true.

  1. iam_role_org:
    Roles that are placed in an org scope will be persisted in the iam_role_org table. An org role has a grant_scope which must be one of:
  • children
  • individual

This enforces that an org role's grants either apply to:

  • All projects in the org.
  • An individual set of projects in the org.

When the grant_scope is set to individual, entries for the specific set of projects can be added to the
iam_role_org_individual_grant_scope table.
NOTE: The projects must belong to the org's scope

Separately, an org role can be set to also apply its grant to the org by setting grant_this_role_scope to true.

  1. resource_enm:
    Contains all boundary resources. This is used by iam_grant to set the resource from a canonical_grant.

  2. iam_grant
    Stores the canonical grant string and the resource for filtering on specific grants.

@elimt elimt added the pr/no-milestone Ignores the Milestone Check label Jan 28, 2025
@elimt elimt requested review from mgaffney and tmessi January 28, 2025 15:21
Comment on lines 130 to 131
create trigger immutable_columns before update on iam_role_global_individual_grant_scope
for each row execute procedure immutable_columns('scope_id', 'create_time');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think every column in this table is immutable, not sure if we need constraints for all of them though 🤔

for each row execute procedure default_create_time();

-- ensure the project's parent is the role's scope
create or replace function ensure_project_belongs_to_org() returns trigger
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick:

Suggested change
create or replace function ensure_project_belongs_to_org() returns trigger
create or replace function ensure_project_belongs_to_role_org() returns trigger

elimt and others added 2 commits January 28, 2025 16:21
@elimt elimt force-pushed the bosorawis-sql-new-grants-tables-and-objects branch from e3afe7b to 6f0ae35 Compare January 28, 2025 21:24
as $$
begin
if new.grant_this_role_scope is distinct from old.grant_this_role_scope then
new.grant_scope_update_time = now();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be grant_this_role_scope_update_time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it should. I'll fix that

Comment on lines 58 to 60
-- This is only applicable to the name and description columns because we
-- do not want the update_time to be updated when the grant_scope or grant_this_role_scope
-- columns are updated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure about this? I thought we wanted the update_time to be updated no matter what changed. If we do what it says here, then the update_time column would behave differently from all of our other update_time columns. @tmessi do you have any thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, the update_time should be updated when anything in the role aggregate changes, including any changes to the grant scopes.

It would probably be sufficient to use the existing update_time_column trigger function.

I would also note that the way this works currently is that in the iam repository functions that modify the nested structure,
like AddRoleGrants, we explicitly increment the version column of the role in the same transaction that we make the changes to the grant scopes. This along with the database trigger ensures that the update_time changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay because we are tracking different update times, I thought we wanted to treat them differently. I can remove this new function for specially handling this.

So with every update to a role subtype, we will update the update_time and the version

references iam_role(public_id)
on delete cascade
on update cascade,
scope_id wt_scope_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can the scope_id be null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should never be null. I'll update the column to scope_id wt_scope_id not null

name text,
description text,
grant_this_role_scope boolean not null default false,
grant_scope text
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can the grant_scope be null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grant_scope can be null when the grant scope uses the special keyword this. The grant_this_role_scope will be set to true and the grant_scope column will be null

Comment on lines 9 to 16
create table iam_grant (
canonical_grant text not null primary key,
resource text not null
constraint resource_enm_fkey
references resource_enm(string)
on delete restrict
on update cascade
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the iam_role_grant table have a foreign key to this table?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there should be one on the iam_role_grant table to ensure canonical_grant can't get updated on only iam_role_grant table. I'll add that

Comment on lines -20 to -41
tests/org/*.sql \
tests/wh/*/*.sql \
tests/sentinel/*.sql \
tests/credential/*/*.sql \
tests/session/*.sql \
tests/account/*/*.sql \
tests/target/*.sql \
tests/controller/*.sql \
tests/hcp/*/*.sql \
tests/alias/*.sql \
tests/auth/*/*.sql \
tests/census/*.sql \
tests/kms/*.sql \
tests/storage/*.sql \
tests/controller/*.sql \
tests/credential/*/*.sql \
tests/domain/*.sql \
tests/hcp/*/*.sql \
tests/history/*.sql \
tests/recording/*.sql \
tests/alias/*.sql \
tests/auth/*/*.sql \
tests/purge/*.sql \
tests/host/*.sql \
tests/iam/*.sql \
tests/kms/*.sql \
tests/org/*.sql \
tests/pagination/*.sql \
tests/policy/*.sql \
tests/host/*.sql \
tests/server/*.sql
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change the order of the existing tests?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to match the alphabetical order - similar to how these directories show up in the IDEs to try to make it easier to glance if any directory was missed.

Comment on lines 1 to 2
-- Copyright (c) HashiCorp, Inc.
-- SPDX-License-Identifier: BUSL-1.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this (almost) empty file?

begin;

create table resource_enm (
string text not null primary key
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think string is a poor column name since it is a primitive type in many languages and it doesn't convey what the values of the column represent.
  2. Shouldn't there be a constraint on this column since this is an enum table?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I updated the column to name so anyone looking at the table knows it's the resource name
  2. Yes there should be. I'll add a constraint for valid resources


create trigger update_iam_role_table_update_time before update on iam_role_global
for each row execute procedure update_iam_role_table_update_time();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need a trigger to update the version column:

  create trigger update_version_column after update on iam_role_global
    for each row execute procedure update_version_column();

You will also need this for iam_role_org and iam_role_project.

Comment on lines 111 to 134
select scope_id
into org_scope_id
from iam_role_org
where public_id = new.role_id;

if org_scope_id is null then
raise exception 'role % not found in iam_role_org', new.role_id;
end if;

-- Find the project parent for the inserted scope_id
select parent_id
into project_parent_id
from iam_scope_project
where scope_id = new.scope_id;

if project_parent_id is null then
raise exception 'project scope_id % not found in iam_scope_project', new.scope_id;
end if;

-- Compare parent_id with the org scope
if project_parent_id != org_scope_id then
raise exception 'project % belongs to a different org',
new.scope_id;
end if;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can do this with a single query. We wouldn't be able to have as many detailed error messages, but some of these cases should not be possible given other constraints on tables.
So you could do something like:

perform
   from iam_scope_project
   join iam_role_org
     on iam_role_org.scope_id = iam_scope_project.parent_id
  where iam_scope_project.scope_id = new.scope_id
    and iam_role_org.public_id     = new.role_id;
if not found then
  raise exception 'project scope_id % not found in org', new.scope_id;
end if;

Comment on lines 7 to 11
-- Set up a scope to test against
insert into iam_scope
(type, public_id, parent_id)
values
('org', 'o_1111111111', 'global');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would take a look at the files in various *_persona.sql files in initdb.d. For the sqltests we tend to use the these for test setup. Each persona represents a different org. They can be used in different ways, depending on what is needed:

  • Generally the widgets persona is preferred. The resources from this persona do no get loaded when the database is initialized, instead they can be created as part of an individual test setup by calling wtt_load with the necessary aggregates.
  • Each test takes place in a single transaction, so that it can be rolled back. This can make some test scenarios difficult when they require multiple transactions especially with things like changes to timestamps. In these cases you can use either the colors or food truck personas which have their resources created during the database initialization phase.

I think in this case we could just used the widgets persona by calling:

wtt_load('widgets', 'iam');

If we need to only create some resources form iam, I would suggest we split up the existing _wtt_load_widgets_iam function into multiple. We could add new functions like _wtt_load_widgets_iam_scope, _wtt_load_widgets_user, _wtt_load_widgets_group, _wtt_load_widgets_grant, etc. And have _wtt_load_widgets_iam call each of these functions.
Then for these tests we could call:

wtt_load('widgets', 'iam_scope');

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to using the personas by calling

wtt_load('widgets', 'iam');

I have not found a need to load only specific parts of iam widget yet. Once necessary, I can split it up

Comment on lines 71 to 72
-- 2a) clean up any previous data
delete from iam_role;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually do this within the sqltests. I see a few spots where it seems like we started doing this, but instead I would recommend either splitting these tests out to separate files so they can run in separate transactions, or if you want keep it all in this one test file, you can use a new public_id for the subsequent tests.

Comment on lines 13 to 14
-- remove any existing roles and their grant scopes, so we can test fresh
delete from iam_role;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be necessary, we should be able to make these assertions even with the roles from the other personas in place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took that out. I switched to using the personas

- alter iam_role_grant to add constraint on canonical_grant
- add comments to all functions and tables
- remove function for only setting `update_time` on iam_role subtypes when only the name and description have been updated
- add new trigger for setting update_time when iam_role subtypes are updated
- add new trigger for updating the version when iam_role subtypes are updated
- update resource_enm table column name from `string` to `name`
- add constraint to resource_enm table for `name` column
elimt added 2 commits January 31, 2025 21:37
- update update_time column on iam_role_org
-  update syntax error on `ensure_project_belongs_to_role_org` function
- adding missing subtype trigger on iam_role_project
- fix column insert on `resource_enm`
@elimt elimt requested review from mgaffney and tmessi February 1, 2025 03:30
@elimt elimt changed the title WIP: SQL Define new grants tables feat: Define new grants tables Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants