From 91dd070fa8a24df1886d59eee6d484be4647c9e3 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 11 Jul 2025 08:54:41 -0600 Subject: feat: import project policies --- docs/gitlab_cedar_design.md | 321 +++++++++++++++++++++ .../gitlab.com/gitlab-org/gitlab/entities.json | 190 ++++++++---- .../authorization/authzd/entities.json | 138 +++++++-- .../authorization/sparkled/entities.json | 138 +++++++-- etc/authzd/gitlab_access.cedar | 127 ++++++++ etc/authzd/gitlab_schema.cedarschema | 158 ++++++++++ etc/authzd/gitlab_simple.cedar | 144 +++++++++ etc/authzd/gitlab_visibility.cedar | 127 ++++++++ etc/authzd/test_simple.cedar | 6 + share/misc/entities_example.json | 58 ++++ src/authorization/entities.rs | 21 +- src/authorization/gitlab_entities.rs | 208 +++++++++++++ src/gitlab/mod.rs | 2 + src/gitlab/project.rs | 3 + src/gitlab/user.rs | 13 + 15 files changed, 1553 insertions(+), 101 deletions(-) create mode 100644 docs/gitlab_cedar_design.md create mode 100644 etc/authzd/gitlab_access.cedar create mode 100644 etc/authzd/gitlab_schema.cedarschema create mode 100644 etc/authzd/gitlab_simple.cedar create mode 100644 etc/authzd/gitlab_visibility.cedar create mode 100644 etc/authzd/test_simple.cedar create mode 100644 share/misc/entities_example.json create mode 100644 src/authorization/gitlab_entities.rs create mode 100644 src/gitlab/user.rs diff --git a/docs/gitlab_cedar_design.md b/docs/gitlab_cedar_design.md new file mode 100644 index 00000000..353b7e53 --- /dev/null +++ b/docs/gitlab_cedar_design.md @@ -0,0 +1,321 @@ +# GitLab Cedar Authorization Design (Simplified) + +This document describes the simplified design for implementing GitLab-style authorization using Cedar policies in the `authzd` service, without feature or license checks. + +## Overview + +The `authzd` service implements a Policy Decision Point (PDP) that authorizes GitLab REST API operations using Cedar policies based purely on user access levels and project visibility. + +## Architecture + +### Entity Model + +The Cedar entity model mirrors GitLab's core entities: + +- **User**: GitLab users with access levels, admin status, and group memberships +- **Project**: GitLab projects with visibility settings, feature flags, and member lists +- **Namespace/Group**: GitLab groups/namespaces with hierarchical relationships +- **ProjectMembership**: Explicit user-project relationships with access levels +- **GroupMembership**: User-group relationships with inheritance +- **Issue/MergeRequest**: Project resources with ownership and state + +### Access Levels + +GitLab's hierarchical access system is implemented using numeric levels: + +- **Guest** (10): Read-only access to allowed resources +- **Reporter** (20): Can create issues, view CI/CD results +- **Developer** (30): Can push code, create merge requests +- **Maintainer** (40): Can manage project settings and members +- **Owner** (50): Full project control including deletion + +### Policy Structure + +Cedar policies are organized into separate files by concern: + +#### 1. Access Level Policies (`gitlab_simple.cedar`) +- Implements role-based authorization using GitLab's access levels +- Grants permissions based on user access level to projects/groups +- Includes admin override capabilities +- Blocks access for blocked users +- No feature or license checks + +#### 2. Visibility Policies (`gitlab_visibility.cedar`) +- Implements GitLab's public/internal/private visibility model +- Allows public access to public projects +- Restricts internal projects to non-external users +- Limits private projects to members only +- Handles archived project restrictions + +## REST API Integration + +### Request Mapping + +REST API requests are mapped to Cedar authorization checks: + +``` +HTTP Request Cedar Authorization +GET /projects/1 -> can?(User, "read_project", Project::1) +POST /projects/1/issues -> can?(User, "create_issue", Project::1) +PUT /projects/1/settings -> can?(User, "admin_project", Project::1) +DELETE /projects/1 -> can?(User, "destroy_project", Project::1) +``` + +### Context Variables + +HTTP request context is passed to Cedar policies: + +- `method`: HTTP method (GET, POST, PUT, DELETE) +- `path`: Request path +- `host`: Request hostname +- `user_agent`: Client user agent +- `ip_address`: Client IP address + +### Authorization Flow + +1. **Extract Principal**: Identify user from JWT token or API key +2. **Determine Resource**: Extract project/group ID from URL path +3. **Map Action**: Convert HTTP method + path to Cedar action +4. **Build Request**: Create Cedar authorization request +5. **Evaluate**: Execute policies against request +6. **Decision**: Return allow/deny based on policy evaluation + +## Entity Generation + +### GitLab API Integration + +The CLI tool generates Cedar entities by fetching data from GitLab's REST API: + +```rust +// Generate entities for a project +let entities = EntitiesRepository::new(api) + .all("gitlab-org/gitlab") + .await?; +``` + +### Entity Structure + +Entities are generated with simplified attributes for policy evaluation: + +```json +{ + "uid": {"type": "Project", "id": "123"}, + "attrs": { + "name": "GitLab", + "path": "gitlab", + "full_path": "gitlab-org/gitlab", + "visibility": "public", + "archived": false, + "members": ["User::\"1\"", "User::\"2\"", "User::\"3\""] + }, + "parents": [{"type": "Group", "id": "456"}] +} +``` + +User entities include access level and status flags: + +```json +{ + "uid": {"type": "User", "id": "1"}, + "attrs": { + "username": "alice", + "access_level": 40, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] +} +``` + +## Implementation Components + +### 1. Enhanced GitLab Types + +Updated GitLab API types include all fields needed for authorization: + +- **Project**: visibility, features, access levels, archived status +- **User**: admin, blocked, external, bot flags +- **Group**: visibility, hierarchy, member access levels + +### 2. Cedar Entity Builders + +The `GitLabEntityBuilder` converts GitLab API responses to Cedar entities: + +```rust +impl GitLabEntityBuilder { + pub fn build_project(project: &Project) -> CedarEntity { ... } + pub fn build_user(user: &User) -> CedarEntity { ... } + pub fn build_membership(user_id: u64, project_id: u64, member: &Member) -> CedarEntity { ... } +} +``` + +### 3. Authorization Service + +The `CedarAuthorizer` evaluates requests against policies: + +```rust +pub fn authorize(&self, principal: &str, action: &str, resource: &str, context: &Context) -> Result { + let request = Request::new(principal, action, resource, context); + self.authorizer.is_authorized(&request, &self.policies, &self.entities) +} +``` + +## gRPC Service Design + +### Authorization Service + +Implement gRPC endpoints that mirror GitLab's `Ability.allowed?` method: + +```protobuf +service GitLabAuthorization { + rpc IsAllowed(IsAllowedRequest) returns (IsAllowedResponse); + rpc BatchIsAllowed(BatchIsAllowedRequest) returns (BatchIsAllowedResponse); +} + +message IsAllowedRequest { + string user_id = 1; + string action = 2; + string resource_type = 3; + string resource_id = 4; + map context = 5; +} + +message IsAllowedResponse { + bool allowed = 1; + string reason = 2; +} +``` + +### Performance Optimizations + +- **Entity Caching**: Cache entities in memory with TTL +- **Policy Compilation**: Pre-compile policies for faster evaluation +- **Batch Operations**: Support bulk authorization checks +- **Connection Pooling**: Pool GitLab API connections + +## Deployment Patterns + +### Standalone PDP + +Deploy `authzd` as a standalone authorization service: + +- Applications make gRPC calls for authorization decisions +- Entities are refreshed periodically from GitLab API +- Policies are updated via configuration management + +### Envoy Integration + +Use as Envoy external authorization service: + +- Intercept HTTP requests at API gateway +- Make real-time authorization decisions +- Block unauthorized requests before reaching application + +### Library Integration + +Embed authorization logic in applications: + +- Link `authzd` as a Rust library +- Perform in-process authorization checks +- Reduce network latency for high-frequency operations + +## Configuration + +### Environment Variables + +```bash +GITLAB_TOKEN=glpat-xxx # GitLab API token +GITLAB_HOST=gitlab.example.com # GitLab instance URL +AUTHZD_PORT=50051 # gRPC service port +ENTITY_REFRESH_INTERVAL=300 # Seconds between entity updates +POLICY_PATH=/etc/authzd # Path to Cedar policy files +``` + +### Policy Management + +- Store policies in version control +- Use GitOps for policy deployment +- Implement policy validation in CI/CD +- Support hot-reloading of policy changes + +## Testing Strategy + +### Unit Tests + +- Test individual policy rules +- Verify entity generation accuracy +- Validate API type parsing + +### Integration Tests + +- Test complete authorization flows +- Verify GitLab API integration +- Test gRPC service endpoints + +### Policy Tests + +- Use Cedar's policy testing framework +- Create test scenarios for common GitLab operations +- Validate policy correctness with real GitLab data + +## Security Considerations + +### Token Management + +- Rotate GitLab API tokens regularly +- Use least-privilege tokens for entity generation +- Secure token storage in production + +### Entity Data + +- Sanitize sensitive data in entities +- Implement data retention policies +- Audit entity access and modifications + +### Policy Security + +- Validate policies before deployment +- Implement policy signing/verification +- Monitor for policy conflicts or errors + +## Migration Strategy + +### Phase 1: Read-Only Operations + +- Implement authorization for GET requests +- Test with non-critical projects +- Validate performance and accuracy + +### Phase 2: Write Operations + +- Add support for POST/PUT/DELETE operations +- Implement fine-grained permissions +- Roll out to production gradually + +### Phase 3: Advanced Features + +- Add support for custom roles +- Implement audit logging +- Add policy analytics and monitoring + +## Monitoring and Observability + +### Metrics + +- Authorization request rate and latency +- Policy evaluation time +- Entity refresh success/failure rates +- Cache hit/miss ratios + +### Logging + +- Log all authorization decisions +- Include request context and policy evaluation details +- Implement structured logging for analysis + +### Alerting + +- Alert on authorization failures +- Monitor entity refresh errors +- Track policy evaluation performance \ No newline at end of file diff --git a/etc/authzd/gitlab.com/gitlab-org/gitlab/entities.json b/etc/authzd/gitlab.com/gitlab-org/gitlab/entities.json index 1992a9c7..f0e61bf3 100644 --- a/etc/authzd/gitlab.com/gitlab-org/gitlab/entities.json +++ b/etc/authzd/gitlab.com/gitlab-org/gitlab/entities.json @@ -7,7 +7,31 @@ "attrs": { "name": "GitLab", "path": "gitlab", - "full_path": "gitlab-org/gitlab" + "full_path": "gitlab-org/gitlab", + "visibility": "public", + "archived": false, + "members": [ + "User::\"1\"", + "User::\"263716\"", + "User::\"2293\"", + "User::\"283999\"", + "User::\"370493\"", + "User::\"138401\"", + "User::\"516904\"", + "User::\"527558\"", + "User::\"215818\"", + "User::\"429540\"", + "User::\"581582\"", + "User::\"626804\"", + "User::\"597578\"", + "User::\"739252\"", + "User::\"201566\"", + "User::\"829774\"", + "User::\"4849\"", + "User::\"790854\"", + "User::\"273486\"", + "User::\"411701\"" + ] }, "parents": [ { @@ -23,7 +47,10 @@ }, "attrs": { "username": "sytses", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -34,7 +61,10 @@ }, "attrs": { "username": "grzesiek", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -45,7 +75,38 @@ }, "attrs": { "username": "brodock", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + }, + { + "uid": { + "type": "User", + "id": "283999" + }, + "attrs": { + "username": "dbalexandre", + "access_level": 40, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + }, + { + "uid": { + "type": "User", + "id": "370493" + }, + "attrs": { + "username": "luke", + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -56,7 +117,24 @@ }, "attrs": { "username": "chriscool", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + }, + { + "uid": { + "type": "User", + "id": "516904" + }, + "attrs": { + "username": "tauriedavis", + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -67,7 +145,10 @@ }, "attrs": { "username": "eliran.mesika", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -78,7 +159,10 @@ }, "attrs": { "username": "tmaczukin", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -89,7 +173,10 @@ }, "attrs": { "username": "ahanselka", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -100,7 +187,10 @@ }, "attrs": { "username": "arihantar", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -111,7 +201,10 @@ }, "attrs": { "username": "pedroms", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -122,7 +215,10 @@ }, "attrs": { "username": "WarheadsSE", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -133,18 +229,10 @@ }, "attrs": { "username": "jdrumtra", - "access_level": 30 - }, - "parents": [] - }, - { - "uid": { - "type": "User", - "id": "739361" - }, - "attrs": { - "username": "Elsje", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -155,7 +243,10 @@ }, "attrs": { "username": "annabeldunstone", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -166,7 +257,10 @@ }, "attrs": { "username": "jivanvl", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -177,7 +271,10 @@ }, "attrs": { "username": "balasankarc", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -188,7 +285,10 @@ }, "attrs": { "username": "harishsr", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -199,7 +299,10 @@ }, "attrs": { "username": "jameslopez", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -210,29 +313,10 @@ }, "attrs": { "username": "kushalpandya", - "access_level": 40 - }, - "parents": [] - }, - { - "uid": { - "type": "User", - "id": "508743" - }, - "attrs": { - "username": "jarka", - "access_level": 40 - }, - "parents": [] - }, - { - "uid": { - "type": "User", - "id": "506061" - }, - "attrs": { - "username": "ahmadsherif", - "access_level": 30 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -244,7 +328,9 @@ "attrs": { "name": "GitLab.org", "path": "gitlab-org", - "full_path": "gitlab-org" + "full_path": "gitlab-org", + "visibility": "private", + "members": [] }, "parents": [] } diff --git a/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/entities.json b/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/entities.json index 6bc513fb..6416ec72 100644 --- a/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/entities.json +++ b/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd/entities.json @@ -7,7 +7,31 @@ "attrs": { "name": "authz.d", "path": "authzd", - "full_path": "gitlab-org/software-supply-chain-security/authorization/authzd" + "full_path": "gitlab-org/software-supply-chain-security/authorization/authzd", + "visibility": "private", + "archived": false, + "members": [ + "User::\"1\"", + "User::\"116\"", + "User::\"13356\"", + "User::\"3585\"", + "User::\"12452\"", + "User::\"64248\"", + "User::\"263716\"", + "User::\"283999\"", + "User::\"2293\"", + "User::\"215818\"", + "User::\"128633\"", + "User::\"273486\"", + "User::\"201566\"", + "User::\"426128\"", + "User::\"138401\"", + "User::\"367626\"", + "User::\"516904\"", + "User::\"527558\"", + "User::\"429540\"", + "User::\"506061\"" + ] }, "parents": [ { @@ -23,7 +47,10 @@ }, "attrs": { "username": "sytses", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -34,7 +61,10 @@ }, "attrs": { "username": "marin", - "access_level": 50 + "access_level": 50, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -45,7 +75,10 @@ }, "attrs": { "username": "dblessing", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -56,7 +89,10 @@ }, "attrs": { "username": "axil", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -67,7 +103,10 @@ }, "attrs": { "username": "ayufan", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -78,7 +117,10 @@ }, "attrs": { "username": "stanhu", - "access_level": 50 + "access_level": 50, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -89,7 +131,10 @@ }, "attrs": { "username": "grzesiek", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -100,7 +145,10 @@ }, "attrs": { "username": "dbalexandre", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -111,7 +159,10 @@ }, "attrs": { "username": "brodock", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -122,7 +173,10 @@ }, "attrs": { "username": "tmaczukin", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -133,7 +187,10 @@ }, "attrs": { "username": "rymai", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -144,7 +201,10 @@ }, "attrs": { "username": "jameslopez", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -155,7 +215,10 @@ }, "attrs": { "username": "annabeldunstone", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -166,7 +229,10 @@ }, "attrs": { "username": "felipe_artur", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -177,7 +243,10 @@ }, "attrs": { "username": "chriscool", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -188,7 +257,10 @@ }, "attrs": { "username": "alejandro", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -199,7 +271,10 @@ }, "attrs": { "username": "tauriedavis", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -210,7 +285,10 @@ }, "attrs": { "username": "eliran.mesika", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -221,7 +299,10 @@ }, "attrs": { "username": "ahanselka", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -232,7 +313,10 @@ }, "attrs": { "username": "ahmadsherif", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -244,7 +328,9 @@ "attrs": { "name": "GitLab.org", "path": "gitlab-org", - "full_path": "gitlab-org" + "full_path": "gitlab-org", + "visibility": "private", + "members": [] }, "parents": [] }, @@ -256,7 +342,9 @@ "attrs": { "name": "software-supply-chain-security", "path": "software-supply-chain-security", - "full_path": "gitlab-org/software-supply-chain-security" + "full_path": "gitlab-org/software-supply-chain-security", + "visibility": "private", + "members": [] }, "parents": [ { @@ -273,7 +361,9 @@ "attrs": { "name": "Authorization", "path": "authorization", - "full_path": "gitlab-org/software-supply-chain-security/authorization" + "full_path": "gitlab-org/software-supply-chain-security/authorization", + "visibility": "private", + "members": [] }, "parents": [ { diff --git a/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/entities.json b/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/entities.json index 4846592a..28c07b12 100644 --- a/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/entities.json +++ b/etc/authzd/gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/entities.json @@ -7,7 +7,31 @@ "attrs": { "name": "sparkle.d", "path": "sparkled", - "full_path": "gitlab-org/software-supply-chain-security/authorization/sparkled" + "full_path": "gitlab-org/software-supply-chain-security/authorization/sparkled", + "visibility": "private", + "archived": false, + "members": [ + "User::\"1\"", + "User::\"116\"", + "User::\"13356\"", + "User::\"3585\"", + "User::\"12452\"", + "User::\"64248\"", + "User::\"263716\"", + "User::\"283999\"", + "User::\"2293\"", + "User::\"215818\"", + "User::\"128633\"", + "User::\"273486\"", + "User::\"201566\"", + "User::\"426128\"", + "User::\"138401\"", + "User::\"367626\"", + "User::\"516904\"", + "User::\"527558\"", + "User::\"429540\"", + "User::\"506061\"" + ] }, "parents": [ { @@ -23,7 +47,10 @@ }, "attrs": { "username": "sytses", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -34,7 +61,10 @@ }, "attrs": { "username": "marin", - "access_level": 50 + "access_level": 50, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -45,7 +75,10 @@ }, "attrs": { "username": "dblessing", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -56,7 +89,10 @@ }, "attrs": { "username": "axil", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -67,7 +103,10 @@ }, "attrs": { "username": "ayufan", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -78,7 +117,10 @@ }, "attrs": { "username": "stanhu", - "access_level": 50 + "access_level": 50, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -89,7 +131,10 @@ }, "attrs": { "username": "grzesiek", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -100,7 +145,10 @@ }, "attrs": { "username": "dbalexandre", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -111,7 +159,10 @@ }, "attrs": { "username": "brodock", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -122,7 +173,10 @@ }, "attrs": { "username": "tmaczukin", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -133,7 +187,10 @@ }, "attrs": { "username": "rymai", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -144,7 +201,10 @@ }, "attrs": { "username": "jameslopez", - "access_level": 40 + "access_level": 40, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -155,7 +215,10 @@ }, "attrs": { "username": "annabeldunstone", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -166,7 +229,10 @@ }, "attrs": { "username": "felipe_artur", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -177,7 +243,10 @@ }, "attrs": { "username": "chriscool", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -188,7 +257,10 @@ }, "attrs": { "username": "alejandro", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -199,7 +271,10 @@ }, "attrs": { "username": "tauriedavis", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -210,7 +285,10 @@ }, "attrs": { "username": "eliran.mesika", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -221,7 +299,10 @@ }, "attrs": { "username": "ahanselka", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -232,7 +313,10 @@ }, "attrs": { "username": "ahmadsherif", - "access_level": 30 + "access_level": 30, + "admin": false, + "blocked": false, + "external": false }, "parents": [] }, @@ -244,7 +328,9 @@ "attrs": { "name": "GitLab.org", "path": "gitlab-org", - "full_path": "gitlab-org" + "full_path": "gitlab-org", + "visibility": "private", + "members": [] }, "parents": [] }, @@ -256,7 +342,9 @@ "attrs": { "name": "software-supply-chain-security", "path": "software-supply-chain-security", - "full_path": "gitlab-org/software-supply-chain-security" + "full_path": "gitlab-org/software-supply-chain-security", + "visibility": "private", + "members": [] }, "parents": [ { @@ -273,7 +361,9 @@ "attrs": { "name": "Authorization", "path": "authorization", - "full_path": "gitlab-org/software-supply-chain-security/authorization" + "full_path": "gitlab-org/software-supply-chain-security/authorization", + "visibility": "private", + "members": [] }, "parents": [ { diff --git a/etc/authzd/gitlab_access.cedar b/etc/authzd/gitlab_access.cedar new file mode 100644 index 00000000..ca17aa67 --- /dev/null +++ b/etc/authzd/gitlab_access.cedar @@ -0,0 +1,127 @@ +// GitLab Access Level Based Authorization +// Maps to Gitlab::Access constants: Guest(10), Reporter(20), Developer(30), Maintainer(40), Owner(50) +// Guest access (read-only operations) +permit ( + principal is User, + action in + [Action::"read_project", + Action::"read_group", + Action::"read_issue", + Action::"read_merge_request", + Action::"read_pipeline", + Action::"read_wiki", + Action::"download_code"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 10 && + resource has visibility && + (resource.visibility == "public" || + resource has members && + principal in resource.members) +}; + +// Reporter access (can create issues, view builds) +permit ( + principal is User, + action in + [Action::"create_issue", + Action::"create_issue_note", + Action::"read_build", + Action::"read_container_image", + Action::"pull_container_image"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 20 && + resource has members && + principal in resource.members +}; + +// Developer access (can push code, create MRs) +permit ( + principal is User, + action in + [Action::"push_code", + Action::"create_merge_request", + Action::"update_merge_request", + Action::"create_pipeline", + Action::"retry_pipeline", + Action::"push_container_image", + Action::"create_release"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 30 && + resource has members && + principal in resource.members +}; + +// Maintainer access (project administration) +permit ( + principal is User, + action in + [Action::"admin_project", + Action::"manage_project_members", + Action::"admin_merge_request", + Action::"push_to_delete_protected_branch", + Action::"admin_pipeline", + Action::"admin_container_registry", + Action::"admin_package_registry"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 40 && + resource has members && + principal in resource.members +}; + +// Owner access (full project control) +permit ( + principal is User, + action in + [Action::"destroy_project", + Action::"transfer_project", + Action::"archive_project", + Action::"change_visibility_level", + Action::"admin_project_hooks", + Action::"admin_project_runners"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 50 && + resource has members && + principal in resource.members +}; + +// Admin override - can do everything +permit ( + principal is User, + action, + resource +) +when +{ + principal has admin && + principal.admin == true && + principal has blocked && + !principal.blocked +}; + +// Block all access for blocked users +forbid ( + principal is User, + action, + resource +) +when { principal has blocked && principal.blocked == true }; diff --git a/etc/authzd/gitlab_schema.cedarschema b/etc/authzd/gitlab_schema.cedarschema new file mode 100644 index 00000000..78d7bd1a --- /dev/null +++ b/etc/authzd/gitlab_schema.cedarschema @@ -0,0 +1,158 @@ +// GitLab Cedar Schema Definition +// Defines entity types and actions for GitLab authorization + +// User entity represents GitLab users +entity User = { + username: String, + name: String, + admin: Bool, + blocked: Bool, + external: Bool, + bot: Bool, + access_level: Long, +}; + +// Group/Namespace entity (can be nested) +entity Namespace = { + name: String, + path: String, + full_path: String, + kind: String, // "user" or "group" + visibility_level: String, + members: Set, +} tags Set; + +// Project entity represents GitLab projects +entity Project = { + name: String, + path: String, + full_path: String, + visibility: String, // "public", "internal", "private" + archived: Bool, + members: Set, // Project members +} tags Set; + +// Group alias for Namespace +entity Group = { + name: String, + path: String, + full_path: String, + visibility: String, + members: Set, +} tags Set; + +// Project membership relationship +entity ProjectMembership = { + user_id: Long, + project_id: Long, + access_level: Long, + expires_at: String, +} tags Set; + +// Group membership relationship +entity GroupMembership = { + user_id: Long, + group_id: Long, + access_level: Long, + expires_at: String, +} tags Set; + +// Issue entity +entity Issue = { + iid: Long, + title: String, + state: String, + confidential: Bool, + author_id: Long, + assignee_ids: Set, + created_at: String, + updated_at: String, +} tags Set; + +// Merge Request entity +entity MergeRequest = { + iid: Long, + title: String, + state: String, + merge_status: String, + author_id: Long, + assignee_id: Long, + target_branch: String, + source_branch: String, + work_in_progress: Bool, + created_at: String, + updated_at: String, +} tags Set; + +// Actions that can be performed +action "read_project"; +action "admin_project"; +action "destroy_project"; +action "transfer_project"; +action "archive_project"; +action "change_visibility_level"; +action "manage_project_members"; + +action "read_group"; +action "admin_group"; +action "read_group_details"; + +action "read_repository"; +action "download_code"; +action "push_code"; +action "admin_repository"; +action "push_to_delete_protected_branch"; + +action "read_issue"; +action "create_issue"; +action "update_issue"; +action "admin_issue"; +action "create_issue_note"; + +action "read_merge_request"; +action "create_merge_request"; +action "update_merge_request"; +action "admin_merge_request"; +action "merge_merge_request"; + +action "read_wiki"; +action "create_wiki_page"; +action "update_wiki_page"; +action "admin_wiki"; + +action "read_snippet"; +action "create_snippet"; +action "update_snippet"; +action "admin_snippet"; + +action "read_build"; +action "read_pipeline"; +action "create_pipeline"; +action "retry_pipeline"; +action "admin_pipeline"; + +action "read_container_image"; +action "pull_container_image"; +action "push_container_image"; +action "admin_container_registry"; + +action "read_package"; +action "pull_package"; +action "push_package"; +action "admin_package_registry"; + +action "read_analytics"; +action "read_cycle_analytics"; +action "read_repository_analytics"; + +action "read_security_report"; +action "admin_security_policy"; +action "read_vulnerability_report"; + +action "read_release"; +action "create_release"; +action "update_release"; +action "admin_release"; + +action "admin_project_hooks"; +action "admin_project_runners"; \ No newline at end of file diff --git a/etc/authzd/gitlab_simple.cedar b/etc/authzd/gitlab_simple.cedar new file mode 100644 index 00000000..5ea8757d --- /dev/null +++ b/etc/authzd/gitlab_simple.cedar @@ -0,0 +1,144 @@ +// Simplified GitLab Authorization - No Feature or License Checks +// Based purely on user access levels and project visibility +// Guest access (read-only operations) +permit ( + principal is User, + action in + [Action::"read_project", + Action::"read_group", + Action::"read_issue", + Action::"read_merge_request", + Action::"read_pipeline", + Action::"read_wiki", + Action::"read_repository", + Action::"download_code", + Action::"read_snippet", + Action::"read_container_image", + Action::"read_package", + Action::"read_build", + Action::"read_analytics", + Action::"read_release"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 10 && + resource has visibility && + (resource.visibility == "public" || + resource has members && + principal in resource.members) +}; + +// Reporter access (can create issues, notes) +permit ( + principal is User, + action in + [Action::"create_issue", + Action::"create_issue_note", + Action::"pull_container_image", + Action::"pull_package"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 20 && + resource has members && + principal in resource.members +}; + +// Developer access (can push code, create MRs) +permit ( + principal is User, + action in + [Action::"push_code", + Action::"create_merge_request", + Action::"update_merge_request", + Action::"update_issue", + Action::"create_pipeline", + Action::"retry_pipeline", + Action::"push_container_image", + Action::"push_package", + Action::"create_release", + Action::"create_wiki_page", + Action::"update_wiki_page", + Action::"create_snippet", + Action::"update_snippet"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 30 && + resource has members && + principal in resource.members +}; + +// Maintainer access (project administration) +permit ( + principal is User, + action in + [Action::"admin_project", + Action::"manage_project_members", + Action::"admin_merge_request", + Action::"push_to_delete_protected_branch", + Action::"admin_pipeline", + Action::"admin_container_registry", + Action::"admin_package_registry", + Action::"admin_wiki", + Action::"admin_snippet", + Action::"admin_repository", + Action::"admin_issue", + Action::"admin_release"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 40 && + resource has members && + principal in resource.members +}; + +// Owner access (full project control) +permit ( + principal is User, + action in + [Action::"destroy_project", + Action::"transfer_project", + Action::"archive_project", + Action::"change_visibility_level", + Action::"admin_project_hooks", + Action::"admin_project_runners"], + resource +) +when +{ + principal has access_level && + principal.access_level >= 50 && + resource has members && + principal in resource.members +}; + +// Admin override - can do everything +permit ( + principal is User, + action, + resource +) +when +{ + principal has admin && + principal.admin == true && + principal has blocked && + !principal.blocked +}; + +// Block all access for blocked users +forbid ( + principal is User, + action, + resource +) +when { principal has blocked && principal.blocked == true }; diff --git a/etc/authzd/gitlab_visibility.cedar b/etc/authzd/gitlab_visibility.cedar new file mode 100644 index 00000000..78049cae --- /dev/null +++ b/etc/authzd/gitlab_visibility.cedar @@ -0,0 +1,127 @@ +// GitLab Visibility Level Authorization +// Controls access based on project/group visibility: public, internal, private +// Public projects - anyone can read +permit ( + principal, + action in + [Action::"read_project", + Action::"read_repository", + Action::"download_code", + Action::"read_issue", + Action::"read_merge_request", + Action::"read_wiki", + Action::"read_snippet"], + resource +) +when +{ + resource has visibility && + resource.visibility == "public" && + principal has blocked && + !principal.blocked +}; + +// Internal projects - authenticated users can read +permit ( + principal is User, + action in + [Action::"read_project", + Action::"read_repository", + Action::"download_code", + Action::"read_issue", + Action::"read_merge_request", + Action::"read_wiki", + Action::"read_snippet"], + resource +) +when +{ + resource has visibility && + resource.visibility == "internal" && + principal has external && + !principal.external && + principal has blocked && + !principal.blocked +}; + +// Private projects - only members can access +permit ( + principal is User, + action in + [Action::"read_project", + Action::"read_repository", + Action::"download_code", + Action::"read_issue", + Action::"read_merge_request", + Action::"read_wiki", + Action::"read_snippet"], + resource +) +when +{ + resource has visibility && + resource.visibility == "private" && + principal in resource.members && + principal has blocked && + !principal.blocked +}; + +// Prevent external users from accessing internal projects +forbid ( + principal is User, + action, + resource +) +when +{ + resource has visibility && + resource.visibility == "internal" && + principal has external && + principal.external == true +}; + +// Group visibility rules - similar to projects +permit ( + principal, + action in [Action::"read_group", Action::"read_group_details"], + resource is Group +) +when { resource has visibility && resource.visibility == "public" }; + +permit ( + principal is User, + action in [Action::"read_group", Action::"read_group_details"], + resource is Group +) +when +{ + resource has visibility && + resource.visibility == "internal" && + principal has external && + !principal.external +}; + +permit ( + principal is User, + action in [Action::"read_group", Action::"read_group_details"], + resource is Group +) +when +{ + resource has visibility && + resource.visibility == "private" && + principal in resource.members +}; + +// Archived projects have limited access +forbid ( + principal, + action in + [Action::"push_code", + Action::"create_issue", + Action::"create_merge_request", + Action::"update_issue", + Action::"update_merge_request"], + resource +) +when { resource has archived && resource.archived == true }; diff --git a/etc/authzd/test_simple.cedar b/etc/authzd/test_simple.cedar new file mode 100644 index 00000000..d236bc7f --- /dev/null +++ b/etc/authzd/test_simple.cedar @@ -0,0 +1,6 @@ +// Simple test policy to validate basic Cedar syntax +permit ( + principal is User, + action == Action::"read_project", + resource is Project +); diff --git a/share/misc/entities_example.json b/share/misc/entities_example.json new file mode 100644 index 00000000..c93f76f3 --- /dev/null +++ b/share/misc/entities_example.json @@ -0,0 +1,58 @@ +[ + { + "uid": {"type": "Project", "id": "123"}, + "attrs": { + "name": "GitLab", + "path": "gitlab", + "full_path": "gitlab-org/gitlab", + "visibility": "public", + "archived": false, + "members": ["User::\"1\"", "User::\"2\"", "User::\"3\""] + }, + "parents": [{"type": "Group", "id": "456"}] + }, + { + "uid": {"type": "Group", "id": "456"}, + "attrs": { + "name": "GitLab.org", + "path": "gitlab-org", + "full_path": "gitlab-org", + "visibility": "private", + "members": ["User::\"1\"", "User::\"2\""] + }, + "parents": [] + }, + { + "uid": {"type": "User", "id": "1"}, + "attrs": { + "username": "alice", + "access_level": 40, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + }, + { + "uid": {"type": "User", "id": "2"}, + "attrs": { + "username": "bob", + "access_level": 30, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + }, + { + "uid": {"type": "User", "id": "3"}, + "attrs": { + "username": "charlie", + "access_level": 20, + "admin": false, + "blocked": false, + "external": false + }, + "parents": [] + } +] \ No newline at end of file diff --git a/src/authorization/entities.rs b/src/authorization/entities.rs index fc1246d7..8d7d178f 100644 --- a/src/authorization/entities.rs +++ b/src/authorization/entities.rs @@ -46,6 +46,16 @@ impl EntitiesRepository { let project = self.api.get_project(&project_path).await?; + // Create member list first + let member_ids: Vec = self + .api + .get_project_members(project.id) + .await? + .iter() + .filter(|m| m.state == "active") + .map(|m| format!("User::\"{}\"", m.id)) + .collect(); + entities.push(CedarEntity { uid: CedarUid { entity_type: "Project".to_string(), @@ -54,7 +64,10 @@ impl EntitiesRepository { attrs: serde_json::json!({ "name": project.name, "path": project.path, - "full_path": format!("{}/{}", project.namespace.full_path, project.path), + "full_path": project.path_with_namespace, + "visibility": project.visibility, + "archived": project.archived.unwrap_or(false), + "members": member_ids, }), parents: if project.namespace.kind == "group" { vec![CedarParent { @@ -66,6 +79,7 @@ impl EntitiesRepository { }, }); + // Get all members again to create User entities for member in self.api.get_project_members(project.id).await? { if member.state == "active" { entities.push(CedarEntity { @@ -76,6 +90,9 @@ impl EntitiesRepository { attrs: serde_json::json!({ "username": member.username, "access_level": member.access_level, + "admin": false, // Would need to fetch from user API for real admin status + "blocked": false, // Would need to fetch from user API for real blocked status + "external": false, // Would need to fetch from user API for real external status }), parents: vec![], }); @@ -133,6 +150,8 @@ impl EntitiesRepository { "name": group.name, "path": group.path, "full_path": group.full_path, + "visibility": "private", // Groups don't have visibility in our simplified model + "members": [], // Would need group members API to populate }), parents, }); diff --git a/src/authorization/gitlab_entities.rs b/src/authorization/gitlab_entities.rs new file mode 100644 index 00000000..643f90e8 --- /dev/null +++ b/src/authorization/gitlab_entities.rs @@ -0,0 +1,208 @@ +use serde::Serialize; +use std::collections::HashMap; + +// Cedar entity structures optimized for GitLab authorization +#[derive(Debug, Serialize)] +pub struct CedarEntity { + pub uid: CedarUid, + pub attrs: serde_json::Value, + pub parents: Vec, +} + +#[derive(Debug, Serialize)] +pub struct CedarUid { + #[serde(rename = "type")] + pub entity_type: String, + pub id: String, +} + +#[derive(Debug, Serialize)] +pub struct CedarParent { + #[serde(rename = "type")] + pub parent_type: String, + pub id: String, +} + +// GitLab-specific entity builders +pub struct GitLabEntityBuilder; + +impl GitLabEntityBuilder { + // Build User entity with GitLab-specific attributes + pub fn build_user(user: &crate::gitlab::User) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "User".to_string(), + id: user.id.to_string(), + }, + attrs: serde_json::json!({ + "username": user.username, + "name": user.name, + "admin": user.admin.unwrap_or(false), + "blocked": user.state == "blocked", + "bot": user.bot.unwrap_or(false), + "external": user.external.unwrap_or(false), + "created_at": user.created_at + }), + parents: vec![], + } + } + + // Build Project entity - simplified without feature checks + pub fn build_project(project: &crate::gitlab::Project) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "Project".to_string(), + id: project.id.to_string(), + }, + attrs: serde_json::json!({ + "name": project.name, + "path": project.path, + "full_path": project.path_with_namespace, + "visibility": project.visibility, + "archived": project.archived.unwrap_or(false), + "members": [] // Will be populated separately + }), + parents: vec![CedarParent { + parent_type: "Namespace".to_string(), + id: project.namespace.id.to_string(), + }], + } + } + + // Build Group/Namespace entity + pub fn build_namespace(namespace: &crate::gitlab::Namespace) -> CedarEntity { + let mut parents = vec![]; + if let Some(parent_id) = namespace.parent_id { + parents.push(CedarParent { + parent_type: "Namespace".to_string(), + id: parent_id.to_string(), + }); + } + + CedarEntity { + uid: CedarUid { + entity_type: "Namespace".to_string(), + id: namespace.id.to_string(), + }, + attrs: serde_json::json!({ + "name": namespace.name, + "path": namespace.path, + "full_path": namespace.full_path, + "kind": namespace.kind, + "visibility_level": namespace.visibility_level + }), + parents, + } + } + + // Build Membership entity - represents user access to projects/groups + pub fn build_project_membership( + user_id: u64, + project_id: u64, + member: &crate::gitlab::Member, + ) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "ProjectMembership".to_string(), + id: format!("{}:{}", user_id, project_id), + }, + attrs: serde_json::json!({ + "user_id": user_id, + "project_id": project_id, + "access_level": member.access_level, + "expires_at": member.expires_at + }), + parents: vec![ + CedarParent { + parent_type: "User".to_string(), + id: user_id.to_string(), + }, + CedarParent { + parent_type: "Project".to_string(), + id: project_id.to_string(), + }, + ], + } + } + + // Build Group Membership entity + pub fn build_group_membership( + user_id: u64, + group_id: u64, + member: &crate::gitlab::Member, + ) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "GroupMembership".to_string(), + id: format!("{}:{}", user_id, group_id), + }, + attrs: serde_json::json!({ + "user_id": user_id, + "group_id": group_id, + "access_level": member.access_level, + "expires_at": member.expires_at + }), + parents: vec![ + CedarParent { + parent_type: "User".to_string(), + id: user_id.to_string(), + }, + CedarParent { + parent_type: "Namespace".to_string(), + id: group_id.to_string(), + }, + ], + } + } + + // Build Issue entity + pub fn build_issue(issue: &crate::gitlab::Issue, project_id: u64) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "Issue".to_string(), + id: issue.id.to_string(), + }, + attrs: serde_json::json!({ + "iid": issue.iid, + "title": issue.title, + "state": issue.state, + "confidential": issue.confidential, + "author_id": issue.author.id, + "assignee_ids": issue.assignees.iter().map(|a| a.id).collect::>(), + "created_at": issue.created_at, + "updated_at": issue.updated_at + }), + parents: vec![CedarParent { + parent_type: "Project".to_string(), + id: project_id.to_string(), + }], + } + } + + // Build MergeRequest entity + pub fn build_merge_request(mr: &crate::gitlab::MergeRequest, project_id: u64) -> CedarEntity { + CedarEntity { + uid: CedarUid { + entity_type: "MergeRequest".to_string(), + id: mr.id.to_string(), + }, + attrs: serde_json::json!({ + "iid": mr.iid, + "title": mr.title, + "state": mr.state, + "merge_status": mr.merge_status, + "author_id": mr.author.id, + "assignee_id": mr.assignee.as_ref().map(|a| a.id), + "target_branch": mr.target_branch, + "source_branch": mr.source_branch, + "work_in_progress": mr.work_in_progress, + "created_at": mr.created_at, + "updated_at": mr.updated_at + }), + parents: vec![CedarParent { + parent_type: "Project".to_string(), + id: project_id.to_string(), + }], + } + } +} \ No newline at end of file diff --git a/src/gitlab/mod.rs b/src/gitlab/mod.rs index e1993d81..e8b49e0b 100644 --- a/src/gitlab/mod.rs +++ b/src/gitlab/mod.rs @@ -3,9 +3,11 @@ pub mod group; pub mod member; pub mod namespace; pub mod project; +pub mod user; pub use api::Api; pub use group::Group; pub use member::Member; pub use namespace::Namespace; pub use project::Project; +pub use user::User; diff --git a/src/gitlab/project.rs b/src/gitlab/project.rs index ba88c2e3..92201af3 100644 --- a/src/gitlab/project.rs +++ b/src/gitlab/project.rs @@ -7,5 +7,8 @@ pub struct Project { pub id: u64, pub name: String, pub path: String, + pub path_with_namespace: String, pub namespace: Namespace, + pub visibility: String, // "public", "internal", "private" + pub archived: Option, } diff --git a/src/gitlab/user.rs b/src/gitlab/user.rs new file mode 100644 index 00000000..44ed61c1 --- /dev/null +++ b/src/gitlab/user.rs @@ -0,0 +1,13 @@ +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct User { + pub id: u64, + pub username: String, + pub name: String, + pub state: String, // "active", "blocked", etc. + pub admin: Option, + pub bot: Option, + pub external: Option, + pub created_at: String, +} -- cgit v1.2.3