Skip to content

Commit

Permalink
Add oscal2policy cmd for kyverno to c2pcli
Browse files Browse the repository at this point in the history
  • Loading branch information
yana1205 committed Oct 18, 2023
1 parent cba6526 commit 6fbc7fc
Show file tree
Hide file tree
Showing 16 changed files with 730 additions and 16 deletions.
2 changes: 2 additions & 0 deletions cmd/c2pcli/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/spf13/cobra"

"github.com/IBM/compliance-to-policy/cmd/c2pcli/options"
"github.com/IBM/compliance-to-policy/cmd/c2pcli/subcommands"
composecmd "github.com/IBM/compliance-to-policy/cmd/compose/cmd"
reportutilscmd "github.com/IBM/compliance-to-policy/cmd/report-utils/cmd"
reportcmd "github.com/IBM/compliance-to-policy/cmd/report/cmd"
Expand Down Expand Up @@ -48,6 +49,7 @@ func New() *cobra.Command {
command.AddCommand(composecmd.New())
command.AddCommand(reportcmd.New())
command.AddCommand(reportutilscmd.New())
command.AddCommand(subcommands.NewKyvernoSubCommand())

return command
}
39 changes: 39 additions & 0 deletions cmd/c2pcli/subcommands/kyverno.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2023 IBM Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package subcommands

import (
"github.com/spf13/cobra"

"github.com/IBM/compliance-to-policy/cmd/c2pcli/options"
composecmd "github.com/IBM/compliance-to-policy/cmd/compose-kyverno/cmd"
)

func NewKyvernoSubCommand() *cobra.Command {
opts := options.NewOptions()

command := &cobra.Command{
Use: "kyverno",
Short: "C2P CLI Kyverno plugin",
}

opts.AddFlags(command.Flags())

command.AddCommand(composecmd.New())

return command
}
83 changes: 83 additions & 0 deletions cmd/compose-kyverno/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2023 IBM Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"os"

"github.com/spf13/cobra"

"github.com/IBM/compliance-to-policy/cmd/compose-kyverno/options"
"github.com/IBM/compliance-to-policy/pkg"
"github.com/IBM/compliance-to-policy/pkg/c2pcr"
"github.com/IBM/compliance-to-policy/pkg/kyverno"
typec2pcr "github.com/IBM/compliance-to-policy/pkg/types/c2pcr"
)

func New() *cobra.Command {
opts := options.NewOptions()

command := &cobra.Command{
Use: "compose",
Short: "Compose deliverable Kyverno policies from OSCAL",
RunE: func(cmd *cobra.Command, args []string) error {
if err := opts.Complete(); err != nil {
return err
}

if err := opts.Validate(); err != nil {
return err
}
return Run(opts)
},
}

opts.AddFlags(command.Flags())

return command
}

func Run(options *options.Options) error {
if err := os.MkdirAll(options.OutputDir, os.ModePerm); err != nil {
return err
}

var c2pcrSpec typec2pcr.Spec
if err := pkg.LoadYamlFileToObject(options.C2PCRPath, &c2pcrSpec); err != nil {
return err
}

gitUtils := pkg.NewGitUtils(pkg.NewTempDirectory(options.TempDirPath))
c2pcrParser := c2pcr.NewParser(gitUtils)
c2pcrParsed, err := c2pcrParser.Parse(c2pcrSpec)
if err != nil {
return err
}

tmpdir := pkg.NewTempDirectory(options.TempDirPath)
composer := kyverno.NewComposer(c2pcrParsed.PolicyResoureDir, tmpdir)
if err := composer.Compose(c2pcrParsed); err != nil {
return err
}

if options.OutputDir != "" {
if err := composer.CopyAllTo(options.OutputDir); err != nil {
return err
}
}
return nil
}
30 changes: 30 additions & 0 deletions cmd/compose-kyverno/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright 2023 IBM Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"os"

"github.com/IBM/compliance-to-policy/cmd/compose-kyverno/cmd"
)

func main() {
err := cmd.New().Execute()
if err != nil {
os.Exit(1)
}
}
50 changes: 50 additions & 0 deletions cmd/compose-kyverno/options/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2023 IBM Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package options

import (
"errors"

"github.com/spf13/pflag"
)

type Options struct {
C2PCRPath string
TempDirPath string
OutputDir string
}

func NewOptions() *Options {
return &Options{}
}

func (o *Options) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.C2PCRPath, "c2pcr", "", "path to c2p CR")
fs.StringVar(&o.TempDirPath, "temp-dir", "", "path to temp directory")
fs.StringVar(&o.OutputDir, "out", ".", "path to a directory for output manifest files of generated Kyverno Policy manifests")
}

func (o *Options) Complete() error {
return nil
}

func (o *Options) Validate() error {
if o.C2PCRPath == "" {
return errors.New("--c2pcr is required")
}
return nil
}
50 changes: 34 additions & 16 deletions cmd/tools/subcommands/kyverno/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,20 @@ func New() *cobra.Command {
}

type policyResourceIndex struct {
Kind string
ApiVersion string
Name string
SourcePath string
HasContext bool
Kind string `json:"kind,omitempty"`
ApiVersion string `json:"apiVersion,omitempty"`
Name string `json:"name,omitempty"`
SrcPath string `json:"srcPath,omitempty"`
DestPath string `json:"destPath,omitempty"`
HasContext bool `json:"hasContext,omitempty"`
}

type Summary struct {
ResourcesHavingContext []string `json:"resourcesHavingContext,omitempty"`
}
type Result struct {
PolicyResourceIndice []policyResourceIndex `json:"policyResourceIndice,omitempty"`
Summary Summary `json:"summary,omitempty"`
}

func mapLoadedObject(unstObj *unstructured.Unstructured, path string) *policyResourceIndex {
Expand All @@ -70,7 +79,7 @@ func mapLoadedObject(unstObj *unstructured.Unstructured, path string) *policyRes
ApiVersion: unstObj.GetAPIVersion(),
Kind: unstObj.GetKind(),
Name: name,
SourcePath: path,
SrcPath: path,
}
}

Expand Down Expand Up @@ -165,46 +174,55 @@ func Run(options *Options) error {
return err
}

inverseMap := map[string][]policyResourceIndex{}
for _, pri := range policyResourceIndice {
inverseMap := map[string][]*policyResourceIndex{}
for idx, pri := range policyResourceIndice {
_, found := inverseMap[pri.Name]
if found {
inverseMap[pri.Name] = append(inverseMap[pri.Name], pri)
inverseMap[pri.Name] = append(inverseMap[pri.Name], &policyResourceIndice[idx])
} else {
inverseMap[pri.Name] = []policyResourceIndex{pri}
inverseMap[pri.Name] = []*policyResourceIndex{&policyResourceIndice[idx]}
}
}
for name, pris := range inverseMap {
if len(pris) > 1 {
logger.Warn(fmt.Sprintf("There are duplicate policies for %s", name))
for _, pri := range pris {
logger.Warn(fmt.Sprintf(" - %s", pri.SourcePath))
logger.Warn(fmt.Sprintf(" - %s", pri.SrcPath))
}
}
}

fnameCreator := pkg.NewFilenameCreator("", &pkg.FilenameCreatorOption{UnlabelToZero: true})
for name, pris := range inverseMap {
for _, pri := range pris {
for idx, pri := range pris {
numberedName := fnameCreator.Get(name)
targetDir, err := pkg.MakeDir(destDir + "/" + numberedName)
if err != nil {
return err
}
if err := cp.Copy(pri.SourcePath, targetDir+"/"+pri.Name+".yaml"); err != nil {
logger.Error(fmt.Sprintf("Failed to copy %s", pri.SourcePath))
pris[idx].DestPath = targetDir + "/" + pri.Name + ".yaml"
if err := cp.Copy(pri.SrcPath, pris[idx].DestPath); err != nil {
logger.Error(fmt.Sprintf("Failed to copy %s", pri.SrcPath))
return err
}
}
}

resourcesHavingContext := []string{}
for name, pris := range inverseMap {
for _, pri := range pris {
if pri.HasContext {
println(fmt.Sprintf("%s has 'context' field: source %s", name, pri.SourcePath))
resourcesHavingContext = append(resourcesHavingContext, name)
}
}
}

return nil
result := Result{
PolicyResourceIndice: policyResourceIndice,
Summary: Summary{
ResourcesHavingContext: resourcesHavingContext,
},
}

return pkg.WriteObjToJsonFile(destDir+"/result.json", result)
}
67 changes: 67 additions & 0 deletions pkg/kyverno/composer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2023 IBM Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kyverno

import (
"fmt"

"github.com/IBM/compliance-to-policy/pkg"
typec2pcr "github.com/IBM/compliance-to-policy/pkg/types/c2pcr"
cp "github.com/otiai10/copy"
"go.uber.org/zap"
)

type Composer struct {
policiesDir string
tempDir pkg.TempDirectory
logger *zap.Logger
}

func NewComposer(policiesDir string, tempDir pkg.TempDirectory) *Composer {
return &Composer{
policiesDir: policiesDir,
tempDir: tempDir,
logger: pkg.GetLogger("kyverno/composer"),
}
}

func (c *Composer) Compose(c2pParsed typec2pcr.C2PCRParsed) error {
for _, componentObject := range c2pParsed.ComponentObjects {
if componentObject.ComponentType == "validation" {
continue
}
for _, ruleObject := range componentObject.RuleObjects {
sourceDir := fmt.Sprintf("%s/%s", c.policiesDir, ruleObject.RuleId)
destDir := fmt.Sprintf("%s/%s", c.tempDir.GetTempDir(), ruleObject.RuleId)
err := cp.Copy(sourceDir, destDir)
if err != nil {
return err
}
}
}
return nil
}

func (c *Composer) CopyAllTo(destDir string) error {
if _, err := pkg.MakeDir(destDir); err != nil {
return err
}
if err := cp.Copy(c.tempDir.GetTempDir(), destDir); err != nil {
return err
}
return nil
}
Loading

0 comments on commit 6fbc7fc

Please sign in to comment.