github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/policy/policy.go (about) 1 package policy 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/open-policy-agent/opa/bundle" 12 "golang.org/x/xerrors" 13 "k8s.io/utils/clock" 14 15 "github.com/devseccon/trivy/pkg/fanal/types" 16 "github.com/devseccon/trivy/pkg/log" 17 "github.com/devseccon/trivy/pkg/oci" 18 ) 19 20 const ( 21 BundleVersion = 0 // Latest released MAJOR version for defsec 22 BundleRepository = "ghcr.io/aquasecurity/trivy-policies" 23 policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" 24 updateInterval = 24 * time.Hour 25 ) 26 27 type options struct { 28 artifact *oci.Artifact 29 clock clock.Clock 30 } 31 32 // WithOCIArtifact takes an OCI artifact 33 func WithOCIArtifact(art *oci.Artifact) Option { 34 return func(opts *options) { 35 opts.artifact = art 36 } 37 } 38 39 // WithClock takes a clock 40 func WithClock(c clock.Clock) Option { 41 return func(opts *options) { 42 opts.clock = c 43 } 44 } 45 46 // Option is a functional option 47 type Option func(*options) 48 49 // Client implements policy operations 50 type Client struct { 51 *options 52 policyDir string 53 policyBundleRepo string 54 quiet bool 55 } 56 57 // Metadata holds default policy metadata 58 type Metadata struct { 59 Digest string 60 DownloadedAt time.Time 61 } 62 63 func (m Metadata) String() string { 64 return fmt.Sprintf(`Policy Bundle: 65 Digest: %s 66 DownloadedAt: %s 67 `, m.Digest, m.DownloadedAt.UTC()) 68 } 69 70 // NewClient is the factory method for policy client 71 func NewClient(cacheDir string, quiet bool, policyBundleRepo string, opts ...Option) (*Client, error) { 72 o := &options{ 73 clock: clock.RealClock{}, 74 } 75 76 for _, opt := range opts { 77 opt(o) 78 } 79 80 if policyBundleRepo == "" { 81 policyBundleRepo = fmt.Sprintf("%s:%d", BundleRepository, BundleVersion) 82 } 83 84 return &Client{ 85 options: o, 86 policyDir: filepath.Join(cacheDir, "policy"), 87 policyBundleRepo: policyBundleRepo, 88 quiet: quiet, 89 }, nil 90 } 91 92 func (c *Client) populateOCIArtifact() error { 93 if c.artifact == nil { 94 log.Logger.Debugf("Using URL: %s to load policy bundle", c.policyBundleRepo) 95 art, err := oci.NewArtifact(c.policyBundleRepo, c.quiet, types.RegistryOptions{}) 96 if err != nil { 97 return xerrors.Errorf("OCI artifact error: %w", err) 98 } 99 c.artifact = art 100 } 101 return nil 102 } 103 104 // DownloadBuiltinPolicies download default policies from GitHub Pages 105 func (c *Client) DownloadBuiltinPolicies(ctx context.Context) error { 106 if err := c.populateOCIArtifact(); err != nil { 107 return xerrors.Errorf("OPA bundle error: %w", err) 108 } 109 110 dst := c.contentDir() 111 if err := c.artifact.Download(ctx, dst, oci.DownloadOption{MediaType: policyMediaType}); err != nil { 112 return xerrors.Errorf("download error: %w", err) 113 } 114 115 digest, err := c.artifact.Digest(ctx) 116 if err != nil { 117 return xerrors.Errorf("digest error: %w", err) 118 } 119 log.Logger.Debugf("Digest of the built-in policies: %s", digest) 120 121 // Update metadata.json with the new digest and the current date 122 if err = c.updateMetadata(digest, c.clock.Now()); err != nil { 123 return xerrors.Errorf("unable to update the policy metadata: %w", err) 124 } 125 126 return nil 127 } 128 129 // LoadBuiltinPolicies loads default policies 130 func (c *Client) LoadBuiltinPolicies() ([]string, error) { 131 f, err := os.Open(c.manifestPath()) 132 if err != nil { 133 return nil, xerrors.Errorf("manifest file open error (%s): %w", c.manifestPath(), err) 134 } 135 defer f.Close() 136 137 var manifest bundle.Manifest 138 if err = json.NewDecoder(f).Decode(&manifest); err != nil { 139 return nil, xerrors.Errorf("json decode error (%s): %w", c.manifestPath(), err) 140 } 141 142 // If the "roots" field is not included in the manifest it defaults to [""] 143 // which means that ALL data and policy must come from the bundle. 144 if manifest.Roots == nil || len(*manifest.Roots) == 0 { 145 return []string{c.contentDir()}, nil 146 } 147 148 var policyPaths []string 149 for _, root := range *manifest.Roots { 150 policyPaths = append(policyPaths, filepath.Join(c.contentDir(), root)) 151 } 152 153 return policyPaths, nil 154 } 155 156 // NeedsUpdate returns if the default policy should be updated 157 func (c *Client) NeedsUpdate(ctx context.Context) (bool, error) { 158 meta, err := c.GetMetadata() 159 if err != nil { 160 return true, nil 161 } 162 163 // No need to update if it's been within a day since the last update. 164 if c.clock.Now().Before(meta.DownloadedAt.Add(updateInterval)) { 165 return false, nil 166 } 167 168 if err = c.populateOCIArtifact(); err != nil { 169 return false, xerrors.Errorf("OPA bundle error: %w", err) 170 } 171 172 digest, err := c.artifact.Digest(ctx) 173 if err != nil { 174 return false, xerrors.Errorf("digest error: %w", err) 175 } 176 177 if meta.Digest != digest { 178 return true, nil 179 } 180 181 // Update DownloadedAt with the current time. 182 // Otherwise, if there are no updates in the remote registry, 183 // the digest will be fetched every time even after this. 184 if err = c.updateMetadata(meta.Digest, time.Now()); err != nil { 185 return false, xerrors.Errorf("unable to update the policy metadata: %w", err) 186 } 187 188 return false, nil 189 } 190 191 func (c *Client) contentDir() string { 192 return filepath.Join(c.policyDir, "content") 193 } 194 195 func (c *Client) metadataPath() string { 196 return filepath.Join(c.policyDir, "metadata.json") 197 } 198 199 func (c *Client) manifestPath() string { 200 return filepath.Join(c.contentDir(), bundle.ManifestExt) 201 } 202 203 func (c *Client) updateMetadata(digest string, now time.Time) error { 204 f, err := os.Create(c.metadataPath()) 205 if err != nil { 206 return xerrors.Errorf("failed to open a policy manifest: %w", err) 207 } 208 defer f.Close() 209 210 meta := Metadata{ 211 Digest: digest, 212 DownloadedAt: now, 213 } 214 215 if err = json.NewEncoder(f).Encode(meta); err != nil { 216 return xerrors.Errorf("json encode error: %w", err) 217 } 218 219 return nil 220 } 221 222 func (c *Client) GetMetadata() (*Metadata, error) { 223 f, err := os.Open(c.metadataPath()) 224 if err != nil { 225 log.Logger.Debugf("Failed to open the policy metadata: %s", err) 226 return nil, err 227 } 228 defer f.Close() 229 230 var meta Metadata 231 if err = json.NewDecoder(f).Decode(&meta); err != nil { 232 log.Logger.Warnf("Policy metadata decode error: %s", err) 233 return nil, err 234 } 235 236 return &meta, nil 237 } 238 239 func (c *Client) Clear() error { 240 log.Logger.Info("Removing policy bundle...") 241 if err := os.RemoveAll(c.policyDir); err != nil { 242 return xerrors.Errorf("failed to remove policy bundle: %w", err) 243 } 244 return nil 245 }