-
Notifications
You must be signed in to change notification settings - Fork 60
fix(EC-1993): reject past --effective-time values by default #3424
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,15 +26,17 @@ import ( | |
| "github.com/spf13/cobra" | ||
| "sigs.k8s.io/yaml" | ||
|
|
||
| "github.com/conforma/cli/internal/policy" | ||
| "github.com/conforma/cli/internal/policy/equivalence" | ||
| ) | ||
|
|
||
| var ( | ||
| effectiveTime string | ||
| imageDigest string | ||
| imageRef string | ||
| imageURL string | ||
| outputFormat string | ||
| effectiveTime string | ||
| allowPastEffectiveTime bool | ||
| imageDigest string | ||
| imageRef string | ||
| imageURL string | ||
| outputFormat string | ||
| ) | ||
|
|
||
| var CompareCmd *cobra.Command | ||
|
|
@@ -74,6 +76,7 @@ Examples: | |
| } | ||
|
|
||
| compareCmd.Flags().StringVar(&effectiveTime, "effective-time", "now", "Effective time for policy evaluation (RFC3339 format, 'now')") | ||
| compareCmd.Flags().BoolVar(&allowPastEffectiveTime, "allow-past-effective-time", false, "Allow setting --effective-time to a date in the past") | ||
| compareCmd.Flags().StringVar(&imageDigest, "image-digest", "", "Image digest for volatile config matching") | ||
| compareCmd.Flags().StringVar(&imageRef, "image-ref", "", "Image reference for volatile config matching") | ||
| compareCmd.Flags().StringVar(&imageURL, "image-url", "", "Image URL for volatile config matching") | ||
|
|
@@ -84,20 +87,12 @@ Examples: | |
|
|
||
| func runCompare(cmd *cobra.Command, args []string) error { | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] scope Compare command refactoring replaces inline time parsing with ParseEffectiveTime. Behavioral change beyond stated fix scope — previously compare accepted any past time silently. |
||
| // Parse effective time | ||
| var effectiveTimeValue time.Time | ||
| switch effectiveTime { | ||
| case "now": | ||
| effectiveTimeValue = time.Now().UTC() | ||
| case "attestation": | ||
| // For now, use current time as default for attestation time | ||
| effectiveTimeValue, err := policy.ParseEffectiveTime(effectiveTime, allowPastEffectiveTime) | ||
| if err != nil { | ||
| return err | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] api-contract The 'attestation' sentinel is accepted by --effective-time but not documented in the compare command's help text. Pre-existing condition. |
||
| } | ||
| if effectiveTimeValue.IsZero() { | ||
| effectiveTimeValue = time.Now().UTC() | ||
| default: | ||
| var err error | ||
| effectiveTimeValue, err = time.Parse(time.RFC3339, effectiveTime) | ||
| if err != nil { | ||
| return fmt.Errorf("invalid effective time format: %w", err) | ||
| } | ||
| } | ||
|
|
||
| // Create image info if provided | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -202,7 +202,8 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command { | |
| data.policyConfiguration = policyConfiguration | ||
|
|
||
| policyOptions := policy.Options{ | ||
| EffectiveTime: data.effectiveTime, | ||
| EffectiveTime: data.effectiveTime, | ||
| AllowPastEffectiveTime: data.allowPastEffectiveTime, | ||
| Identity: cosign.Identity{ | ||
| Issuer: data.certificateOIDCIssuer, | ||
| IssuerRegExp: data.certificateOIDCIssuerRegExp, | ||
|
|
@@ -547,6 +548,11 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command { | |
| a RFC3339 formatted value, e.g. 2022-11-18T00:00:00Z. | ||
| `)) | ||
|
|
||
| cmd.Flags().BoolVar(&data.allowPastEffectiveTime, "allow-past-effective-time", false, hd.Doc(` | ||
| Allow setting --effective-time to a date in the past. By default, dates | ||
| more than 5 minutes in the past are rejected. | ||
| `)) | ||
|
|
||
| cmd.Flags().StringSliceVar(&data.extraRuleData, "extra-rule-data", data.extraRuleData, hd.Doc(` | ||
| Extra data to be provided to the Rego policy evaluator. Use format 'key=value'. May be used multiple times. | ||
| `)) | ||
|
|
@@ -633,6 +639,7 @@ type imageData struct { | |
| certificateOIDCIssuer string | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] struct-field-ordering New allowPastEffectiveTime field breaks alphabetical ordering convention in imageData struct. |
||
| certificateOIDCIssuerRegExp string | ||
| effectiveTime string | ||
| allowPastEffectiveTime bool | ||
| extraRuleData []string | ||
| filePath string | ||
| filterType string | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,21 +167,22 @@ func (p *policy) SigstoreOpts() (SigstoreOpts, error) { | |
| } | ||
|
|
||
| type Options struct { | ||
| EffectiveTime string | ||
| Identity cosign.Identity | ||
| IgnoreRekor bool | ||
| SkipImageSigCheck bool | ||
| SkipAttSigCheck bool | ||
| PolicyRef string | ||
| PublicKey string | ||
| RekorURL string | ||
| EffectiveTime string | ||
| AllowPastEffectiveTime bool | ||
| Identity cosign.Identity | ||
| IgnoreRekor bool | ||
| SkipImageSigCheck bool | ||
| SkipAttSigCheck bool | ||
| PolicyRef string | ||
| PublicKey string | ||
| RekorURL string | ||
| } | ||
|
|
||
| // NewOfflinePolicy construct and return a new instance of Policy that is used | ||
| // in offline scenarios, i.e. without cluster or specific services access, and | ||
| // no signature verification being performed. | ||
|
cuipinghuo marked this conversation as resolved.
|
||
| func NewOfflinePolicy(ctx context.Context, effectiveTime string) (Policy, error) { | ||
| if efn, err := parseEffectiveTime(effectiveTime); err == nil { | ||
| if efn, err := parseEffectiveTime(effectiveTime, true); err == nil { | ||
| return &policy{ | ||
| effectiveTime: efn, | ||
| choosenTime: effectiveTime, | ||
|
|
@@ -219,8 +220,8 @@ func NewInertPolicy(ctx context.Context, policyRef string) (Policy, error) { | |
| // resource in Kubernetes using the format: [namespace/]name | ||
| // | ||
| // If policyRef is blank, an empty EnterpriseContractPolicySpec is used. | ||
| func NewInputPolicy(ctx context.Context, policyRef string, effectiveTime string) (Policy, error) { | ||
| if efn, err := parseEffectiveTime(effectiveTime); err == nil { | ||
| func NewInputPolicy(ctx context.Context, policyRef string, effectiveTime string, allowPastEffectiveTime bool) (Policy, error) { | ||
| if efn, err := parseEffectiveTime(effectiveTime, allowPastEffectiveTime); err == nil { | ||
| p := policy{ | ||
| choosenTime: effectiveTime, | ||
| checkOpts: &cosign.CheckOpts{}, | ||
|
|
@@ -299,7 +300,7 @@ func NewPolicy(ctx context.Context, opts Options) (Policy, error) { | |
| } | ||
| } | ||
|
|
||
| if efn, err := parseEffectiveTime(opts.EffectiveTime); err != nil { | ||
| if efn, err := parseEffectiveTime(opts.EffectiveTime, opts.AllowPastEffectiveTime); err != nil { | ||
| return nil, err | ||
| } else { | ||
| p.effectiveTime = efn | ||
|
|
@@ -421,7 +422,28 @@ func isNow(choosenTime string) bool { | |
| return strings.EqualFold(choosenTime, Now) | ||
| } | ||
|
|
||
| func parseEffectiveTime(choosenTime string) (*time.Time, error) { | ||
| // pastEffectiveTimeGracePeriod is the tolerance for clock skew when rejecting | ||
| // past effective-time values. Times within this window are accepted even | ||
| // without --allow-past-effective-time. | ||
| const pastEffectiveTimeGracePeriod = 5 * time.Minute | ||
|
|
||
| // ParseEffectiveTime parses the effective-time string and returns the resolved | ||
| // time. It accepts "now", "attestation", RFC3339, or YYYY-MM-DD formats. When | ||
| // allowPast is false, times more than 5 minutes in the past are rejected. | ||
| // The "attestation" sentinel returns a zero time.Time — callers should handle | ||
| // that case separately. | ||
| func ParseEffectiveTime(choosenTime string, allowPast bool) (time.Time, error) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] naming-convention Parameter name choosenTime perpetuates existing misspelling in new exported ParseEffectiveTime function. Opportunity to use correct spelling chosenTime. |
||
| t, err := parseEffectiveTime(choosenTime, allowPast) | ||
| if err != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [medium] scope-creep New exported ParseEffectiveTime() has the same name as existing vsa.ParseEffectiveTime() in internal/validate/vsa/validator.go with different signature and behavior, creating a confusing dual-function situation. Suggested fix: Document the relationship or file follow-up to unify the two functions. |
||
| return time.Time{}, err | ||
| } | ||
| if t == nil { | ||
| return time.Time{}, nil | ||
| } | ||
| return *t, nil | ||
| } | ||
|
|
||
| func parseEffectiveTime(choosenTime string, allowPast bool) (*time.Time, error) { | ||
| switch { | ||
| case isNow(choosenTime): | ||
| now := now().UTC() | ||
|
|
@@ -435,6 +457,9 @@ func parseEffectiveTime(choosenTime string) (*time.Time, error) { | |
| if when, err := time.Parse(time.RFC3339, choosenTime); err == nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [high] breaking-cli Default behavior of --effective-time changed in backward-incompatible way. Past dates now rejected unless --allow-past-effective-time is passed. Downstream CI/CD pipelines or scripts using --effective-time with past dates will break after CLI upgrade. Suggested fix: Document as breaking change in release notes and consider bumping the minor/major version. At minimum, release notes must call out the breaking behavior. |
||
| log.Debugf("Using provided effective time %s", when.Format(time.RFC3339)) | ||
| whenUTC := when.UTC() | ||
| if err := rejectPastTime(whenUTC, allowPast); err != nil { | ||
| return nil, err | ||
| } | ||
| return &whenUTC, nil | ||
| } | ||
|
|
||
|
|
@@ -444,6 +469,9 @@ func parseEffectiveTime(choosenTime string) (*time.Time, error) { | |
| if when, err := time.Parse(DateFormat, choosenTime); err == nil { | ||
| log.Debugf("Using provided effective time %s", when.Format(time.RFC3339)) | ||
| whenUTC := when.UTC() | ||
| if err := rejectPastTime(whenUTC, allowPast); err != nil { | ||
| return nil, err | ||
| } | ||
| return &whenUTC, nil | ||
| } | ||
| log.Debugf("Unable to parse provided effective time string `%s` using %s format", choosenTime, DateFormat) | ||
|
|
@@ -453,6 +481,17 @@ func parseEffectiveTime(choosenTime string) (*time.Time, error) { | |
| } | ||
| } | ||
|
|
||
| func rejectPastTime(when time.Time, allowPast bool) error { | ||
| if allowPast { | ||
| return nil | ||
| } | ||
| threshold := now().UTC().Add(-pastEffectiveTimeGracePeriod) | ||
| if when.Before(threshold) { | ||
| return fmt.Errorf("effective time %s is in the past; use --allow-past-effective-time to override", when.Format(time.RFC3339)) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // checkOpts returns an instance based on attributes of the Policy. | ||
| func checkOpts(ctx context.Context, p *policy) (*cosign.CheckOpts, error) { | ||
| var err error | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[low] flag-declaration-style
Flag description for --allow-past-effective-time in compare.go omits 'By default, dates more than 5 minutes in the past are rejected' which is present in image.go and input.go.