Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Lorenzo Susini <susinilorenzo1@gmail.com>
  • Loading branch information
loresuso committed Feb 3, 2023
1 parent 5af39d3 commit 9dd6b8d
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 6 deletions.
19 changes: 19 additions & 0 deletions cmd/artifact/follow/follow.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/falcosecurity/falcoctl/internal/follower"
"github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/index"
"github.com/falcosecurity/falcoctl/pkg/oci"
"github.com/falcosecurity/falcoctl/pkg/options"
"github.com/falcosecurity/falcoctl/pkg/output"
)
Expand Down Expand Up @@ -88,6 +89,7 @@ type artifactFollowOptions struct {
versions config.FalcoVersions
timeout time.Duration
closeChan chan bool
allowedTypes oci.ArtifactTypeSlice
}

// NewArtifactFollowCmd returns the artifact follow command.
Expand Down Expand Up @@ -184,6 +186,21 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.CommonOptions) *cobr
}
}

// Override "allowed-types" flag with viper config if not set by user.
f = cmd.Flags().Lookup("allowed-types")
if f == nil {
// should never happen
o.Printer.CheckErr(fmt.Errorf("unable to retrieve flag allowed-types"))
} else if !f.Changed && viper.IsSet(config.ArtifactAllowedTypesKey) {
val, err := config.ArtifactAllowedTypes()
if err != nil {
o.Printer.CheckErr(err)
}
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
o.Printer.CheckErr(fmt.Errorf("unable to overwrite \"allowed-types\" flag: %w", err))
}
}

if o.every != 0 && o.cron != "" {
o.Printer.CheckErr(fmt.Errorf("can't set both \"cron\" and \"every\" flags"))
}
Expand Down Expand Up @@ -214,6 +231,7 @@ func NewArtifactFollowCmd(ctx context.Context, opt *options.CommonOptions) *cobr
"Where to retrieve versions, it can be either an URL or a path to a file")
cmd.Flags().DurationVar(&o.timeout, "timeout", defaultBackoffConfig.MaxDelay,
"Timeout for initial connection to the Falco versions endpoint")
cmd.Flags().Var(&o.allowedTypes, "allowed-types", "whitelist of artifacts type that can be followed")
return cmd
}

Expand Down Expand Up @@ -285,6 +303,7 @@ func (o *artifactFollowOptions) RunArtifactFollow(ctx context.Context, args []st
CloseChan: o.closeChan,
TmpDir: o.tmpDir,
FalcoVersions: o.versions,
AllowedTypes: o.allowedTypes,
}
fol, err := follower.New(ctx, ref, o.Printer, cfg)
if err != nil {
Expand Down
23 changes: 22 additions & 1 deletion cmd/artifact/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type artifactInstallOptions struct {
*options.RegistryOptions
rulesfilesDir string
pluginsDir string
allowedTypes oci.ArtifactTypeSlice
}

// NewArtifactInstallCmd returns the artifact install command.
Expand Down Expand Up @@ -117,6 +118,21 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.CommonOptions) *cob
if err := utils.ExistsAndIsWritable(f.Value.String()); err != nil {
o.Printer.CheckErr(fmt.Errorf("plugins-dir: %w", err))
}

// Override "allowed-types" flag with viper config if not set by user.
f = cmd.Flags().Lookup("allowed-types")
if f == nil {
// should never happen
o.Printer.CheckErr(fmt.Errorf("unable to retrieve flag allowed-types"))
} else if !f.Changed && viper.IsSet(config.ArtifactAllowedTypesKey) {
val, err := config.ArtifactAllowedTypes()
if err != nil {
o.Printer.CheckErr(err)
}
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
o.Printer.CheckErr(fmt.Errorf("unable to overwrite \"allowed-types\" flag: %w", err))
}
}
},
Run: func(cmd *cobra.Command, args []string) {
o.Printer.CheckErr(o.RunArtifactInstall(ctx, args))
Expand All @@ -128,6 +144,7 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.CommonOptions) *cob
"directory where to install rules. Defaults to /etc/falco")
cmd.Flags().StringVarP(&o.pluginsDir, "plugins-dir", "", config.PluginsDir,
"directory where to install plugins. Defaults to /usr/share/falco/plugins")
cmd.Flags().Var(&o.allowedTypes, "allowed-types", "whitelist of artifacts type that can be installed")

return cmd
}
Expand All @@ -137,7 +154,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
// Retrieve configuration for installer
configuredInstaller, err := config.Installer()
if err != nil {
o.Printer.CheckErr(fmt.Errorf("unable to retrieved the configured installer: %w", err))
o.Printer.CheckErr(fmt.Errorf("unable to retrieve the configured installer: %w", err))
}

// Set args as configured if no arg was passed
Expand Down Expand Up @@ -233,6 +250,10 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
return err
}

if err := puller.CheckAllowedType(ctx, ref, o.allowedTypes.Types); err != nil {
return err
}

// Install will always install artifact for the current OS and architecture
result, err := puller.Pull(ctx, ref, tmpDir, runtime.GOOS, runtime.GOARCH)
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"github.com/docker/docker/pkg/homedir"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"

"github.com/falcosecurity/falcoctl/pkg/oci"
)

var (
Expand Down Expand Up @@ -92,6 +94,8 @@ const (
ArtifactInstallRulesfilesDirKey = "artifact.install.rulesfilesdir"
// ArtifactInstallPluginsDirKey is the Viper key for follower "pluginsDir" configuration.
ArtifactInstallPluginsDirKey = "artifact.install.pluginsdir"
// ArtifactAllowedTypesKey is the Viper key for the whitelist of artifacts to be installed in the system.
ArtifactAllowedTypesKey = "artifact.allowedTypes"
)

// Index represents a configured index.
Expand Down Expand Up @@ -161,6 +165,9 @@ func Load(path string) error {
// Set default index
viper.SetDefault(IndexesKey, []Index{DefaultIndex})

// Set default artifact types
viper.SetDefault(ArtifactAllowedTypesKey, []string{oci.Rulesfile.String()})

err = viper.ReadInConfig()
if errors.As(err, &viper.ConfigFileNotFoundError{}) || os.IsNotExist(err) {
// If the config is not found, we create the file with the
Expand Down Expand Up @@ -414,6 +421,32 @@ func Installer() (Install, error) {
}, nil
}

// ArtifactAllowedTypes retrieves the allowed types section of the config file.
func ArtifactAllowedTypes() (*oci.ArtifactTypeSlice, error) {
allowedTypes := viper.GetStringSlice(ArtifactAllowedTypesKey)
if len(allowedTypes) == 1 { // in this case it might come from the env
if !CommaSeparatedRegexp.MatchString(allowedTypes[0]) {
return nil, fmt.Errorf("env variable not correctly set, should match %q, got %q", SemicolonSeparatedRegexp.String(), allowedTypes[0])
}
allowedTypes = strings.Split(allowedTypes[0], ",")
}

var allowedArtifactTypes []oci.ArtifactType
for _, t := range allowedTypes {
var at oci.ArtifactType
if err := at.Set(t); err != nil {
return nil, fmt.Errorf("unrecognized artifact type in config: %q", t)
}

allowedArtifactTypes = append(allowedArtifactTypes, at)
}

return &oci.ArtifactTypeSlice{
Types: allowedArtifactTypes,
CommaSeparatedString: strings.Join(allowedTypes, ","),
}, nil
}

// UpdateConfigFile is used to update a section of the config file.
// We create a brand new viper instance for doing it so that we are sure that modifications
// are scoped to the passed key with no side effects (e.g user forgot to unset one env variable for
Expand Down
7 changes: 7 additions & 0 deletions internal/follower/follower.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type Config struct {
// FalcoVersions is a struct containing all the required Falco versions that this follower
// has to take into account when installing artifacts.
FalcoVersions config.FalcoVersions
// AllowedTypes specify a list of artifacts that we are allowed to download.
AllowedTypes oci.ArtifactTypeSlice
}

var (
Expand Down Expand Up @@ -236,6 +238,11 @@ func (f *Follower) follow(ctx context.Context) {

// pull downloads, extracts, and installs the artifact.
func (f *Follower) pull(ctx context.Context) (filePaths []string, res *oci.RegistryResult, err error) {
f.Verbosef("check if pulling an allowed type of artifact")
if err := f.Puller.CheckAllowedType(ctx, f.ref, f.Config.AllowedTypes.Types); err != nil {
return nil, nil, err
}

// Pull the artifact from the repository.
f.Verbosef("pulling artifact %q", f.ref)
res, err = f.Pull(ctx, f.ref, f.tmpDir, runtime.GOOS, runtime.GOARCH)
Expand Down
51 changes: 48 additions & 3 deletions pkg/oci/puller/puller.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ func manifestFromDesc(ctx context.Context, target oras.Target, desc *v1.Descript
return &manifest, nil
}

// PullConfigLayer fetches only the config layer from a given ref.
func (p *Puller) PullConfigLayer(ctx context.Context, ref string) (*oci.ArtifactConfig, error) {
// manifestFromRef retieves the manifest of an artifact, also taking care of resolving to it walking through indexes.
func (p *Puller) manifestFromRef(ctx context.Context, ref string) (*v1.Manifest, error) {
repo, err := repository.NewRepository(ref, repository.WithClient(p.Client), repository.WithPlainHTTP(p.plainHTTP))
if err != nil {
return nil, err
Expand Down Expand Up @@ -214,6 +214,21 @@ func (p *Puller) PullConfigLayer(ctx context.Context, ref string) (*oci.Artifact
return nil, fmt.Errorf("unable to unmarshal manifest: %w", err)
}

return &manifest, nil
}

// PullConfigLayer fetches only the config layer from a given ref.
func (p *Puller) PullConfigLayer(ctx context.Context, ref string) (*oci.ArtifactConfig, error) {
repo, err := repository.NewRepository(ref, repository.WithClient(p.Client), repository.WithPlainHTTP(p.plainHTTP))
if err != nil {
return nil, err
}

manifest, err := p.manifestFromRef(ctx, ref)
if err != nil {
return nil, err
}

configRef := manifest.Config.Digest.String()

descriptor, err := repo.Blobs().Resolve(ctx, configRef)
Expand All @@ -223,7 +238,7 @@ func (p *Puller) PullConfigLayer(ctx context.Context, ref string) (*oci.Artifact

rc, err := repo.Fetch(ctx, descriptor)
if err != nil {
return nil, fmt.Errorf("unable to fetch descriptor with digest: %s", desc.Digest.String())
return nil, fmt.Errorf("unable to fetch descriptor with digest: %s", descriptor.Digest.String())
}

configBytes, err := io.ReadAll(rc)
Expand All @@ -238,3 +253,33 @@ func (p *Puller) PullConfigLayer(ctx context.Context, ref string) (*oci.Artifact

return &artifactConfig, nil
}

// CheckAllowedType does a preliminary check on the manifest to state whether we are allowed
// or not to download this type of artifact.
func (p *Puller) CheckAllowedType(ctx context.Context, ref string, allowedTypes []oci.ArtifactType) error {
if len(allowedTypes) == 0 {
return fmt.Errorf("cannot download any artifact types because any was allowed")
}

manifest, err := p.manifestFromRef(ctx, ref)
if err != nil {
return err
}

if len(manifest.Layers) == 0 {
return fmt.Errorf("malformed artifact, expected to find at least one layer for ref %q", ref)
}

var allowedMediaTypes []string
for _, t := range allowedTypes {
allowedMediaTypes = append(allowedMediaTypes, t.ToMediaType())
}

for _, t := range allowedMediaTypes {
if manifest.Layers[0].MediaType == t {
return nil
}
}

return fmt.Errorf("cannot download artifact of type %q: not permitted", manifest.Layers[0].MediaType)
}
56 changes: 54 additions & 2 deletions pkg/oci/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package oci
import (
"errors"
"fmt"
"regexp"
"sort"
"strings"

Expand All @@ -36,8 +37,8 @@ const (
// The following functions are necessary to use ArtifactType with Cobra.

// String returns a string representation of ArtifactType.
func (e *ArtifactType) String() string {
return string(*e)
func (e ArtifactType) String() string {
return string(e)
}

// Set an ArtifactType.
Expand All @@ -56,6 +57,57 @@ func (e *ArtifactType) Type() string {
return "ArtifactType"
}

// ToMediaType converts type to its corresponding media type.
// Ensure this is called after a Set().
func (e *ArtifactType) ToMediaType() string {
switch *e {
case Rulesfile:
return FalcoRulesfileLayerMediaType
case Plugin:
return FalcoPluginLayerMediaType
}

// should never happen
return ""
}

// ArtifactTypeSlice is a slice of ArtifactType, can be passed as comma separated values.
type ArtifactTypeSlice struct {
Types []ArtifactType
CommaSeparatedString string
}

// String returns a string representation of ArtifactTypeSlice.
func (e ArtifactTypeSlice) String() string {
return e.CommaSeparatedString
}

// Set an ArtifactType.
func (e *ArtifactTypeSlice) Set(v string) error {
commaSeparatedRegexp := regexp.MustCompile(`^([^,]+)(,[^,]+)*$`)
if !commaSeparatedRegexp.MatchString(v) {
return fmt.Errorf("%q is not a valid comma separated string", v)
}

e.CommaSeparatedString = v

tokens := strings.Split(v, ",")
for _, token := range tokens {
var at ArtifactType
if err := at.Set(token); err != nil {
return fmt.Errorf("not valid token: %w", err)
}
e.Types = append(e.Types, at)
}

return nil
}

// Type returns a string representing this type.
func (e *ArtifactTypeSlice) Type() string {
return "ArtifactTypeSlice"
}

// RegistryResult represents a generic result that is generated when
// interacting with a remote OCI registry.
type RegistryResult struct {
Expand Down

0 comments on commit 9dd6b8d

Please sign in to comment.