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

fix: Remove all punctuation from anchors in policies.md #565

Merged
merged 1 commit into from
Feb 22, 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
34 changes: 27 additions & 7 deletions examples/policies-no-rego.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* [P1005: Pods must not run with access to the host IPC](#p1005-pods-must-not-run-with-access-to-the-host-ipc)
* [P1006: Pods must not run with access to the host networking](#p1006-pods-must-not-run-with-access-to-the-host-networking)
* [P1007: Pods must not run with access to the host PID namespace](#p1007-pods-must-not-run-with-access-to-the-host-pid-namespace)
* [P1008: Pods must run as non-root](#p1008-pods-must-run-as-non-root)
* [P1008: Pods must run as non\-root](#p1008-pods-must-run-as-non-root)
* [P1009: PodSecurityPolicies must require all capabilities are dropped](#p1009-podsecuritypolicies-must-require-all-capabilities-are-dropped)
* [P1010: PodSecurityPolicies must not allow privileged escalation](#p1010-podsecuritypolicies-must-not-allow-privileged-escalation)
* [P1011: PodSecurityPolicies must not allow access to the host aliases](#p1011-podsecuritypolicies-must-not-allow-access-to-the-host-aliases)
Expand All @@ -21,13 +21,14 @@
* [P2001: Images must not use the latest tag](#p2001-images-must-not-use-the-latest-tag)
* [P2002: Containers must define resource constraints](#p2002-containers-must-define-resource-constraints)
* [P2005: Roles must not allow use of privileged PodSecurityPolicies](#p2005-roles-must-not-allow-use-of-privileged-podsecuritypolicies)
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants'-containers-must-not-run-as-privileged)
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants-containers-must-not-run-as-privileged)

## Warnings

* [P0001: Deprecated Deployment and DaemonSet API](#p0001-deprecated-deployment-and-daemonset-api)
* [P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark ](#p0003-title-_wórds_-with-punct_uation_-mark)
* [P2003: Containers should not have a writable root filesystem](#p2003-containers-should-not-have-a-writable-root-filesystem)
* [P2004: PodSecurityPolicies should require that a read-only root filesystem is set](#p2004-podsecuritypolicies-should-require-that-a-read-only-root-filesystem-is-set)
* [P2004: PodSecurityPolicies should require that a read\-only root filesystem is set](#p2004-podsecuritypolicies-should-require-that-a-read-only-root-filesystem-is-set)

## P0002: Required Labels

Expand Down Expand Up @@ -168,7 +169,7 @@ boundary.

_source: [pod_deny_host_pid](pod_deny_host_pid)_

## P1008: Pods must run as non-root
## P1008: Pods must run as non\-root

**Severity:** Violation

Expand Down Expand Up @@ -341,7 +342,7 @@ _source: [container_deny_without_resource_constraints](container_deny_without_re

**Resources:**

- rbac.authorization.k8s.io/Role
- rbac\.authorization\.k8s\.io/Role

Workloads not running in the exempted namespaces must not use PodSecurityPolicies with privileged permissions.

Expand All @@ -359,7 +360,7 @@ _source: [role_deny_use_privileged_psps](role_deny_use_privileged_psps)_
- apps/Deployment
- apps/StatefulSet

**MatchLabels:** is-tenant=true
**MatchLabels:** is\-tenant=true

Privileged containers can easily escalate to root privileges on the node. As
such containers running as privileged or with sufficient capabilities granted
Expand All @@ -386,6 +387,25 @@ the version for both of these resources must be `apps/v1`.

_source: [any_warn_deprecated_api_versions](any_warn_deprecated_api_versions)_

## P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark

**Severity:** Warning

**Resources:**

- \*/Pod

**MatchLabels:** \_test\_=true

**Parameters:**

* \_param\_name\_: array of string

This is only here to test and illustrate _punctuation_ / Markdown handling


_source: [policy_markdown_punctuation](policy_markdown_punctuation)_

## P2003: Containers should not have a writable root filesystem

**Severity:** Warning
Expand All @@ -403,7 +423,7 @@ important to make the root filesystem read-only.

_source: [container_warn_no_ro_fs](container_warn_no_ro_fs)_

## P2004: PodSecurityPolicies should require that a read-only root filesystem is set
## P2004: PodSecurityPolicies should require that a read\-only root filesystem is set

**Severity:** Warning

Expand Down
50 changes: 43 additions & 7 deletions examples/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* [P1005: Pods must not run with access to the host IPC](#p1005-pods-must-not-run-with-access-to-the-host-ipc)
* [P1006: Pods must not run with access to the host networking](#p1006-pods-must-not-run-with-access-to-the-host-networking)
* [P1007: Pods must not run with access to the host PID namespace](#p1007-pods-must-not-run-with-access-to-the-host-pid-namespace)
* [P1008: Pods must run as non-root](#p1008-pods-must-run-as-non-root)
* [P1008: Pods must run as non\-root](#p1008-pods-must-run-as-non-root)
* [P1009: PodSecurityPolicies must require all capabilities are dropped](#p1009-podsecuritypolicies-must-require-all-capabilities-are-dropped)
* [P1010: PodSecurityPolicies must not allow privileged escalation](#p1010-podsecuritypolicies-must-not-allow-privileged-escalation)
* [P1011: PodSecurityPolicies must not allow access to the host aliases](#p1011-podsecuritypolicies-must-not-allow-access-to-the-host-aliases)
Expand All @@ -21,13 +21,14 @@
* [P2001: Images must not use the latest tag](#p2001-images-must-not-use-the-latest-tag)
* [P2002: Containers must define resource constraints](#p2002-containers-must-define-resource-constraints)
* [P2005: Roles must not allow use of privileged PodSecurityPolicies](#p2005-roles-must-not-allow-use-of-privileged-podsecuritypolicies)
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants'-containers-must-not-run-as-privileged)
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants-containers-must-not-run-as-privileged)

## Warnings

* [P0001: Deprecated Deployment and DaemonSet API](#p0001-deprecated-deployment-and-daemonset-api)
* [P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark ](#p0003-title-_wórds_-with-punct_uation_-mark)
* [P2003: Containers should not have a writable root filesystem](#p2003-containers-should-not-have-a-writable-root-filesystem)
* [P2004: PodSecurityPolicies should require that a read-only root filesystem is set](#p2004-podsecuritypolicies-should-require-that-a-read-only-root-filesystem-is-set)
* [P2004: PodSecurityPolicies should require that a read\-only root filesystem is set](#p2004-podsecuritypolicies-should-require-that-a-read-only-root-filesystem-is-set)

## P0002: Required Labels

Expand Down Expand Up @@ -383,7 +384,7 @@ pod_has_hostpid if {

_source: [pod_deny_host_pid](pod_deny_host_pid)_

## P1008: Pods must run as non-root
## P1008: Pods must run as non\-root

**Severity:** Violation

Expand Down Expand Up @@ -812,7 +813,7 @@ _source: [container_deny_without_resource_constraints](container_deny_without_re

**Resources:**

- rbac.authorization.k8s.io/Role
- rbac\.authorization\.k8s\.io/Role

Workloads not running in the exempted namespaces must not use PodSecurityPolicies with privileged permissions.

Expand Down Expand Up @@ -872,7 +873,7 @@ _source: [role_deny_use_privileged_psps](role_deny_use_privileged_psps)_
- apps/Deployment
- apps/StatefulSet

**MatchLabels:** is-tenant=true
**MatchLabels:** is\-tenant=true

Privileged containers can easily escalate to root privileges on the node. As
such containers running as privileged or with sufficient capabilities granted
Expand Down Expand Up @@ -954,6 +955,41 @@ warn contains msg if {

_source: [any_warn_deprecated_api_versions](any_warn_deprecated_api_versions)_

## P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark

**Severity:** Warning

**Resources:**

- \*/Pod

**MatchLabels:** \_test\_=true

**Parameters:**

* \_param\_name\_: array of string

This is only here to test and illustrate _punctuation_ / Markdown handling

### Rego

```rego
package policy_markdown_punctuation

import data.lib.core
import future.keywords.contains
import future.keywords.if

policyID := "P0003"

warn contains msg if {
core.apiVersion == "foo/bar"
msg := core.format_with_id("Title tester", policyID)
}
```

_source: [policy_markdown_punctuation](policy_markdown_punctuation)_

## P2003: Containers should not have a writable root filesystem

**Severity:** Warning
Expand Down Expand Up @@ -1003,7 +1039,7 @@ no_read_only_filesystem(container) if {

_source: [container_warn_no_ro_fs](container_warn_no_ro_fs)_

## P2004: PodSecurityPolicies should require that a read-only root filesystem is set
## P2004: PodSecurityPolicies should require that a read\-only root filesystem is set

**Severity:** Warning

Expand Down
31 changes: 31 additions & 0 deletions examples/policy_markdown_punctuation/src.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# METADATA
# title: " Title _wórds_ with-punct_uation_ !\"#$%&'()*+,./:;<=>?@[\\]^`{|}~mark "
# description: This is only here to test and illustrate _punctuation_ /
# Markdown handling
# custom:
# matchers:
# kinds:
# - apiGroups:
# - "*"
# kinds:
# - Pod
# labelSelector:
# matchLabels:
# _test_: "true"
# parameters:
# _param_name_:
# type: array
# items:
# type: string
package policy_markdown_punctuation

import data.lib.core
import future.keywords.contains
import future.keywords.if

policyID := "P0003"

warn contains msg if {
core.apiVersion == "foo/bar"
msg := core.format_with_id("Title tester", policyID)
}
7 changes: 7 additions & 0 deletions examples/policy_markdown_punctuation/src_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package policy_markdown_punctuation

import future.keywords.if

test_ignoreme if {
count(warn) == 1 with input as {"apiVersion": "foo/bar"}
}
65 changes: 63 additions & 2 deletions internal/commands/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"text/template"
"unicode"

"github.com/go-sprout/sprout/sprigin"
"github.com/plexsystems/konstraint/internal/rego"
Expand Down Expand Up @@ -39,6 +41,29 @@ type Document struct {
//go:embed document_template.tpl
var docTemplate string

var (
// One or more spaces
multiSpaceRE = regexp.MustCompile(` +`)

// Escape the characters on this list: https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
// (Admittedly, a few of these seem... odd.)
markdownReplacer = strings.NewReplacer(
"\\", "\\\\", "`", "\\`", "*", "\\*", "_", "\\_", "{", "\\{", "}", "\\}", "[", "\\[", "]", "\\]", "<", "\\<",
">", "\\>", "(", "\\(", ")", "\\)", "#", "\\#", "+", "\\+", "-", "\\-", ".", "\\.", "!", "\\!", "|", "\\|",
)

// Space -> -, remove all ASCII punctuation except - and _
//
// (This is part of the GitHub anchor algorithm, but see below regarding tabs and other whitespace. Ref:
// https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links)
anchorReplacer = strings.NewReplacer(
" ", "-",
"!", "", "\"", "", "#", "", "$", "", "%", "", "&", "", "'", "", "(", "", ")", "", "*", "", "+", "", ",", "",
".", "", "/", "", ":", "", ";", "", "<", "", "=", "", ">", "", "?", "", "@", "", "[", "", "\\", "", "]", "",
"^", "", "`", "", "{", "", "|", "", "}", "", "~", "",
)
)

func newDocCommand() *cobra.Command {
cmd := cobra.Command{
Use: "doc <dir>",
Expand Down Expand Up @@ -186,8 +211,37 @@ func getDocumentation(path string, outputDirectory string) (map[rego.Severity][]
documentTitle = fmt.Sprintf("%s: %s", policy.PolicyID(), documentTitle)
}

anchor := strings.ToLower(strings.ReplaceAll(documentTitle, " ", "-"))
anchor = strings.ReplaceAll(anchor, ":", "")
// Tabs in Markdown headings are handled inconsistently across different parsers when it comes to matching
// anchors; some expect them to be removed (which is what GitHub does), while some expect them to be changed to
// -. Changing tabs to spaces beforehand solves that problem, and we'll handle other whitespace the same way
// just in case. Incidentally, handling of other Unicode characters varies; for instance, letters with accents
// are handled normally, but emoji seem to break some parsers, and a circled s (ⓢ, U+24E2) works, but
// markdownlint complains about it. Here, I think, is the place to draw the line in terms of how much
// intervention to do to be sure the anchors work.
var spacedDocumentTitle strings.Builder
for _, rune := range documentTitle {
if unicode.IsSpace(rune) {
spacedDocumentTitle.WriteString(" ")
} else {
spacedDocumentTitle.WriteString(string(rune))
}
}
documentTitle = spacedDocumentTitle.String()

// Similarly, parsers differ in whether they collapse multiple spaces when matching anchors. The safe thing is
// to collapse them before it can become an issue.
documentTitle = multiSpaceRE.ReplaceAllString(documentTitle, " ")

// The GitHub anchor algorithm says that Markdown is removed before conversion. However, '_foo_bar_' counts as
// 'foo_bar' in italics (which are removed), whereas 'foo_bar' is just text. Since only full parsing can
// determine what is actually functional Markdown, it's safest to just escape all of the Markdown characters.
// (That means that Markdown won't work in titles, but it's probably a reasonable tradeoff.) Plus, of course,
// some characters (such as []) would actually break the generated link otherwise.
documentTitle = markdownReplacer.Replace(documentTitle)

// Skip non-U+0020-whitespace and Markdown removal because we handled them above. Ref:
// https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
anchor := anchorReplacer.Replace(strings.TrimSpace(strings.ToLower(documentTitle)))

legacyMatchers, err := policy.Matchers()
if err != nil {
Expand All @@ -209,13 +263,17 @@ func getDocumentation(path string, outputDirectory string) (map[rego.Severity][]
logger.Warn("No kind matchers set, this can lead to poor policy performance.")
matchResources = append(matchResources, "Any Resource")
}
for i := range matchResources {
matchResources[i] = markdownReplacer.Replace(matchResources[i])
}

var matchLabels string
if policy.AnnotationLabelSelectorMatcher() != nil {
matchLabels = labelSelectorDocString(policy.AnnotationLabelSelectorMatcher())
} else {
matchLabels = legacyMatchers.MatchLabelsMatcher.String()
}
matchLabels = markdownReplacer.Replace(matchLabels)

parameters := policy.Parameters()
if len(policy.AnnotationParameters()) > 0 {
Expand All @@ -224,6 +282,9 @@ func getDocumentation(path string, outputDirectory string) (map[rego.Severity][]
sort.Slice(parameters, func(i, j int) bool {
return parameters[i].Name < parameters[j].Name
})
for i := range parameters {
parameters[i].Name = markdownReplacer.Replace(parameters[i].Name)
}

header := Header{
Title: documentTitle,
Expand Down
Loading