Skip to content

Commit 88544fb

Browse files
committed
Remove all punctuation from anchors in policies.md
1 parent be7edd7 commit 88544fb

File tree

5 files changed

+171
-16
lines changed

5 files changed

+171
-16
lines changed

examples/policies-no-rego.md

+27-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* [P1005: Pods must not run with access to the host IPC](#p1005-pods-must-not-run-with-access-to-the-host-ipc)
1111
* [P1006: Pods must not run with access to the host networking](#p1006-pods-must-not-run-with-access-to-the-host-networking)
1212
* [P1007: Pods must not run with access to the host PID namespace](#p1007-pods-must-not-run-with-access-to-the-host-pid-namespace)
13-
* [P1008: Pods must run as non-root](#p1008-pods-must-run-as-non-root)
13+
* [P1008: Pods must run as non\-root](#p1008-pods-must-run-as-non-root)
1414
* [P1009: PodSecurityPolicies must require all capabilities are dropped](#p1009-podsecuritypolicies-must-require-all-capabilities-are-dropped)
1515
* [P1010: PodSecurityPolicies must not allow privileged escalation](#p1010-podsecuritypolicies-must-not-allow-privileged-escalation)
1616
* [P1011: PodSecurityPolicies must not allow access to the host aliases](#p1011-podsecuritypolicies-must-not-allow-access-to-the-host-aliases)
@@ -21,13 +21,14 @@
2121
* [P2001: Images must not use the latest tag](#p2001-images-must-not-use-the-latest-tag)
2222
* [P2002: Containers must define resource constraints](#p2002-containers-must-define-resource-constraints)
2323
* [P2005: Roles must not allow use of privileged PodSecurityPolicies](#p2005-roles-must-not-allow-use-of-privileged-podsecuritypolicies)
24-
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants'-containers-must-not-run-as-privileged)
24+
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants-containers-must-not-run-as-privileged)
2525

2626
## Warnings
2727

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

3233
## P0002: Required Labels
3334

@@ -168,7 +169,7 @@ boundary.
168169

169170
_source: [pod_deny_host_pid](pod_deny_host_pid)_
170171

171-
## P1008: Pods must run as non-root
172+
## P1008: Pods must run as non\-root
172173

173174
**Severity:** Violation
174175

@@ -341,7 +342,7 @@ _source: [container_deny_without_resource_constraints](container_deny_without_re
341342

342343
**Resources:**
343344

344-
- rbac.authorization.k8s.io/Role
345+
- rbac\.authorization\.k8s\.io/Role
345346

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

@@ -359,7 +360,7 @@ _source: [role_deny_use_privileged_psps](role_deny_use_privileged_psps)_
359360
- apps/Deployment
360361
- apps/StatefulSet
361362

362-
**MatchLabels:** is-tenant=true
363+
**MatchLabels:** is\-tenant=true
363364

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

387388
_source: [any_warn_deprecated_api_versions](any_warn_deprecated_api_versions)_
388389

390+
## P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark
391+
392+
**Severity:** Warning
393+
394+
**Resources:**
395+
396+
- \*/Pod
397+
398+
**MatchLabels:** \_test\_=true
399+
400+
**Parameters:**
401+
402+
* \_param\_name\_: array of string
403+
404+
This is only here to test and illustrate _punctuation_ / Markdown handling
405+
406+
407+
_source: [policy_markdown_punctuation](policy_markdown_punctuation)_
408+
389409
## P2003: Containers should not have a writable root filesystem
390410

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

404424
_source: [container_warn_no_ro_fs](container_warn_no_ro_fs)_
405425

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

408428
**Severity:** Warning
409429

examples/policies.md

+43-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* [P1005: Pods must not run with access to the host IPC](#p1005-pods-must-not-run-with-access-to-the-host-ipc)
1111
* [P1006: Pods must not run with access to the host networking](#p1006-pods-must-not-run-with-access-to-the-host-networking)
1212
* [P1007: Pods must not run with access to the host PID namespace](#p1007-pods-must-not-run-with-access-to-the-host-pid-namespace)
13-
* [P1008: Pods must run as non-root](#p1008-pods-must-run-as-non-root)
13+
* [P1008: Pods must run as non\-root](#p1008-pods-must-run-as-non-root)
1414
* [P1009: PodSecurityPolicies must require all capabilities are dropped](#p1009-podsecuritypolicies-must-require-all-capabilities-are-dropped)
1515
* [P1010: PodSecurityPolicies must not allow privileged escalation](#p1010-podsecuritypolicies-must-not-allow-privileged-escalation)
1616
* [P1011: PodSecurityPolicies must not allow access to the host aliases](#p1011-podsecuritypolicies-must-not-allow-access-to-the-host-aliases)
@@ -21,13 +21,14 @@
2121
* [P2001: Images must not use the latest tag](#p2001-images-must-not-use-the-latest-tag)
2222
* [P2002: Containers must define resource constraints](#p2002-containers-must-define-resource-constraints)
2323
* [P2005: Roles must not allow use of privileged PodSecurityPolicies](#p2005-roles-must-not-allow-use-of-privileged-podsecuritypolicies)
24-
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants'-containers-must-not-run-as-privileged)
24+
* [P2006: Tenants' containers must not run as privileged](#p2006-tenants-containers-must-not-run-as-privileged)
2525

2626
## Warnings
2727

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

3233
## P0002: Required Labels
3334

@@ -383,7 +384,7 @@ pod_has_hostpid if {
383384

384385
_source: [pod_deny_host_pid](pod_deny_host_pid)_
385386

386-
## P1008: Pods must run as non-root
387+
## P1008: Pods must run as non\-root
387388

388389
**Severity:** Violation
389390

@@ -812,7 +813,7 @@ _source: [container_deny_without_resource_constraints](container_deny_without_re
812813

813814
**Resources:**
814815

815-
- rbac.authorization.k8s.io/Role
816+
- rbac\.authorization\.k8s\.io/Role
816817

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

@@ -872,7 +873,7 @@ _source: [role_deny_use_privileged_psps](role_deny_use_privileged_psps)_
872873
- apps/Deployment
873874
- apps/StatefulSet
874875

875-
**MatchLabels:** is-tenant=true
876+
**MatchLabels:** is\-tenant=true
876877

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

955956
_source: [any_warn_deprecated_api_versions](any_warn_deprecated_api_versions)_
956957

958+
## P0003: Title \_wórds\_ with\-punct\_uation\_ \!"\#$%&'\(\)\*\+,\./:;\<=\>?@\[\\\]^\`\{\|\}~mark
959+
960+
**Severity:** Warning
961+
962+
**Resources:**
963+
964+
- \*/Pod
965+
966+
**MatchLabels:** \_test\_=true
967+
968+
**Parameters:**
969+
970+
* \_param\_name\_: array of string
971+
972+
This is only here to test and illustrate _punctuation_ / Markdown handling
973+
974+
### Rego
975+
976+
```rego
977+
package policy_markdown_punctuation
978+
979+
import data.lib.core
980+
import future.keywords.contains
981+
import future.keywords.if
982+
983+
policyID := "P0003"
984+
985+
warn contains msg if {
986+
core.apiVersion == "foo/bar"
987+
msg := core.format_with_id("Title tester", policyID)
988+
}
989+
```
990+
991+
_source: [policy_markdown_punctuation](policy_markdown_punctuation)_
992+
957993
## P2003: Containers should not have a writable root filesystem
958994

959995
**Severity:** Warning
@@ -1003,7 +1039,7 @@ no_read_only_filesystem(container) if {
10031039

10041040
_source: [container_warn_no_ro_fs](container_warn_no_ro_fs)_
10051041

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

10081044
**Severity:** Warning
10091045

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# METADATA
2+
# title: " Title _wórds_ with-punct_uation_ !\"#$%&'()*+,./:;<=>?@[\\]^`{|}~mark "
3+
# description: This is only here to test and illustrate _punctuation_ /
4+
# Markdown handling
5+
# custom:
6+
# matchers:
7+
# kinds:
8+
# - apiGroups:
9+
# - "*"
10+
# kinds:
11+
# - Pod
12+
# labelSelector:
13+
# matchLabels:
14+
# _test_: "true"
15+
# parameters:
16+
# _param_name_:
17+
# type: array
18+
# items:
19+
# type: string
20+
package policy_markdown_punctuation
21+
22+
import data.lib.core
23+
import future.keywords.contains
24+
import future.keywords.if
25+
26+
policyID := "P0003"
27+
28+
warn contains msg if {
29+
core.apiVersion == "foo/bar"
30+
msg := core.format_with_id("Title tester", policyID)
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package policy_markdown_punctuation
2+
3+
import future.keywords.if
4+
5+
test_ignoreme if {
6+
count(warn) == 1 with input as {"apiVersion": "foo/bar"}
7+
}

internal/commands/document.go

+63-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"regexp"
89
"sort"
910
"strings"
1011
"text/template"
12+
"unicode"
1113

1214
"github.com/go-sprout/sprout/sprigin"
1315
"github.com/plexsystems/konstraint/internal/rego"
@@ -39,6 +41,29 @@ type Document struct {
3941
//go:embed document_template.tpl
4042
var docTemplate string
4143

44+
var (
45+
// One or more spaces
46+
multiSpaceRE = regexp.MustCompile(` +`)
47+
48+
// Escape the characters on this list: https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
49+
// (Admittedly, a few of these seem... odd.)
50+
markdownReplacer = strings.NewReplacer(
51+
"\\", "\\\\", "`", "\\`", "*", "\\*", "_", "\\_", "{", "\\{", "}", "\\}", "[", "\\[", "]", "\\]", "<", "\\<",
52+
">", "\\>", "(", "\\(", ")", "\\)", "#", "\\#", "+", "\\+", "-", "\\-", ".", "\\.", "!", "\\!", "|", "\\|",
53+
)
54+
55+
// Space -> -, remove all ASCII punctuation except - and _
56+
//
57+
// (This is part of the GitHub anchor algorithm, but see below regarding tabs and other whitespace. Ref:
58+
// 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)
59+
anchorReplacer = strings.NewReplacer(
60+
" ", "-",
61+
"!", "", "\"", "", "#", "", "$", "", "%", "", "&", "", "'", "", "(", "", ")", "", "*", "", "+", "", ",", "",
62+
".", "", "/", "", ":", "", ";", "", "<", "", "=", "", ">", "", "?", "", "@", "", "[", "", "\\", "", "]", "",
63+
"^", "", "`", "", "{", "", "|", "", "}", "", "~", "",
64+
)
65+
)
66+
4267
func newDocCommand() *cobra.Command {
4368
cmd := cobra.Command{
4469
Use: "doc <dir>",
@@ -186,8 +211,37 @@ func getDocumentation(path string, outputDirectory string) (map[rego.Severity][]
186211
documentTitle = fmt.Sprintf("%s: %s", policy.PolicyID(), documentTitle)
187212
}
188213

189-
anchor := strings.ToLower(strings.ReplaceAll(documentTitle, " ", "-"))
190-
anchor = strings.ReplaceAll(anchor, ":", "")
214+
// Tabs in Markdown headings are handled inconsistently across different parsers when it comes to matching
215+
// anchors; some expect them to be removed (which is what GitHub does), while some expect them to be changed to
216+
// -. Changing tabs to spaces beforehand solves that problem, and we'll handle other whitespace the same way
217+
// just in case. Incidentally, handling of other Unicode characters varies; for instance, letters with accents
218+
// are handled normally, but emoji seem to break some parsers, and a circled s (ⓢ, U+24E2) works, but
219+
// markdownlint complains about it. Here, I think, is the place to draw the line in terms of how much
220+
// intervention to do to be sure the anchors work.
221+
var spacedDocumentTitle strings.Builder
222+
for _, rune := range documentTitle {
223+
if unicode.IsSpace(rune) {
224+
spacedDocumentTitle.WriteString(" ")
225+
} else {
226+
spacedDocumentTitle.WriteString(string(rune))
227+
}
228+
}
229+
documentTitle = spacedDocumentTitle.String()
230+
231+
// Similarly, parsers differ in whether they collapse multiple spaces when matching anchors. The safe thing is
232+
// to collapse them before it can become an issue.
233+
documentTitle = multiSpaceRE.ReplaceAllString(documentTitle, " ")
234+
235+
// The GitHub anchor algorithm says that Markdown is removed before conversion. However, '_foo_bar_' counts as
236+
// 'foo_bar' in italics (which are removed), whereas 'foo_bar' is just text. Since only full parsing can
237+
// determine what is actually functional Markdown, it's safest to just escape all of the Markdown characters.
238+
// (That means that Markdown won't work in titles, but it's probably a reasonable tradeoff.) Plus, of course,
239+
// some characters (such as []) would actually break the generated link otherwise.
240+
documentTitle = markdownReplacer.Replace(documentTitle)
241+
242+
// Skip non-U+0020-whitespace and Markdown removal because we handled them above. Ref:
243+
// 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
244+
anchor := anchorReplacer.Replace(strings.TrimSpace(strings.ToLower(documentTitle)))
191245

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

213270
var matchLabels string
214271
if policy.AnnotationLabelSelectorMatcher() != nil {
215272
matchLabels = labelSelectorDocString(policy.AnnotationLabelSelectorMatcher())
216273
} else {
217274
matchLabels = legacyMatchers.MatchLabelsMatcher.String()
218275
}
276+
matchLabels = markdownReplacer.Replace(matchLabels)
219277

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

228289
header := Header{
229290
Title: documentTitle,

0 commit comments

Comments
 (0)