diff --git a/example/controls/README.md b/example/controls/README.md new file mode 100644 index 0000000..a336148 --- /dev/null +++ b/example/controls/README.md @@ -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 + +``` diff --git a/go.mod b/go.mod index 508dd4a..e06a272 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/internal/model/control.go b/internal/model/control.go new file mode 100644 index 0000000..a34d94a --- /dev/null +++ b/internal/model/control.go @@ -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 +} diff --git a/internal/model/framework.go b/internal/model/framework.go index 3da9cb6..91f79bb 100644 --- a/internal/model/framework.go +++ b/internal/model/framework.go @@ -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 { diff --git a/internal/model/fs.go b/internal/model/fs.go index 2317ebd..f4156b8 100644 --- a/internal/model/fs.go +++ b/internal/model/fs.go @@ -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 diff --git a/internal/model/model.go b/internal/model/model.go index bfc2ce8..4577840 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -4,6 +4,7 @@ type Data struct { Frameworks []*Framework Narratives []*Document Policies []*Document + Controls []*Control Procedures []*Procedure Tickets []*Ticket Audits []*Audit diff --git a/internal/model/policy.go b/internal/model/policy.go new file mode 100644 index 0000000..7d65da7 --- /dev/null +++ b/internal/model/policy.go @@ -0,0 +1,7 @@ +package model + +type Policy struct { + Name string `yaml:"policyName"` + ID string `yaml:"policyID"` + Clause string `yaml:"policyClause"` +} diff --git a/internal/path/path.go b/internal/path/path.go index 3c902d4..db71634 100644 --- a/internal/path/path.go +++ b/internal/path/path.go @@ -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") diff --git a/internal/render/controller.go b/internal/render/controller.go index cd8ee87..85fc435 100644 --- a/internal/render/controller.go +++ b/internal/render/controller.go @@ -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 } diff --git a/internal/render/watch.go b/internal/render/watch.go index 07a6c30..8694b11 100644 --- a/internal/render/watch.go +++ b/internal/render/watch.go @@ -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/") diff --git a/internal/theme/themes_bindata.go b/internal/theme/themes_bindata.go index e5b7f40..affd7ec 100644 --- a/internal/theme/themes_bindata.go +++ b/internal/theme/themes_bindata.go @@ -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{}}, diff --git a/themes/comply-blank/controls/README.md b/themes/comply-blank/controls/README.md new file mode 100644 index 0000000..a336148 --- /dev/null +++ b/themes/comply-blank/controls/README.md @@ -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 + +``` diff --git a/themes/comply-soc2/controls/README.md b/themes/comply-soc2/controls/README.md new file mode 100644 index 0000000..a336148 --- /dev/null +++ b/themes/comply-soc2/controls/README.md @@ -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 + +```