Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: introduce allowedTypes #251

Merged
merged 5 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 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, val.String()); 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,12 @@ 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",
`list of artifact types that can be followed. If not specified or configured, all types are allowed.
It accepts comma separated values or it can be repeated multiple times.
Examples:
--allowed-types="rulesfile,plugin"
--allowed-types=rulesfile --allowed-types=plugin`)
return cmd
}

Expand Down Expand Up @@ -285,6 +308,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
61 changes: 53 additions & 8 deletions cmd/artifact/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ type artifactInstallOptions struct {
*options.RegistryOptions
rulesfilesDir string
pluginsDir string
allowedTypes oci.ArtifactTypeSlice
resolveDeps bool
}

// NewArtifactInstallCmd returns the artifact install command.
Expand Down Expand Up @@ -117,6 +119,32 @@ 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, val.String()); err != nil {
o.Printer.CheckErr(fmt.Errorf("unable to overwrite \"allowed-types\" flag: %w", err))
}
}

f = cmd.Flags().Lookup("resolve-deps")
if f == nil {
// should never happen
o.Printer.CheckErr(fmt.Errorf("unable to retrieve flag resolve-deps"))
} else if !f.Changed && viper.IsSet(config.ArtifactInstallResolveDepsKey) {
val := viper.Get(config.ArtifactInstallResolveDepsKey)
if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
o.Printer.CheckErr(fmt.Errorf("unable to overwrite \"resolve-deps\" flag: %w", err))
}
}
},
Run: func(cmd *cobra.Command, args []string) {
o.Printer.CheckErr(o.RunArtifactInstall(ctx, args))
Expand All @@ -128,6 +156,14 @@ 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",
`list of artifact types that can be installed. If not specified or configured, all types are allowed.
It accepts comma separated values or it can be repeated multiple times.
Examples:
--allowed-types="rulesfile,plugin"
--allowed-types=rulesfile --allowed-types=plugin`)
cmd.Flags().BoolVar(&o.resolveDeps, "resolve-deps", true,
"whether this command should resolve dependencies or not")

return cmd
}
Expand All @@ -137,7 +173,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 @@ -205,17 +241,22 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
}
}

// Solve dependencies
o.Printer.Info.Println("Resolving dependencies ...")
resolvedDepsRefs, err := ResolveDeps(resolver, args...)
if err != nil {
return err
var refs []string
if o.resolveDeps {
// Solve dependencies
o.Printer.Info.Println("Resolving dependencies ...")
refs, err = ResolveDeps(resolver, args...)
if err != nil {
return err
}
} else {
refs = args
}

o.Printer.Info.Printfln("Installing the following artifacts: %v", resolvedDepsRefs)
o.Printer.Info.Printfln("Installing the following artifacts: %v", refs)

// Install artifacts
for _, ref := range resolvedDepsRefs {
for _, ref := range refs {
ref, err = o.IndexCache.ResolveReference(ref)
if err != nil {
return err
Expand All @@ -233,6 +274,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
34 changes: 34 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,10 @@ const (
ArtifactInstallRulesfilesDirKey = "artifact.install.rulesfilesdir"
// ArtifactInstallPluginsDirKey is the Viper key for follower "pluginsDir" configuration.
ArtifactInstallPluginsDirKey = "artifact.install.pluginsdir"
// ArtifactInstallResolveDepsKey is the Viper key for installer "resolveDeps" configuration.
ArtifactInstallResolveDepsKey = "artifact.install.resolveDeps"
// 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 @@ -130,6 +136,7 @@ type Install struct {
Artifacts []string `mapstructure:"artifacts"`
RulesfilesDir string `mapstructure:"rulesFilesDir"`
PluginsDir string `mapstructure:"pluginsDir"`
ResolveDeps bool `mapstructure:"resolveDeps"`
}

func init() {
Expand Down Expand Up @@ -411,6 +418,33 @@ func Installer() (Install, error) {
Artifacts: artifacts,
RulesfilesDir: viper.GetString(ArtifactInstallRulesfilesDirKey),
PluginsDir: viper.GetString(ArtifactInstallPluginsDirKey),
ResolveDeps: viper.GetBool(ArtifactInstallResolveDepsKey),
}, 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, %w", t, err)
}

allowedArtifactTypes = append(allowedArtifactTypes, at)
}

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

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
52 changes: 49 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,34 @@ 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. If allowedTypes is empty, everything is allowed,
// else it is used to perform the check.
func (p *Puller) CheckAllowedType(ctx context.Context, ref string, allowedTypes []oci.ArtifactType) error {
if len(allowedTypes) == 0 {
return nil
}

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)
}
Loading