Skip to content

Introduce expiration field for accessConfig policies#4036

Closed
matheuscscp wants to merge 1 commit intoproject-zot:mainfrom
matheuscscp:authz-exp
Closed

Introduce expiration field for accessConfig policies#4036
matheuscscp wants to merge 1 commit intoproject-zot:mainfrom
matheuscscp:authz-exp

Conversation

@matheuscscp
Copy link
Copy Markdown
Contributor

@matheuscscp matheuscscp commented May 5, 2026

Use case

Similar to #3755 (implemented via #3761), but now for the accessConfig policies. It turns out that issuing customer licenses as simple "ID cards" i.e. without the permissions embedded directly on them is much better (i.e. use OIDC federation), because we don't have to re-issue a license for a customer every time their permissions change. This feature allows us to configure the customer access on the server side via static Zot config (e.g. giving a customer limited access to a container image as a free trial, for example), and they can always use the same license, which is now just an attestation of their identity.

Why this feature belongs in Zot from a separation of concerns perspective

This is an important feature for OIDC federation users: OIDC's responsibility is to simply identify a user/identity (authn), and the relying party (Zot) is fully responsible for authz. When OIDC federation was introduced (via #3711), we agreed that it would entirely rely on accessConfig. So to implement a rule like this for OIDC, the only system that can do it is Zot, the relying party. There's nowhere else where this rule can be enforced from OIDC perspective. So the separation of concerns is:

  • Zot delegates authn to the configured OIDC provider
  • Zot takes full responsibility for the authz part

Furthermore, a feature like this for access control is very common and it would simply enrich Zot's capabilities. All the three major cloud providers have this feature, see below.

AWS:

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws-dates.html

{
    "Version":"2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "service-prefix:action-name",
            "Resource": "*",
            "Condition": {
                "DateGreaterThan": {"aws:CurrentTime": "2020-04-01T00:00:00Z"},
                "DateLessThan": {"aws:CurrentTime": "2020-06-30T23:59:59Z"}
            }
        }
    ]
}

GCP:

https://docs.cloud.google.com/iam/docs/conditions-overview#example-date-time

request.time < timestamp('2021-01-01T00:00:00Z')

Azure:

https://learn.microsoft.com/en-us/azure/role-based-access-control/pim-integration

{
    "properties": {
        "principalId": "aaaaaaaa-bbbb-cccc-1111-222222222222",
        "roleDefinitionId": "/subscriptions/.../providers/Microsoft.Authorization/roleDefinitions/c8d4ff99-41c3-41a8-9f60-21dfdad59608",
        "requestType": "AdminAssign",
        "scheduleInfo": {
            "startDateTime": "2020-04-01T00:00:00Z",
            "expiration": {
                "type": "AfterDateTime",
                "endDateTime": "2020-06-30T23:59:59Z"
            }
        }
    }
}

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
@matheuscscp matheuscscp changed the title Introduce expiration field for accessConfig policies Introduce expiration field for accessConfig policies May 5, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.55%. Comparing base (7ec34a9) to head (fcf6551).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/api/authz.go 89.47% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4036   +/-   ##
=======================================
  Coverage   91.54%   91.55%           
=======================================
  Files         197      197           
  Lines       28395    28406   +11     
=======================================
+ Hits        25995    26007   +12     
+ Misses       1549     1547    -2     
- Partials      851      852    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an optional expiration timestamp to accessControl policy entries (server-side accessConfig), allowing policy rules to become inactive after a configured time without requiring license/token re-issuance.

Changes:

  • Add ExpiresAt *time.Time to config.Policy and enforce expiry during authorization evaluation.
  • Extend server config loading to decode RFC3339 timestamps from config files.
  • Add tests for config decoding of expiresAt and for authz behavior with expired/non-expired policies.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/cli/server/root.go Adds RFC3339 string-to-time decoding hook for config unmarshalling.
pkg/cli/server/root_test.go Adds coverage ensuring expiresAt decodes and malformed values fail config load.
pkg/api/config/config.go Introduces Policy.ExpiresAt as an optional expiration timestamp.
pkg/api/authz.go Skips expired policy entries during permission checks and glob pattern generation.
pkg/api/authz_internal_test.go Adds unit tests covering expiry behavior for user/group policies and glob patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/api/config/config.go

// ExpiresAt is an optional expiration time for this policy entry.
// When set, the policy is ignored once the timestamp is in the past.
ExpiresAt *time.Time
Comment thread pkg/api/authz.go
Comment on lines +223 to +226
// isPolicyExpired reports whether a policy's optional ExpiresAt has passed.
func isPolicyExpired(p config.Policy) bool {
return p.ExpiresAt != nil && p.ExpiresAt.Before(time.Now())
}
cfg := config.New()

err := cli.LoadConfiguration(cfg, tmpfile)
So(err, ShouldNotBeNil)
@matheuscscp
Copy link
Copy Markdown
Contributor Author

@rchincha @andaaron Ramkumar told me in Slack that you were debating internally if this feature makes sense to be in Zot at all from a separation of concerns perspective. I think it does, as it is a simple and common access control feature, all the major cloud providers have it, please see the updated PR description.

@matheuscscp
Copy link
Copy Markdown
Contributor Author

Superseded by: #4040

@matheuscscp matheuscscp closed this May 6, 2026
@matheuscscp matheuscscp deleted the authz-exp branch May 6, 2026 14:59
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.

2 participants