Description
Policy for checking for arbitrary file existence
User Story
As a Security Engineer, so that I can enforce organization-wide policies regarding arbitrary files, I would like a new File Check policy that I may configure to meet my organization's needs.
Overview
OSSF Allstar current includes policies for checking the existence of various specific files. For example, consider SECURITY.md via pkg/policies/security/ ; this policy specifies a constant at security.md line 31 of polName
with a value of SECURITY.md
. In #469, @matias-jt asked if it was possible to verify if a file exists and @ArisBee responded with advice to update the polName
variable. Similarly, @melmos asked in #450 if would be possible for a policy to check for security.json
instead of SECURITY.md
and it was suggested that the two issues may be duplicates with #469 referencing a general use-case while #450 is more specific.
The suggested change in the value of polName
would require recompiling the tool -- that is, the value is set at compile-time, not at configuration-time or run-time.
Current Use-Case
My organization has multiple repositories. We have an organizational policy (governance, not Allstar policy) of requiring repositories to include a configuration files for Pre-Commit, a tool that supports hooks for running detect-secrets, gitleaks, etc.. That is, we want to iterate across all our repos and make sure they all have .pre-commit-config.yaml
files -- basically exactly what Allstar does, but instead of SECURITY.md
, we would look for .pre-commit-config.yaml
files. While we could fork Allstar, we're thinking that it may be possible to help others who are in similar situations (e.g., the aforementioned issues) by generalizing a policy in Allstar that could be configured separately.
Alternative Implementation
High-level Goals
- Allow changing the name of the file to be check at configuration-time
- Allow the policy to check for the existence of absence of a file
- Allow the policy to check in multiple locations
- Allow the policy to check for multiple files
Without getting overly-prescriptive, it may be worthwhile to consider breaking down a new policy into smaller goals and iterate across them instead of trying to do the whole thing at once.
Iterative Steps
Iteration 1: variable filenames
Allow polName
to be set via configuration file. The configuration file would function just like the other tools' configuration files (e.g., security.md line 30 which specifies configFile = "security.yaml"
). The sample quickstart file would be extended to support an additional parameter, such as fileName
. At runtime, instead of referencing a constant, a variable would be extracted from the configuration file. Everything else would remain the same. That is, if the file in question exists, policy compliance is accepted; if it's missing, the repository is non-compliant and the usual remedies (block checks, create issues, etc.) are taken.
It is recommended that basic checks are run to only allow tests for files in the repository, not arbitrary files on the filesystem.
- read
fileName
string from the config file - if
fileName
exists, pass - otherwise fail
Consider the following configuration file:
---
optConfig:
optOutStrategy: true
action: issue
fileName: "SECURITY.md"
Iteration 2: support file absence
Instead of having a policy check receive configuration via a string named fileName
, the tool will accept a map with two keys of fileName
and state
where fileName
is the name of the target file (as before) and state
is either present
or absent
(consider Ansible's ansible.builtin.file module which allows absent
and a variety of potential file types).
File exists? | State | Result |
---|---|---|
Yes | Present | Pass |
Yes | Absent | Fail |
No | Present | Fail |
No | Absent | Pass |
The most significant aspect of this iteration isn't the supporting the absence of a file, it's changing the configuration approach
from accepting a string to accepting a map.
- read
fileCheck
map from the config file - extract
fileName
string fromfileCheck
- extract
state
string fromfileCheck
- if
fileName
exists andstate
==present
, pass - if
filename
doesn't exist andstate
==absent
, pass - otherwise, fail
Consider the following configuration file:
---
optConfig:
optOutStrategy: true
action: issue
fileCheck:
fileName: "SECURITY.md"
state: "present"
Iteration 3: support multiple locations
Instead of having the policy check for a single specific location for a file (e.g., /SECURITY.md
) specified as a string, accept an array of locations to check. In the case of checking for file existence, if any of the locations match, consider the check passed; in checking for file absence, if any of the locations match, conside the check failed. For example, the SECURITY.md file may be located at:
/SECURITY.md
/.github/SECURITY.md
/docs/SECURITY.md
The most significant change here is having the location be specified as an array rather than a string and iterating across the array instead of just checking one location.
- read
fileCheck
map from the config file - extract
fileNames
array fromfileCheck
- extract
state
string fromfileCheck
- iterate across
fileNames
a. ifstate
==present
and any match, pass; otherwise fail
b. ifstate
==absent
and any match, fail; otherwise pass
Consider the following configuration file:
---
optConfig:
optOutStrategy: true
action: issue
fileCheck
fileNames:
- "/SECURITY.md"
- "/.github/SECURITY.md"
- "/docs/SECURITY.md"
state: "present"
Iteration 4: support multiple checks
Currently, there's a 1:1 correspondence with regards to file checks and policies (e.g., the Security policy can't look for a CODEOWNERS file); this iteration supports multiple checks.
Instead of accepting a single map, fileCheck
, accept an array of maps with each item representing the check for
a single file (although it may be in multiple locations).
Consider the following configuration file:
---
optConfig:
optOutStrategy: true
action: issue
files:
- SECURITY.md:
fileNames:
- "/SECURITY.md"
- "/.github/SECURITY.md"
- "/docs/SECURITY.md"
state: "present"
- CODEOWNERS:
fileNames:
- "/CODEOWNERS"
- "/.github/CODEOWNERS"
- "/docs/CODEOWNERS"
state: "present"
- read
files
array from the config file - iterate across
files
array - extract
fileNames
array from iterant - extract
state
string from iterant - iterate across
fileNames
a. ifstate
==present
and any match, pass; otherwise fail
b. ifstate
==absent
and any match, fail; otherwise pass