Skip to content

Commit

Permalink
Adding docs generation and example docs files
Browse files Browse the repository at this point in the history
Adds logic to auto-generate a documentation directory and provides two
docs files
  • Loading branch information
MbolotSuse committed Jun 6, 2023
1 parent 2e89c65 commit 9c124a4
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 1 deletion.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ is also rejected.
The Go web-server itself is configured by [dynamiclistener](https://github.com/rancher/dynamiclistener).
It handles TLS certificates and the management of associated Secrets for secure communication of other Rancher components with the Webhook.

## Docs

Documentation on each of the CRDs that are validated can be found in `docs.md`. It is recommended to review the [kubernetes docs on CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) as well.

Docs are added by creating a resource-specific readme in the directory of your mutator/validator (e.x. `pkg/resources/$GROUP/$GROUP_VERSION/$RESOURCE/$READABLE_RESOURCE.MD`).
These files should be named with a human-readable version of the CRD's name.
Running `go generate` will then aggregate these into the user-facing docs in the `docs.md` file.

## Webhooks
Rancher-Webhook is composed of multiple [WebhookHandlers](pkg/admission/admission.go) which is used when creating [ValidatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#validatingwebhook-v1-admissionregistration-k8s-io) and [MutatingWebhooks](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#mutatingwebhook-v1-admissionregistration-k8s-io).

Expand Down
24 changes: 24 additions & 0 deletions docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# management.cattle.io/v3

## GlobalRole

### Validation Checks
Note: all checks are bypassed if the GlobalRole is being deleted

#### Escalation Prevention
Users can only change GlobalRoles which have less permissions than they do. This is to prevents privilege escalation.

## RoleTemplate

### Validation Checks
Note: all checks are bypassed if the RoleTemplate is being deleted

#### Circular Reference
Circular references to webhooks (a inherits b, b inherits a) are not allowed. More specifically, if "roleTemplate1" is included in the `roleTemplateNames` of "roleTemplate2", then "roleTemplate2" must not be included in the `roleTemplateNames` of "roleTemplate1". This checks prevents the creation of roles whose end-state cannot be resolved.

#### Rules Without Verbs
Rules without verbs are not peritted. The `rules` included in a roleTemplate are of the same type as the rules used by standard kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one.

#### Escalation Prevention
Users can only change RoleTemplates which have less permissions than they do. This prevents privilege escalation.

2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:generate go run pkg/codegen/cleanup/main.go
//go:generate go run pkg/codegen/main.go pkg/codegen/template.go
//go:generate go run pkg/codegen/main.go pkg/codegen/template.go pkg/codegen/docs.go
package main

import (
Expand Down
4 changes: 4 additions & 0 deletions pkg/codegen/cleanup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ func main() {
if err := os.RemoveAll("./pkg/generated"); err != nil {
logrus.Fatal(err)
}
// if we don't have the docs file no need to clean it up
if err := os.Remove("./docs.md"); err != nil && !os.IsNotExist(err) {
logrus.Fatal(err)
}
}
117 changes: 117 additions & 0 deletions pkg/codegen/docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"

"golang.org/x/exp/slices"
)

// docFileName defines the name of the files that will be aggregated into overall docs
const docFileExtension = ".md"

type docFile struct {
content []byte
resource string
group string
version string
}

func generateDocs(resourcesBaseDir, outputFilePath string) error {
outputFile, err := os.OpenFile(outputFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
docFiles, err := getDocFiles(resourcesBaseDir)
if err != nil {
return fmt.Errorf("unable to create documentation: %w", err)
}
currentGroup := ""
for _, docFile := range docFiles {
newGroup := docFile.group
if newGroup != currentGroup {
// our group has changed, output a new group header
_, err = fmt.Fprintf(outputFile, "# %s/%s \n \n", docFile.group, docFile.version)
if err != nil {
return fmt.Errorf("unable to write group header for %s/%s: %w", docFile.group, docFile.version, err)
}
currentGroup = newGroup
}

_, err = fmt.Fprintf(outputFile, "## %s \n\n", docFile.resource)
if err != nil {
return fmt.Errorf("unable to write resource header for %s: %w", docFile.resource, err)
}

lines := strings.Split(string(docFile.content), "\n")
for i, line := range lines {
newLine := line
if i < len(lines)-1 {
// last line doesn't need a newLine re-added
newLine += "\n"
}
if strings.HasPrefix(line, "#") {
// this line is a markdown header. Since the group header is the top-level indent, indent this down one line
newLine = "#" + line
}
_, err := outputFile.WriteString(newLine)
if err != nil {
return fmt.Errorf("unable to write content for %s/%s.%s: %w", docFile.group, docFile.version, docFile.resource, err)
}
}
}
return nil
}

// getDocFiles finds all markdown files recursively in resourcesBaseDir and converts them to docFiles. Returns in a sorted order,
// first by group, then by resourceName
func getDocFiles(baseDir string) ([]docFile, error) {
entries, err := os.ReadDir(baseDir)
if err != nil {
return nil, fmt.Errorf("unable to list entries in directory %s: %w", baseDir, err)
}
var docFiles []docFile
for _, entry := range entries {
entryPath := filepath.Join(baseDir, entry.Name())
if entry.IsDir() {
subDocFiles, err := getDocFiles(entryPath)
if err != nil {
return nil, err
}
docFiles = append(docFiles, subDocFiles...)
}
if filepath.Ext(entry.Name()) == docFileExtension {
content, err := os.ReadFile(filepath.Join(baseDir, entry.Name()))
if err != nil {
return nil, fmt.Errorf("unable to read file content for %s: %w", entryPath, err)
}
var newDir, resource, version, group string
newDir, _ = filepath.Split(baseDir)
newDir, version = filepath.Split(newDir[:len(newDir)-1])
newDir, group = filepath.Split(newDir[:len(newDir)-1])
resource = strings.TrimSuffix(entry.Name(), docFileExtension)
if newDir == "" || resource == "" || version == "" || group == "" {
return nil, fmt.Errorf("unable to extract gvr from %s, got group %s, version %s, resource %s", baseDir, group, version, resource)
}
docFiles = append(docFiles, docFile{
content: content,
resource: resource,
group: group,
version: version,
})
}
}
// if the groups differ, sort based on the group. If the groups are the same, sort based on the resource
slices.SortFunc(docFiles, func(a, b docFile) bool {
if a.group < b.group {
return true
} else if a.group == b.group {
return a.resource < b.resource
}
return false
})

return docFiles, nil
}
4 changes: 4 additions & 0 deletions pkg/codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ type typeInfo struct {

func main() {
os.Unsetenv("GOPATH")
err := generateDocs("pkg/resources", "docs.md")
if err != nil {
panic(err)
}
controllergen.Run(args.Options{
OutputPackage: "github.com/rancher/webhook/pkg/generated",
Boilerplate: "scripts/boilerplate.go.txt",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Validation Checks

Note: all checks are bypassed if the GlobalRole is being deleted

### Escalation Prevention

Users can only change GlobalRoles which have less permissions than they do. This is to prevents privilege escalation.

16 changes: 16 additions & 0 deletions pkg/resources/management.cattle.io/v3/roletemplate/RoleTemplate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Validation Checks

Note: all checks are bypassed if the RoleTemplate is being deleted

### Circular Reference

Circular references to webhooks (a inherits b, b inherits a) are not allowed. More specifically, if "roleTemplate1" is included in the `roleTemplateNames` of "roleTemplate2", then "roleTemplate2" must not be included in the `roleTemplateNames` of "roleTemplate1". This checks prevents the creation of roles whose end-state cannot be resolved.

### Rules Without Verbs

Rules without verbs are not peritted. The `rules` included in a roleTemplate are of the same type as the rules used by standard kubernetes RBAC types (such as `Roles` from `rbac.authorization.k8s.io/v1`). Because of this, they inherit the same restrictions as these types, including this one.

### Escalation Prevention

Users can only change RoleTemplates which have less permissions than they do. This prevents privilege escalation.

0 comments on commit 9c124a4

Please sign in to comment.