internal/policygen/policygen.go (126 lines of code) (raw):
// Copyright 2021 Google LLC
//
// 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 policygen implements the Policy Generator.
package policygen
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/cmd"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/fileutil"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/hcl"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/licenseutil"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/runner"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/template"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/version"
"github.com/hashicorp/terraform/states"
"github.com/otiai10/copy"
)
const (
forsetiOutputRoot = "forseti_policies"
)
// RunArgs is the struct representing the arguments passed to Run().
type RunArgs struct {
ConfigPath string
StatePaths []string
OutputPath string
}
// Run executes main policygen logic.
func Run(ctx context.Context, rn runner.Runner, args *RunArgs) error {
var err error
configPath, err := fileutil.Expand(args.ConfigPath)
if err != nil {
return fmt.Errorf("normalize path %q: %v", args.ConfigPath, err)
}
c, err := loadConfig(configPath)
if err != nil {
return fmt.Errorf("load config: %v", err)
}
compat, err := version.IsCompatible(c.Version)
if err != nil {
return err
}
if !compat {
return fmt.Errorf("binary version %v incompatible with template version constraint %v in %v", cmd.Version, c.Version, configPath)
}
var statePaths []string
for _, p := range args.StatePaths {
p, err = fileutil.Expand(p)
if err != nil {
return fmt.Errorf("normalize path %q: %v", p, err)
}
statePaths = append(statePaths, p)
}
outputPath, err := fileutil.Expand(args.OutputPath)
if err != nil {
return fmt.Errorf("normalize path %q: %v", args.OutputPath, err)
}
cacheDir, err := ioutil.TempDir("", "")
if err != nil {
return err
}
defer os.RemoveAll(cacheDir)
pp, err := fileutil.Fetch(c.TemplateDir, filepath.Dir(args.ConfigPath), cacheDir)
if err != nil {
return fmt.Errorf("resolve policy template path: %v", err)
}
c.TemplateDir = pp
tmpDir, err := ioutil.TempDir("", "")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
// Policy Library templates are released in a backwards compatible way, and old templates will be
// kept in the repository as well, so it's relatively safe to pull from 'master' branch all the time.
tp, err := fileutil.Fetch("github.com/forseti-security/policy-library?ref=master", "", cacheDir)
if err != nil {
return fmt.Errorf("fetch policy templates and utils: %v", err)
}
if err := copy.Copy(filepath.Join(tp, "policies"), filepath.Join(tmpDir, forsetiOutputRoot, "policies")); err != nil {
return err
}
if err := copy.Copy(filepath.Join(tp, "lib"), filepath.Join(tmpDir, forsetiOutputRoot, "lib")); err != nil {
return err
}
if err := generateForsetiPolicies(ctx, rn, statePaths, filepath.Join(tmpDir, forsetiOutputRoot, "policies", "constraints"), c); err != nil {
return fmt.Errorf("generate Forseti policies: %v", err)
}
if err := licenseutil.AddLicense(tmpDir); err != nil {
return fmt.Errorf("add license header: %v", err)
}
if err := hcl.FormatDir(rn, tmpDir); err != nil {
return fmt.Errorf("hcl format: %v", err)
}
if err := os.MkdirAll(outputPath, 0755); err != nil {
return fmt.Errorf("mkdir %q: %v", outputPath, err)
}
return copy.Copy(tmpDir, outputPath)
}
func generateForsetiPolicies(ctx context.Context, rn runner.Runner, statePaths []string, outputPath string, c *config) error {
if c.ForsetiPolicies == nil {
return nil
}
if err := generateGeneralForsetiPolicies(outputPath, c); err != nil {
return fmt.Errorf("generate general forseti policies: %v", err)
}
if err := generateTerraformBasedForsetiPolicies(ctx, rn, statePaths, outputPath, c.TemplateDir); err != nil {
return fmt.Errorf("generate forseti policies from terraform state: %v", err)
}
return nil
}
func generateGeneralForsetiPolicies(outputPath string, c *config) error {
in := filepath.Join(c.TemplateDir, "forseti", "overall")
out := filepath.Join(outputPath, "overall")
return template.WriteDir(in, out, c.ForsetiPolicies)
}
func generateTerraformBasedForsetiPolicies(ctx context.Context, rn runner.Runner, statePaths []string, outputPath, templateDir string) error {
if len(statePaths) == 0 {
log.Println("No Terraform state given, only generating Terraform-agnostic security policies")
return nil
}
var resources []*states.Resource
for _, p := range statePaths {
rs, err := loadResources(ctx, p)
if err != nil {
return err
}
resources = append(resources, rs...)
}
return generateIAMPolicies(rn, resources, outputPath, templateDir)
}