-
Epic
-
Resolution: Unresolved
-
Critical
-
None
-
None
-
None
-
Immutable Tag Support
-
False
-
-
False
-
Not Selected
-
To Do
-
88% To Do, 13% In Progress, 0% Done
[Tags] Immutable Tag Support
Overview
Enable users to mark image tags as immutable, preventing them from being overwritten, deleted, or modified. This provides a stable, trusted reference for builds and releases while meeting regulatory and compliance requirements for organizations that need to programmatically protect specific image tags without making entire repositories read-only.
Context
Container image tags are inherently floating and dynamic, but this is not always desirable. When build identifiers (git commits, releases) are used as tags, users need assurance that the tag reference remains stable. This feature extends Quay's tag management with immutability capabilities at individual tag, repository, and organization levels.
Reference: Original feature PROJQUAY-1253
Current State: The database schema already includes an immutable column on the tag table (migration 5b8dc452f5c3) and indexes for efficient queries, but enforcement logic and user-facing functionality are not implemented.
Scope
In Scope
- Individual tag immutability via API and UI
- Repository-level immutability policies with regex patterns
- Organization-level immutability policies with regex patterns
- quay.immutable=true manifest label support during push
- Enforcement: block overwrite, delete, label changes, and auto-prune of immutable tags
- Coexistence of immutability with expiration dates
- Org-level setting to optionally disallow expiration for immutable tags
- Preserve immutability status for tags pulled through caching proxy
- Block conversion to mirror/cache org when immutable tags exist
Out of Scope
- Bulk operations to retroactively apply immutability to existing tags (Phase 2)
- Audit/reporting dashboards for immutable tag compliance
- Integration with external policy engines
Child Stories
Phase 1: Core Backend Enforcement
- Enforce immutability on tag deletion: Block delete_tag() and remove_tag_from_timemachine() for immutable tags
- Enforce immutability on tag retargeting: Block retarget_tag() from overwriting immutable tags
- Enforce immutability on expiration changes: Block expiration modification for immutable tags (unless org allows it)
- Enforce immutability on manifest label changes: Block adding/removing labels on manifests with immutable tags
- Exclude immutable tags from auto-pruning: Update auto-prune workers to skip immutable tags
Phase 2: API Layer
- Add API endpoint to get tag immutability status: Extend _tag_dict() to include immutable field
- Add API endpoint to set tag immutability: New endpoint or extend RepositoryTag.put() with immutable field
- Add permission checks: Write permission to make immutable, admin permission to make mutable again
- Add audit logging: Log change_tag_immutability actions
Phase 3: Policy System
- Create immutability policy data model: Database tables for org/repo-level regex policies
- Add API for managing immutability policies: CRUD endpoints for policies
- Implement policy evaluation during push: Apply policies when tags are created
- Add org-level setting for immutable tag expiration: Config option to disallow expiration on immutable tags
Phase 4: Manifest Label Support
- Handle quay.immutable=true label on push: Set immutability flag when manifest contains label
- Make quay.immutable a read-only built-in label: Prevent removal/modification via label API
- Document label/API precedence: API settings override label settings
Phase 5: System Integration
- Block mirror conversion with immutable tags: Check for immutable tags before allowing mirror conversion
- Block cache org conversion with immutable tags: Check for immutable tags before allowing cache conversion
- Block tag reversion to immutable tags: Prevent reverting to a manifest that has an immutable tag
- Preserve immutability on cache proxy pull-through: Propagate immutability status from upstream
Phase 6: UI Implementation
- Display immutable status in tag list: Show immutable indicator on tags
- Add immutability toggle for individual tags: UI to set/unset immutability
- Add repository settings page for immutability policies: Policy management UI
- Add organization settings page for immutability policies: Org-level policy management UI
- Add bulk search and apply UI: Search tags by regex and apply immutability in bulk
Phase 7: Testing and Documentation
- Unit tests for enforcement logic: Cover all enforcement points
- Integration tests for API endpoints: Test permission checks, logging
- E2E tests for UI: Cypress/Playwright tests for tag immutability flows
- Documentation: User-facing docs for immutable tag feature
Dependencies
- Technical: Database migration already exists; core tag model infrastructure in place
- Cross-team: None identified (self-contained feature)
- External: None
Success Criteria
- [ ] Users can set individual tags as immutable/mutable via API and UI
- [ ] Only write-permission users can make tags immutable
- [ ] Only admin-permission users can make tags mutable again
- [ ] Immutable tags cannot be overwritten, deleted, or auto-pruned
- [ ] Manifests with immutable tags cannot have labels changed
- [ ] Repository/org-level regex policies can define tag immutability
- [ ] quay.immutable=true label sets tag immutable on push
- [ ] Immutability and expiration can coexist (configurable at org level)
- [ ] Mirror/cache conversion blocked when immutable tags exist
- [ ] All changes are audit-logged
Technical Approach
Components Affected
- data/model/oci/tag.py: Add enforcement logic to tag operations
- data/database.py: Add policy tables if needed (or extend existing)
- endpoints/api/tag.py: Extend API with immutability endpoints
- workers/autopruneworker.py: Exclude immutable tags from pruning
- data/registry_model/label_handlers.py: Add quay.immutable handler
- endpoints/v2/manifest.py: Apply policy during manifest push
- web/src/: UI components for tag immutability
Key Technical Decisions
- Leverage existing immutable column: No new migration needed for core functionality
- Policy enforcement at model layer: Centralized enforcement in data/model/oci/tag.py
- Policies stored per-namespace and per-repo: Similar to existing auto-prune policy pattern
- Log entry kind already exists: change_tag_immutability ready to use
Risks and Mitigations
- Risk: Breaking changes for users relying on tag overwrite behavior
Mitigation: Feature is opt-in per tag/policy; no default immutability - Risk: Performance impact of policy regex evaluation on every push
Mitigation: Cache compiled regex patterns; evaluate lazily - Risk: Complexity of policy precedence (tag-level vs repo-level vs org-level)
Mitigation: Clear precedence rules: explicit tag setting > repo policy > org policy
Testing Strategy
- Unit tests: Test enforcement in tag.py operations with immutable=True
- Integration tests: API permission checks, audit logging, policy evaluation
- E2E tests: Playwright tests for UI flows (setting immutability, viewing status)
- Registry protocol tests: Push with quay.immutable=true label
Rollout Strategy
- Feature can be rolled out incrementally by story
- No feature flag required (enforcement only applies to explicitly-set immutable tags)
- Backward compatible (existing tags remain mutable by default)
- No migration needed (schema already in place)
Documentation Needs
- User guide: How to set tag immutability via UI and API
- Admin guide: How to configure org/repo-level immutability policies
- API reference: New endpoints and schema changes
- Dockerfile/Containerfile label reference: quay.immutable=true usage
Related Work
- Original Feature: PROJQUAY-1253
- Related Issues: PROJQUAY-277 (prior related feature, closed), PROJQUAY-6970 (duplicate, closed)
- Reference: Existing auto-prune policy pattern in data/model/autoprune.py