Skip to content
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

Add a ruletype for enforcing a file being present in a repo #227

Merged
merged 7 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
48 changes: 48 additions & 0 deletions rule-types/common/enforce_file.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
tests:
- name: "File should be present"
def:
file: README
content: ""
params: {}
expect: "pass"
git:
repo_base: file_present
- name: "File is missing"
def:
file: README
params: {}
expect: "fail"
git:
repo_base: file_missing
- name: "File present and matches content"
def:
file: README
content: "Test content"
params: {}
expect: "pass"
git:
repo_base: file_present
- name: "File present, but has different content"
def:
file: README
content: "Different content"
params: {}
expect: "fail"
git:
repo_base: file_present
- name: "File present, but has more content than expected"
def:
file: README
content: "Test"
params: { }
expect: "fail"
git:
repo_base: file_present
- name: "File present, but has less content than expected"
def:
file: README
content: "Test content with a subset"
params: { }
expect: "fail"
git:
repo_base: file_present
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test content
101 changes: 101 additions & 0 deletions rule-types/common/enforce_file.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
version: v1
release_phase: alpha
type: rule-type
name: enforce_file
display_name: Enforce a file in the repository
short_failure_message: File does not exist or does not match the expected content
severity:
value: medium
context: {}
description: |
Enforce the presence of a file and its content in the repository.
guidance: |
Ensures that the repository contains a specific file and its content.

This rule is useful for enforcing the presence of various files in the repository, such as LICENSE, README, CONTRIBUTING,
CODE_OF_CONDUCT, Dependabot configuration files and many more. It can also be used to enforce the presence of specific
Copy link
Member

Choose a reason for hiding this comment

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

I suspect that Dependabot is trickier, because you probably want it to be adjusted based on the languages present. The others seem fairly likely to be consistent.

content in the file so you know that the file is not just a placeholder.
def:
in_entity: repository
rule_schema:
type: object
properties:
file:
type: string
description: |
The file to enforce in the repository.
For example, LICENSE, README, CONTRIBUTING, .github/dependabot.yml, etc.
content:
type: string
description: |
The content to enforce in the file.
For example, the content of the LICENSE file.
default: ""
apply_if_file:
type: string
description: |
Optional. If specified, the rule will only be evaluated if the given file exists.
This is useful for rules that are only applicable to certain types of repositories.
default: ""
required:
- file
ingest:
type: git
git:
# The following code checks for the presence of a file and its content.
# If the content is not specified (content = ""), then only the presence of the file is checked.
# If apply_if_file is specified, the rule is only evaluated if that file exists.
eval:
type: rego
rego:
type: deny-by-default
def: |
package minder

import future.keywords.if

default allow := false
default skip := false
fileStr := trim_space(file.read(input.profile.file))
Copy link
Contributor

Choose a reason for hiding this comment

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

concern: I'm afraid this can't be used to implement OSPS-DO-02 since it requires checking the existence of CONTRIBUTING/ folder as well.

How complex would it be to support folders as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me see what I can do 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the main difficulty is that input.profile.file is also used for the remediation, and in the case of a folder it's harder to figure out what's the right thing to do (i.e. one can't always default to creating a file).

On the other hand, OSSF is not currently interested in remediation.


# Skip if apply_if_file is specified and the file doesn't exist
skip if {
input.profile.apply_if_file != ""
not file.exists(input.profile.apply_if_file)
}

allow if {
# Read the file and check if it contains the content
fileStr == trim_space(input.profile.content)
} else if {
# Check if the file exists and the content is left blank
file.exists(input.profile.file)
input.profile.content == ""
}

message := sprintf("Skipping rule because file %v does not exist", [input.profile.apply_if_file]) if {
input.profile.apply_if_file != ""
not file.exists(input.profile.apply_if_file)
} else := sprintf("File %v does not exist", [input.profile.file]) if {
not file.exists(input.profile.file)
} else := sprintf("File %v does not match the expected content %v", [input.profile.file, input.profile.content]) if {
fileStr != trim_space(input.profile.content)
}
remediate:
type: pull_request
pull_request:
title: "Ensure {{.Profile.file }} exists with the expected content"
body: |
This is a Minder automated pull request.

This pull request ensures that this repository contains the file {{.Profile.file}} with the expected content set by your organization.
contents:
- path: "{{.Profile.file}}"
action: replace
content: |
{{.Profile.content}}
Comment on lines +86 to +97
Copy link
Member

Choose a reason for hiding this comment

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

Ah! This is a (current) reason not to use the object formulation -- we don't have a way to iterate over all the keys in the Profile.files map, correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly 👍 Of course, we can try to do that if we want. I was also thinking if I should try and make the path field optional for the pull request remediation type, but at the same time I like explicitly scoping it so you don't accidentally push another change. My concern for not looking at these is both will be considered breaking changes so I think it'll be out of scope for this one.

# Defines the configuration for alerting on the rule
alert:
type: security_advisory
security_advisory: {}
Loading