Skip to content

feat(rbac): allow tokens to create org invitations and storage backends#2710

Merged
jiparis merged 18 commits intochainloop-dev:mainfrom
jiparis:PFM-4146
Feb 6, 2026
Merged

feat(rbac): allow tokens to create org invitations and storage backends#2710
jiparis merged 18 commits intochainloop-dev:mainfrom
jiparis:PFM-4146

Conversation

@jiparis
Copy link
Member

@jiparis jiparis commented Feb 5, 2026

For easy onboarding, this PR includes the RBAC logic to allow Tokens with proper permissions to create organization invitations (initially only InstanceAdminRole), and list and create storage backends.

Also, for InstanceAdmin users, they will be allowed to invite users to orgs they don't belong to.

Some tests:

Organization invitations

With Instance admin token: 🟢

cldev org member invitation create --receiver foo@acme.corp --role member --org membership
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
┌──────────────────────────────────────┬────────────────┬────────┬─────────┬─────────────────────┐
│ ID                                   │ RECEIVER EMAIL │ ROLE   │ STATUS  │ CREATED AT          │
├──────────────────────────────────────┼────────────────┼────────┼─────────┼─────────────────────┤
│ 7a2e12e2-4fa6-4c09-879d-78e92d868d4f │ foo@acme.corp  │ member │ pending │ 05 Feb 26 10:04 UTC │
└──────────────────────────────────────┴────────────────┴────────┴─────────┴─────────────────────┘

Then authenticate with the target user:

✗ cldev org ls
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
WRN Both user credentials and $CHAINLOOP_TOKEN set. Ignoring $CHAINLOOP_TOKEN.
┌────────────┬─────────┬─────────┬────────┬─────────────────────────┬─────────────────────┐
│ NAME       │ CURRENT │ DEFAULT │ ROLE   │ DEFAULT POLICY STRATEGY │ JOINED AT           │
├────────────┼─────────┼─────────┼────────┼─────────────────────────┼─────────────────────┤
│ membership │ true    │ false   │ member │ ADVISORY                │ 05 Feb 26 10:06 UTC │
└────────────┴─────────┴─────────┴────────┴─────────────────────────┴─────────────────────┘

With Instance admin user: 🟢

✗ cldev org member invitation create --receiver bar@acme.corp --role member --org membership
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
┌──────────────────────────────────────┬────────────────┬────────┬─────────┬─────────────────────┐
│ ID                                   │ RECEIVER EMAIL │ ROLE   │ STATUS  │ CREATED AT          │
├──────────────────────────────────────┼────────────────┼────────┼─────────┼─────────────────────┤
│ ab2751f8-51ab-47a6-b8d3-4764d0d25812 │ bar@acme.corp  │ member │ pending │ 05 Feb 26 10:59 UTC │
└──────────────────────────────────────┴────────────────┴────────┴─────────┴─────────────────────┘

With regular token: 🔴

✗ cldev org member invitation create --receiver foo@acme.corp --role member
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
ERR operation not allowed
exit status 1

With regular user (Org Member): 🔴

✗ cldev org member invitation create --receiver nah@acme.corp --role member --org membership
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
ERR operation not allowed
exit status 1

Same user trying to invite someone to an org he doesn't belong to (but exists):

✗ cldev org member invitation create --receiver user4@acme.corp --role member --org org4
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
ERR org not found

With regular user (org admin): 🟢

✗ cldev org member invitation create --receiver user3@acme.corp --role member --org membership
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
┌──────────────────────────────────────┬─────────────────┬────────┬─────────┬─────────────────────┐
│ ID                                   │ RECEIVER EMAIL  │ ROLE   │ STATUS  │ CREATED AT          │
├──────────────────────────────────────┼─────────────────┼────────┼─────────┼─────────────────────┤
│ 754b217f-e3ff-4ae7-9dcd-b68c54db8cc1 │ user3@acme.corp │ member │ pending │ 05 Feb 26 11:06 UTC │
└──────────────────────────────────────┴─────────────────┴────────┴─────────┴─────────────────────┘

Storage backends

Regular user (Org Owner): 🟢

✗ cldev cas-backend add aws-s3 --access-key-id xxx --secret-access-key xxx --bucket chainloop --endpoint http://localhost:9002 --name minio2001 --default
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
You are changing the default CAS backend in your organization
Please confirm to continue y/N
y
┌───────────┬─────────────────────────────────┬──────────┬─────────────┬───────────────┬─────────┬──────────┬────────┐
│ NAME      │ LOCATION                        │ PROVIDER │ DESCRIPTION │ LIMITS        │ DEFAULT │ FALLBACK │ STATUS │
├───────────┼─────────────────────────────────┼──────────┼─────────────┼───────────────┼─────────┼──────────┼────────┤
│ minio2001 │ http://localhost:9002/chainloop │ AWS-S3   │             │ MaxSize: 300M │ true    │ false    │ valid  │
└───────────┴─────────────────────────────────┴──────────┴─────────────┴───────────────┴─────────┴──────────┴────────┘

Regular user (Org Viewer): 🔴

✗ cldev cas-backend add aws-s3 --access-key-id xxx --secret-access-key xxx --bucket chainloop --endpoint http://localhost:9002 --name minio2001 --default
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
You are changing the default CAS backend in your organization
Please confirm to continue y/N
y
ERR operation not allowed

Regular user (Instance admin, not member of the target org): 🟢

cldev cas-backend ls --org storage-tests
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
┌────────────────┬─────────────────────────────────┬──────────┬─────────────────────────────────────┬───────────────┬─────────┬──────────┬────────┐
│ NAME           │ LOCATION                        │ PROVIDER │ DESCRIPTION                         │ LIMITS        │ DEFAULT │ FALLBACK │ STATUS │
├────────────────┼─────────────────────────────────┼──────────┼─────────────────────────────────────┼───────────────┼─────────┼──────────┼────────┤
│ minio2001      │ http://localhost:9002/chainloop │ AWS-S3   │                                     │ MaxSize: 300M │ true    │ false    │ valid  │
├────────────────┼─────────────────────────────────┼──────────┼─────────────────────────────────────┼───────────────┼─────────┼──────────┼────────┤
│ default-inline │                                 │ INLINE   │ Embed artifacts content in the atte │ MaxSize: 500K │ false   │ false    │ valid  │
│                │                                 │          │ station (fallback)                  │               │         │          │        │
└────────────────┴─────────────────────────────────┴──────────┴─────────────────────────────────────┴───────────────┴─────────┴──────────┴────────┘

✗ cldev cas-backend add aws-s3 --access-key-id xxx --secret-access-key xxx --bucket chainloop --endpoint http://localhost:9002 --name minio2002 --default --org storage-tests
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
You are changing the default CAS backend in your organization
Please confirm to continue y/N
y
┌───────────┬─────────────────────────────────┬──────────┬─────────────┬───────────────┬─────────┬──────────┬────────┐
│ NAME      │ LOCATION                        │ PROVIDER │ DESCRIPTION │ LIMITS        │ DEFAULT │ FALLBACK │ STATUS │
├───────────┼─────────────────────────────────┼──────────┼─────────────┼───────────────┼─────────┼──────────┼────────┤
│ minio2002 │ http://localhost:9002/chainloop │ AWS-S3   │             │ MaxSize: 300M │ true    │ false    │ valid  │
└───────────┴─────────────────────────────────┴──────────┴─────────────┴───────────────┴─────────┴──────────┴────────┘

Token (instance admin)
Without org: ⚠️

cldev cas-backend ls
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
ERR current organization not set
exit status 1

With org: 🟢

cldev cas-backend add aws-s3 --access-key-id xxx --secret-access-key xxx --bucket chainloop --endpoint http://localhost:9002 --name minio2001 --default --org storage-tests
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
You are changing the default CAS backend in your organization
Please confirm to continue y/N
y
┌───────────┬─────────────────────────────────┬──────────┬─────────────┬───────────────┬─────────┬──────────┬────────┐
│ NAME      │ LOCATION                        │ PROVIDER │ DESCRIPTION │ LIMITS        │ DEFAULT │ FALLBACK │ STATUS │
├───────────┼─────────────────────────────────┼──────────┼─────────────┼───────────────┼─────────┼──────────┼────────┤
│ minio2001 │ http://localhost:9002/chainloop │ AWS-S3   │             │ MaxSize: 300M │ true    │ false    │ valid  │
└───────────┴─────────────────────────────────┴──────────┴─────────────┴───────────────┴─────────┴──────────┴────────┘
✗ cldev cas-backend ls --org storage-tests
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
┌────────────────┬─────────────────────────────────┬──────────┬─────────────────────────────────────┬───────────────┬─────────┬──────────┬────────┐
│ NAME           │ LOCATION                        │ PROVIDER │ DESCRIPTION                         │ LIMITS        │ DEFAULT │ FALLBACK │ STATUS │
├────────────────┼─────────────────────────────────┼──────────┼─────────────────────────────────────┼───────────────┼─────────┼──────────┼────────┤
│ minio2001      │ http://localhost:9002/chainloop │ AWS-S3   │                                     │ MaxSize: 300M │ true    │ false    │ valid  │
├────────────────┼─────────────────────────────────┼──────────┼─────────────────────────────────────┼───────────────┼─────────┼──────────┼────────┤
│ default-inline │                                 │ INLINE   │ Embed artifacts content in the atte │ MaxSize: 500K │ false   │ false    │ valid  │
│                │                                 │          │ station (fallback)                  │               │         │          │        │
└────────────────┴─────────────────────────────────┴──────────┴─────────────────────────────────────┴───────────────┴─────────┴──────────┴────────┘

Regular token: 🔴

✗ cldev cas-backend ls
DBG using config file path="/Users/jiparis/Library/Application Support/chainloop/config.devel.toml"
WRN API contacted in insecure mode
ERR operation not allowed

Refs #2704

Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
@jiparis jiparis marked this pull request as ready for review February 5, 2026 11:13
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
// 2.c - Set its user
usercontext.WithCurrentUserMiddleware(opts.UserUseCase, logHelper),
// Store all memberships in the context
usercontext.WithCurrentMembershipsMiddleware(opts.MembershipUseCase),
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 just moved this earlier in the chain, so that membership is available to the middlewares in the next selector.

Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
@jiparis jiparis changed the title feat(rbac): allow tokens to create org invitations feat(rbac): allow tokens to create org invitations and storage backends Feb 5, 2026
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
}

// 3 - Check that the user is a member of the given org
// NOTE: this check is not necessary, as the user is already a member of the org
Copy link
Member Author

Choose a reason for hiding this comment

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

As the comment states, this is not needed since validation is done in the middleware layer already.

migmartri
migmartri previously approved these changes Feb 5, 2026
Copy link
Member

@migmartri migmartri left a comment

Choose a reason for hiding this comment

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

Thanks

@@ -0,0 +1,2 @@
-- Modify "org_invitations" table
ALTER TABLE "org_invitations" ALTER COLUMN "sender_id" DROP NOT NULL;
Copy link
Member

Choose a reason for hiding this comment

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

why is this?

Copy link
Member Author

Choose a reason for hiding this comment

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

The sender is now optional (instance tokens are anonymous)

Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
@Piskoo
Copy link
Collaborator

Piskoo commented Feb 6, 2026

How does the mail look like when instance admin token is used? I think the header might be off
Screenshot from 2026-02-06 11-54-05

@jiparis jiparis merged commit 3ff1709 into chainloop-dev:main Feb 6, 2026
13 checks passed
@jiparis jiparis deleted the PFM-4146 branch February 6, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants