1
0
mirror of https://github.com/strongdm/comply synced 2024-12-23 14:07:34 +00:00

Add a model for a Control, which ou can use to document control statements, owners, governing policies, etc.

This commit is contained in:
Craine Runton 2020-09-17 10:38:11 -05:00
parent a66764470c
commit e289d6d8f4
13 changed files with 238 additions and 0 deletions

View File

@ -0,0 +1,33 @@
# Controls
Controls explicitly state a specific action that the organization will take to enforce a Policy goal.
## Format
```
name: Access Control Procedures
family: Access Control
identifier: AC-2
governingPolicy:
- policyName: Access Onboarding and Termination
policyID: SDM-AOTP
policyClause: 1.1
owner: Director, Security & Compliance
published: 2020-01-01
targets:
TSC 2017:
- CC6.1
- CC6.2
- CC6.3
NIST 800-53:
- AC-1
revisions:
- date: Sep 1 2020
comment: Initial documentation of control
---
1. Develop, document, and disseminate to all employees:
1. Organizational access control policy that:
1. Addresses purpose, scope, roles, responsibilities, management commitment, coordination among organizational entities, and compliance; and
```

1
go.mod
View File

@ -33,6 +33,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1
github.com/robfig/cron v1.2.0
github.com/russross/blackfriday/v2 v2.0.1
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/trivago/tgo v1.0.7 // indirect

24
internal/model/control.go Normal file
View File

@ -0,0 +1,24 @@
package model
import (
"time"
"html/template"
)
type Control struct {
Name string `yaml:"name"`
ID string `yaml:"identifier"`
Family string `yaml:"family"`
Owner string `yaml:"owner"`
GoverningPolicy []Policy `yaml:"governingPolicy"`
Revisions []Revision `yaml:"revisions"`
Targets Target `yaml:"targets"`
Published string `yaml:"published"`
FullPath string
OutputFilename string
ModifiedAt time.Time
Body string
BodyHTML template.HTML
}

View File

@ -11,6 +11,8 @@ type Framework struct {
Criteria map[string]Criterion `yaml:",inline"`
}
type Target map[string][]string
// CriteriaSatisfied determines the unique criteria currently satisfied by all Narratives, Policies, and Procedures
func CriteriaSatisfied(data *Data) map[string][]string {
satisfied := make(map[string][]string)
@ -38,6 +40,13 @@ func CriteriaSatisfied(data *Data) map[string][]string {
}
}
}
for _, n := range data.Controls {
for _, criteriaKeys := range n.Targets {
for _, key := range criteriaKeys {
satisfied[key] = appendSatisfaction(satisfied, key, n.OutputFilename)
}
}
}
for _, n := range data.Procedures {
for _, criteriaKeys := range n.Satisfies {
for _, key := range criteriaKeys {

View File

@ -26,6 +26,10 @@ func ReadData() (*Data, error) {
if err != nil {
return nil, err
}
controls, err := ReadControls()
if err != nil {
return nil, err
}
procedures, err := ReadProcedures()
if err != nil {
return nil, err
@ -39,6 +43,7 @@ func ReadData() (*Data, error) {
Tickets: tickets,
Narratives: narratives,
Policies: policies,
Controls: controls,
Procedures: procedures,
Frameworks: frameworks,
}, nil
@ -166,6 +171,31 @@ func ReadPolicies() ([]*Document, error) {
return policies, nil
}
// ReadControls loads control documents from the filesystem
func ReadControls() ([]*Control, error) {
var controls []*Control
files, err := path.Controls()
if err != nil {
return nil, errors.Wrap(err, "unable to enumerate paths")
}
for _, f := range files {
c := &Control{}
mdmd := loadMDMD(f.FullPath)
err = yaml.Unmarshal([]byte(mdmd.yaml), &c)
if err != nil {
return nil, errors.Wrap(err, "unable to parse "+f.FullPath)
}
c.Body = mdmd.body
c.FullPath = f.FullPath
c.ModifiedAt = f.Info.ModTime()
c.OutputFilename = fmt.Sprintf("%s-%s.pdf", config.Config().FilePrefix, c.ID)
controls = append(controls, c)
}
return controls, nil
}
type metadataMarkdown struct {
yaml string
body string

View File

@ -4,6 +4,7 @@ type Data struct {
Frameworks []*Framework
Narratives []*Document
Policies []*Document
Controls []*Control
Procedures []*Procedure
Tickets []*Ticket
Audits []*Audit

7
internal/model/policy.go Normal file
View File

@ -0,0 +1,7 @@
package model
type Policy struct {
Name string `yaml:"policyName"`
ID string `yaml:"policyID"`
Clause string `yaml:"policyClause"`
}

View File

@ -30,6 +30,11 @@ func Policies() ([]File, error) {
return filesFor("policies", "md")
}
// Controls lists all control files.
func Controls() ([]File, error) {
return filesFor("controls", "md")
}
// Procedures lists all procedure files.
func Procedures() ([]File, error) {
return filesFor("procedures", "md")

View File

@ -4,10 +4,13 @@ import (
"fmt"
"sort"
"time"
"html/template"
"github.com/pkg/errors"
"github.com/strongdm/comply/internal/config"
"github.com/strongdm/comply/internal/model"
"github.com/russross/blackfriday/v2"
)
type project struct {
@ -35,6 +38,7 @@ type renderData struct {
Stats *stats
Narratives []*model.Document
Policies []*model.Document
Controls []*model.Control
Procedures []*model.Procedure
Frameworks []*model.Framework
Tickets []*model.Ticket
@ -86,6 +90,7 @@ func load() (*model.Data, *renderData, error) {
rd := &renderData{}
rd.Narratives = modelData.Narratives
rd.Policies = modelData.Policies
rd.Controls = modelData.Controls
rd.Procedures = modelData.Procedures
rd.Frameworks = modelData.Frameworks
rd.Tickets = modelData.Tickets
@ -114,6 +119,12 @@ func loadWithStats() (*model.Data, *renderData, error) {
return nil, nil, err
}
// Convert the markdown body of each control to HTML
for _, n := range modelData.Controls {
b := []byte(n.Body)
n.BodyHTML = template.HTML(blackfriday.Run(b))
}
addStats(modelData, renderData)
return modelData, renderData, nil
}

View File

@ -16,6 +16,7 @@ func watch(errCh chan error) {
b.Add("./templates/")
b.Add("./narratives/")
b.Add("./policies/")
b.Add("./controls/")
b.Add("./procedures/")
b.Add("./.comply/")

View File

@ -2,6 +2,7 @@
// sources:
// themes/comply-blank/README.md
// themes/comply-blank/TODO.md
// themes/comply-blank/controls/README.md
// themes/comply-blank/frameworks/.gitkeep
// themes/comply-blank/narratives/.gitkeep
// themes/comply-blank/policies/.gitkeep
@ -12,6 +13,7 @@
// themes/comply-blank/templates/index.ace
// themes/comply-soc2/README.md
// themes/comply-soc2/TODO.md
// themes/comply-soc2/controls/README.md
// themes/comply-soc2/frameworks/README.md
// themes/comply-soc2/frameworks/TSC-2017.yml
// themes/comply-soc2/narratives/README.md
@ -164,6 +166,26 @@ func complyBlankTodoMd() (*asset, error) {
return a, nil
}
var _complyBlankControlsReadmeMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x52\x5d\x6f\xdb\x30\x0c\x7c\xd7\xaf\x38\xa0\xc0\x9e\xe2\xc0\x4e\xb1\x0f\x78\x4f\x81\x83\x01\x7d\x58\x5b\x20\xf9\x01\x65\x65\xc6\x25\x26\x8b\x82\x24\xb7\xcb\x7e\xfd\x60\xd9\x2d\xb2\xbd\x51\x04\x4f\x77\x3c\xde\x0d\x3a\xf5\x39\xaa\x4b\xc6\xbc\x57\xe0\xdf\xc1\x89\x95\xec\x2e\x48\x99\x32\x83\x90\x02\x5b\x39\x8b\x05\xd9\x2c\xea\x91\x5f\x28\x23\xbf\x30\x34\x0e\xe4\xe5\x0f\x95\xee\x9b\x38\x87\x4c\xbf\x18\x59\xc1\xfe\xac\xd1\xce\xe0\x47\x75\x62\x2f\x18\x94\xdc\xd6\x98\x9b\x1b\xfc\xd0\x38\x52\x36\x4f\x4f\x4f\xc6\xd3\xc8\x2d\xf6\xd6\x72\x4a\xef\x5a\xf0\x18\xd5\x72\x3f\x45\x4e\xe6\x4c\xa3\xb8\xcb\xff\x13\x46\x7a\xf6\x59\xce\xc2\xb1\xc5\xbe\xab\x76\x66\xd0\x57\x8e\x5e\xfc\xb0\x90\xb5\x06\xa8\x10\x4a\x7d\x7f\x4d\xf1\xe0\x9f\x95\x62\x2f\x7e\x00\xf9\x1e\x27\x8e\xa3\xf8\xa2\xde\x00\x58\x11\x77\x87\x16\xc7\xc3\xcf\x6a\xff\x70\x7a\xbc\x6a\x77\x8e\xa6\xc4\x2d\x9a\x6d\x63\xf4\xcd\xcf\xdc\x07\x89\x6c\xb3\xc6\x0d\x8e\x6c\xa7\x28\xf9\x82\x4f\xe8\x74\x0c\x4e\xc8\x5b\x36\x61\x7a\x76\x92\x5e\xb8\x6f\xb1\xab\x77\x75\x55\x37\x55\xdd\x98\x4c\x71\xe0\x9c\x66\x91\xa7\x63\x87\x5d\xdd\x7c\x6d\x0b\x4f\x85\xae\xfb\xb2\x6d\xae\xea\xdd\x55\x7d\x6b\x80\xfb\xbb\xe3\x09\xdf\xea\xba\xfa\x7c\xfb\x0e\xd9\x77\x55\x63\x22\xbf\x4a\x12\xf5\x69\xd9\xbc\xa7\xcc\x2d\x8e\x1c\xd0\x14\xe2\x32\x6a\x75\x1c\xd9\xe7\x16\x77\x5e\xb2\x90\x43\xaf\x76\x9a\x3b\xcb\xf5\xf4\x0c\xbb\xda\x5b\x55\x95\x69\xb6\x38\xf0\x2b\x3b\x0d\x9b\x8f\xc1\x4d\x31\xad\x97\x94\xb8\xd8\x56\x0e\x4d\xce\x81\xc7\xe0\xf4\xc2\x9c\x5a\x53\xa8\x9a\x2d\x1e\xae\x92\x41\x0e\xb4\xf8\xbf\x32\xac\x8e\x96\x1c\xad\x90\x15\xb6\xef\xfb\xc8\x29\x71\x42\x98\x62\xd0\xc4\x1b\x24\xab\x81\x37\x88\xea\x38\x6d\x10\x39\x05\xf5\x49\x9e\xc5\x49\x96\xb9\x33\x92\xa7\x81\x67\x7d\x65\x45\xc9\x8b\x54\xab\x3a\x1f\x7a\x59\x8e\x46\xf5\xc3\x3f\x69\x25\x87\x39\x43\xcb\x17\xf3\x5a\xf6\xe3\x6c\xdf\xe7\xb7\x29\xf9\xfc\x1b\x00\x00\xff\xff\x4a\x40\xb8\xbd\x21\x03\x00\x00")
func complyBlankControlsReadmeMdBytes() ([]byte, error) {
return bindataRead(
_complyBlankControlsReadmeMd,
"comply-blank/controls/README.md",
)
}
func complyBlankControlsReadmeMd() (*asset, error) {
bytes, err := complyBlankControlsReadmeMdBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "comply-blank/controls/README.md", size: 801, mode: os.FileMode(420), modTime: time.Unix(1600355384, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _complyBlankFrameworksGitkeep = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
func complyBlankFrameworksGitkeepBytes() ([]byte, error) {
@ -364,6 +386,26 @@ func complySoc2TodoMd() (*asset, error) {
return a, nil
}
var _complySoc2ControlsReadmeMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x52\x5d\x6f\xdb\x30\x0c\x7c\xd7\xaf\x38\xa0\xc0\x9e\xe2\xc0\x4e\xb1\x0f\x78\x4f\x81\x83\x01\x7d\x58\x5b\x20\xf9\x01\x65\x65\xc6\x25\x26\x8b\x82\x24\xb7\xcb\x7e\xfd\x60\xd9\x2d\xb2\xbd\x51\x04\x4f\x77\x3c\xde\x0d\x3a\xf5\x39\xaa\x4b\xc6\xbc\x57\xe0\xdf\xc1\x89\x95\xec\x2e\x48\x99\x32\x83\x90\x02\x5b\x39\x8b\x05\xd9\x2c\xea\x91\x5f\x28\x23\xbf\x30\x34\x0e\xe4\xe5\x0f\x95\xee\x9b\x38\x87\x4c\xbf\x18\x59\xc1\xfe\xac\xd1\xce\xe0\x47\x75\x62\x2f\x18\x94\xdc\xd6\x98\x9b\x1b\xfc\xd0\x38\x52\x36\x4f\x4f\x4f\xc6\xd3\xc8\x2d\xf6\xd6\x72\x4a\xef\x5a\xf0\x18\xd5\x72\x3f\x45\x4e\xe6\x4c\xa3\xb8\xcb\xff\x13\x46\x7a\xf6\x59\xce\xc2\xb1\xc5\xbe\xab\x76\x66\xd0\x57\x8e\x5e\xfc\xb0\x90\xb5\x06\xa8\x10\x4a\x7d\x7f\x4d\xf1\xe0\x9f\x95\x62\x2f\x7e\x00\xf9\x1e\x27\x8e\xa3\xf8\xa2\xde\x00\x58\x11\x77\x87\x16\xc7\xc3\xcf\x6a\xff\x70\x7a\xbc\x6a\x77\x8e\xa6\xc4\x2d\x9a\x6d\x63\xf4\xcd\xcf\xdc\x07\x89\x6c\xb3\xc6\x0d\x8e\x6c\xa7\x28\xf9\x82\x4f\xe8\x74\x0c\x4e\xc8\x5b\x36\x61\x7a\x76\x92\x5e\xb8\x6f\xb1\xab\x77\x75\x55\x37\x55\xdd\x98\x4c\x71\xe0\x9c\x66\x91\xa7\x63\x87\x5d\xdd\x7c\x6d\x0b\x4f\x85\xae\xfb\xb2\x6d\xae\xea\xdd\x55\x7d\x6b\x80\xfb\xbb\xe3\x09\xdf\xea\xba\xfa\x7c\xfb\x0e\xd9\x77\x55\x63\x22\xbf\x4a\x12\xf5\x69\xd9\xbc\xa7\xcc\x2d\x8e\x1c\xd0\x14\xe2\x32\x6a\x75\x1c\xd9\xe7\x16\x77\x5e\xb2\x90\x43\xaf\x76\x9a\x3b\xcb\xf5\xf4\x0c\xbb\xda\x5b\x55\x95\x69\xb6\x38\xf0\x2b\x3b\x0d\x9b\x8f\xc1\x4d\x31\xad\x97\x94\xb8\xd8\x56\x0e\x4d\xce\x81\xc7\xe0\xf4\xc2\x9c\x5a\x53\xa8\x9a\x2d\x1e\xae\x92\x41\x0e\xb4\xf8\xbf\x32\xac\x8e\x96\x1c\xad\x90\x15\xb6\xef\xfb\xc8\x29\x71\x42\x98\x62\xd0\xc4\x1b\x24\xab\x81\x37\x88\xea\x38\x6d\x10\x39\x05\xf5\x49\x9e\xc5\x49\x96\xb9\x33\x92\xa7\x81\x67\x7d\x65\x45\xc9\x8b\x54\xab\x3a\x1f\x7a\x59\x8e\x46\xf5\xc3\x3f\x69\x25\x87\x39\x43\xcb\x17\xf3\x5a\xf6\xe3\x6c\xdf\xe7\xb7\x29\xf9\xfc\x1b\x00\x00\xff\xff\x4a\x40\xb8\xbd\x21\x03\x00\x00")
func complySoc2ControlsReadmeMdBytes() ([]byte, error) {
return bindataRead(
_complySoc2ControlsReadmeMd,
"comply-soc2/controls/README.md",
)
}
func complySoc2ControlsReadmeMd() (*asset, error) {
bytes, err := complySoc2ControlsReadmeMdBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "comply-soc2/controls/README.md", size: 801, mode: os.FileMode(420), modTime: time.Unix(1600355381, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _complySoc2FrameworksReadmeMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\x8f\x41\x6e\xeb\x30\x0c\x44\xf7\x3e\xc5\x00\x7f\xfb\x1b\xed\xbb\xcb\xa6\xe7\x08\x23\xd1\x16\x13\x49\x34\x48\xba\x86\x6f\x5f\xb8\x41\x8b\xee\x1e\xc0\x47\xcc\xcc\x3f\x7c\x18\x75\xde\xd5\x9e\x3e\x4d\xd7\xd6\x70\x3b\xa8\xb7\x1b\x66\x69\xec\x90\x81\xa8\xe2\x28\x62\x9c\x43\xed\x00\x19\x83\xdc\xb7\xce\x05\xa1\xc8\x3a\x66\xb5\x7e\x62\x8d\x58\xfd\x3d\xa5\x45\xa2\x6e\xf7\x4b\xd6\x9e\x74\xe5\x91\x75\x84\x69\x4b\x9e\x2b\x77\xf2\x14\xc6\x9c\x3a\x79\xb0\xa5\xe7\x4e\x4d\xe6\x23\x79\xd0\x28\x64\x65\x9a\xae\xe5\xb1\x79\x20\x2a\x23\xc8\x16\x0e\xfc\xdc\x30\xab\xbd\xca\xac\xa6\x0f\xce\x81\xfb\x01\x2a\x45\xc6\x02\x35\x18\x77\xfd\x3c\xb9\xc9\xe0\x37\x09\xee\x8e\x5d\xa2\xca\x00\x53\xae\xdf\x7b\xfe\x9f\xe2\xeb\x25\xfd\xfa\xf4\x27\x41\x1a\x83\x47\x88\x71\x3b\x2e\xd3\x57\x00\x00\x00\xff\xff\x04\x1d\x23\xdb\x1c\x01\x00\x00")
func complySoc2FrameworksReadmeMdBytes() ([]byte, error) {
@ -1298,6 +1340,7 @@ func AssetNames() []string {
var _bindata = map[string]func() (*asset, error){
"comply-blank/README.md": complyBlankReadmeMd,
"comply-blank/TODO.md": complyBlankTodoMd,
"comply-blank/controls/README.md": complyBlankControlsReadmeMd,
"comply-blank/frameworks/.gitkeep": complyBlankFrameworksGitkeep,
"comply-blank/narratives/.gitkeep": complyBlankNarrativesGitkeep,
"comply-blank/policies/.gitkeep": complyBlankPoliciesGitkeep,
@ -1308,6 +1351,7 @@ var _bindata = map[string]func() (*asset, error){
"comply-blank/templates/index.ace": complyBlankTemplatesIndexAce,
"comply-soc2/README.md": complySoc2ReadmeMd,
"comply-soc2/TODO.md": complySoc2TodoMd,
"comply-soc2/controls/README.md": complySoc2ControlsReadmeMd,
"comply-soc2/frameworks/README.md": complySoc2FrameworksReadmeMd,
"comply-soc2/frameworks/TSC-2017.yml": complySoc2FrameworksTsc2017Yml,
"comply-soc2/narratives/README.md": complySoc2NarrativesReadmeMd,
@ -1397,6 +1441,9 @@ var _bintree = &bintree{nil, map[string]*bintree{
"comply-blank": &bintree{nil, map[string]*bintree{
"README.md": &bintree{complyBlankReadmeMd, map[string]*bintree{}},
"TODO.md": &bintree{complyBlankTodoMd, map[string]*bintree{}},
"controls": &bintree{nil, map[string]*bintree{
"README.md": &bintree{complyBlankControlsReadmeMd, map[string]*bintree{}},
}},
"frameworks": &bintree{nil, map[string]*bintree{
".gitkeep": &bintree{complyBlankFrameworksGitkeep, map[string]*bintree{}},
}},
@ -1421,6 +1468,9 @@ var _bintree = &bintree{nil, map[string]*bintree{
"comply-soc2": &bintree{nil, map[string]*bintree{
"README.md": &bintree{complySoc2ReadmeMd, map[string]*bintree{}},
"TODO.md": &bintree{complySoc2TodoMd, map[string]*bintree{}},
"controls": &bintree{nil, map[string]*bintree{
"README.md": &bintree{complySoc2ControlsReadmeMd, map[string]*bintree{}},
}},
"frameworks": &bintree{nil, map[string]*bintree{
"README.md": &bintree{complySoc2FrameworksReadmeMd, map[string]*bintree{}},
"TSC-2017.yml": &bintree{complySoc2FrameworksTsc2017Yml, map[string]*bintree{}},

View File

@ -0,0 +1,33 @@
# Controls
Controls explicitly state a specific action that the organization will take to enforce a Policy goal.
## Format
```
name: Access Control Procedures
family: Access Control
identifier: AC-2
governingPolicy:
- policyName: Access Onboarding and Termination
policyID: SDM-AOTP
policyClause: 1.1
owner: Director, Security & Compliance
published: 2020-01-01
targets:
TSC 2017:
- CC6.1
- CC6.2
- CC6.3
NIST 800-53:
- AC-1
revisions:
- date: Sep 1 2020
comment: Initial documentation of control
---
1. Develop, document, and disseminate to all employees:
1. Organizational access control policy that:
1. Addresses purpose, scope, roles, responsibilities, management commitment, coordination among organizational entities, and compliance; and
```

View File

@ -0,0 +1,33 @@
# Controls
Controls explicitly state a specific action that the organization will take to enforce a Policy goal.
## Format
```
name: Access Control Procedures
family: Access Control
identifier: AC-2
governingPolicy:
- policyName: Access Onboarding and Termination
policyID: SDM-AOTP
policyClause: 1.1
owner: Director, Security & Compliance
published: 2020-01-01
targets:
TSC 2017:
- CC6.1
- CC6.2
- CC6.3
NIST 800-53:
- AC-1
revisions:
- date: Sep 1 2020
comment: Initial documentation of control
---
1. Develop, document, and disseminate to all employees:
1. Organizational access control policy that:
1. Addresses purpose, scope, roles, responsibilities, management commitment, coordination among organizational entities, and compliance; and
```