Skip to content
Open
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
50 changes: 48 additions & 2 deletions cmd/guaccollect/cmd/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"
"time"

"github.com/guacsec/guac/pkg/cli"
csubclient "github.com/guacsec/guac/pkg/collectsub/client"
"github.com/guacsec/guac/pkg/collectsub/datasource"
"github.com/guacsec/guac/pkg/collectsub/datasource/csubsource"
Expand All @@ -34,6 +35,10 @@ import (
"github.com/spf13/viper"
)

const (
ociInsecureSkipTLSVerify = "insecure-skip-tls-verify"
)

type ociOptions struct {
// datasource for the collector
dataSource datasource.CollectSource
Expand All @@ -45,6 +50,8 @@ type ociOptions struct {
poll bool
// enable/disable message publish to queue
publishToQueue bool
// skip TLS verification for registry connections
insecureSkipTLSVerify bool
}

type ociRegistryOptions struct {
Expand All @@ -58,6 +65,8 @@ type ociRegistryOptions struct {
poll bool
// enable/disable message publish to queue
publishToQueue bool
// skip TLS verification for registry connections
insecureSkipTLSVerify bool
}

var ociCmd = &cobra.Command{
Expand Down Expand Up @@ -91,6 +100,7 @@ you have access to read and write to the respective blob store.`,
viper.GetBool("use-csub"),
viper.GetBool("service-poll"),
viper.GetBool("publish-to-queue"),
viper.GetBool(ociInsecureSkipTLSVerify),
args)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand All @@ -102,7 +112,14 @@ you have access to read and write to the respective blob store.`,
// TODO(lumjjb): Return this to a longer duration (~10 minutes) so as to not keep hitting
// the OCI server. This will require adding triggers to get new repos as they come up from
// the CollectSources so that there isn't a long delay from adding new data sources.
ociCollector := oci.NewOCICollector(ctx, opts.dataSource, opts.poll, 30*time.Second)

// Build regclient options with TLS configuration
registryHosts := oci.ExtractRegistryHosts(args)
rcOpts := oci.BuildRegClientOptions(registryHosts, oci.OCIClientOptions{
InsecureSkipTLSVerify: opts.insecureSkipTLSVerify,
})

ociCollector := oci.NewOCICollector(ctx, opts.dataSource, opts.poll, 30*time.Second, rcOpts...)
err = collector.RegisterDocumentCollector(ociCollector, oci.OCICollector)
if err != nil {
logger.Fatalf("unable to register oci collector: %v", err)
Expand All @@ -129,6 +146,7 @@ var ociRegistryCmd = &cobra.Command{
viper.GetBool("use-csub"),
viper.GetBool("service-poll"),
viper.GetBool("publish-to-queue"),
viper.GetBool(ociInsecureSkipTLSVerify),
args)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand All @@ -139,7 +157,14 @@ var ociRegistryCmd = &cobra.Command{
// Register collector
// We probably want a much longer poll interval for registry collectors as the _catalog
// endpoint can be expensive to hit and likely won't change often.
ociRegistryCollector := oci.NewOCIRegistryCollector(ctx, opts.dataSource, opts.poll, 30*time.Minute)

// Build regclient options with TLS configuration
// For registry command, args are registry hosts directly
rcOpts := oci.BuildRegClientOptions(args, oci.OCIClientOptions{
InsecureSkipTLSVerify: opts.insecureSkipTLSVerify,
})

ociRegistryCollector := oci.NewOCIRegistryCollector(ctx, opts.dataSource, opts.poll, 30*time.Minute, rcOpts...)
err = collector.RegisterDocumentCollector(ociRegistryCollector, oci.OCIRegistryCollector)
if err != nil {
logger.Errorf("unable to register oci collector: %v", err)
Expand All @@ -158,13 +183,15 @@ func validateOCIFlags(
useCsub,
poll bool,
pubToQueue bool,
insecureSkipTLSVerify bool,
args []string,
) (ociOptions, error) {
var opts ociOptions
opts.pubsubAddr = pubsubAddr
opts.blobAddr = blobAddr
opts.poll = poll
opts.publishToQueue = pubToQueue
opts.insecureSkipTLSVerify = insecureSkipTLSVerify

if useCsub {
csubOpts, err := csubclient.ValidateCsubClientFlags(csubAddr, csubTls, csubTlsSkipVerify)
Expand Down Expand Up @@ -214,13 +241,15 @@ func validateOCIRegistryFlags(
useCsub,
poll,
pubToQueue bool,
insecureSkipTLSVerify bool,
args []string,
) (ociRegistryOptions, error) {
var opts ociRegistryOptions
opts.pubsubAddr = pubsubAddr
opts.blobAddr = blobAddr
opts.poll = poll
opts.publishToQueue = pubToQueue
opts.insecureSkipTLSVerify = insecureSkipTLSVerify

if useCsub {
csubOpts, err := csubclient.ValidateCsubClientFlags(csubAddr, csubTls, csubTlsSkipVerify)
Expand Down Expand Up @@ -264,6 +293,23 @@ func validateOCIRegistryFlags(
}

func init() {
set, err := cli.BuildFlags([]string{ociInsecureSkipTLSVerify})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err)
os.Exit(1)
}

ociCmd.PersistentFlags().AddFlagSet(set)
if err := viper.BindPFlags(ociCmd.PersistentFlags()); err != nil {
fmt.Fprintf(os.Stderr, "failed to bind flags: %v", err)
os.Exit(1)
}
rootCmd.AddCommand(ociCmd)

ociRegistryCmd.PersistentFlags().AddFlagSet(set)
if err := viper.BindPFlags(ociRegistryCmd.PersistentFlags()); err != nil {
fmt.Fprintf(os.Stderr, "failed to bind flags: %v", err)
os.Exit(1)
}
rootCmd.AddCommand(ociRegistryCmd)
}
32 changes: 27 additions & 5 deletions cmd/guacone/cmd/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ import (
"github.com/spf13/viper"
)

const (
ociInsecureSkipTLSVerify = "insecure-skip-tls-verify"
)

type ociOptions struct {
graphqlEndpoint string
headerFile string
Expand All @@ -48,6 +52,7 @@ type ociOptions struct {
queryEOLOnIngestion bool
queryDepsDevOnIngestion bool
useCsub bool
insecureSkipTLSVerify bool
}

type ociRegistryOptions struct {
Expand All @@ -60,6 +65,7 @@ type ociRegistryOptions struct {
queryEOLOnIngestion bool
queryDepsDevOnIngestion bool
useCsub bool
insecureSkipTLSVerify bool
}

var ociCmd = &cobra.Command{
Expand All @@ -78,6 +84,7 @@ var ociCmd = &cobra.Command{
viper.GetBool("add-eol-on-ingest"),
viper.GetBool("add-depsdev-on-ingest"),
viper.GetBool("use-csub"),
viper.GetBool(ociInsecureSkipTLSVerify),
args)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand All @@ -90,7 +97,13 @@ var ociCmd = &cobra.Command{
transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport)

// Register collector
ociCollector := oci.NewOCICollector(ctx, opts.dataSource, false, 10*time.Minute)
// Build regclient options with TLS configuration
registryHosts := oci.ExtractRegistryHosts(args)
rcOpts := oci.BuildRegClientOptions(registryHosts, oci.OCIClientOptions{
InsecureSkipTLSVerify: opts.insecureSkipTLSVerify,
})

ociCollector := oci.NewOCICollector(ctx, opts.dataSource, false, 10*time.Minute, rcOpts...)
err = collector.RegisterDocumentCollector(ociCollector, oci.OCICollector)
if err != nil {
logger.Fatalf("unable to register oci collector: %v", err)
Expand Down Expand Up @@ -156,6 +169,7 @@ var ociRegistryCmd = &cobra.Command{
viper.GetBool("add-eol-on-ingest"),
viper.GetBool("add-depsdev-on-ingest"),
viper.GetBool("use-csub"),
viper.GetBool(ociInsecureSkipTLSVerify),
args)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand All @@ -168,7 +182,13 @@ var ociRegistryCmd = &cobra.Command{
transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport)

// Register collector
ociRegistryCollector := oci.NewOCIRegistryCollector(ctx, opts.dataSource, false, 30*time.Second)
// Build regclient options with TLS configuration
// For registry command, args are registry hosts directly
rcOpts := oci.BuildRegClientOptions(args, oci.OCIClientOptions{
InsecureSkipTLSVerify: opts.insecureSkipTLSVerify,
})

ociRegistryCollector := oci.NewOCIRegistryCollector(ctx, opts.dataSource, false, 30*time.Second, rcOpts...)
err = collector.RegisterDocumentCollector(ociRegistryCollector, oci.OCIRegistryCollector)
if err != nil {
logger.Errorf("unable to register oci collector: %v", err)
Expand Down Expand Up @@ -219,7 +239,7 @@ var ociRegistryCmd = &cobra.Command{
}

func validateOCIFlags(gqlEndpoint, headerFile, csubAddr string, csubTls, csubTlsSkipVerify bool,
queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, useCsub bool, args []string) (ociOptions, csub_client.Client, error) {
queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, useCsub bool, insecureSkipTLSVerify bool, args []string) (ociOptions, csub_client.Client, error) {
var opts ociOptions
opts.graphqlEndpoint = gqlEndpoint
opts.headerFile = headerFile
Expand All @@ -228,6 +248,7 @@ func validateOCIFlags(gqlEndpoint, headerFile, csubAddr string, csubTls, csubTls
opts.queryEOLOnIngestion = queryEOLIngestion
opts.queryDepsDevOnIngestion = queryDepsDevOnIngestion
opts.useCsub = useCsub
opts.insecureSkipTLSVerify = insecureSkipTLSVerify

var csubClient csub_client.Client

Expand Down Expand Up @@ -274,7 +295,7 @@ func validateOCIFlags(gqlEndpoint, headerFile, csubAddr string, csubTls, csubTls
}

func validateOCIRegistryFlags(gqlEndpoint, headerFile, csubAddr string, csubTls, csubTlsSkipVerify bool,
queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, useCsub bool, args []string) (ociRegistryOptions, csub_client.Client, error) {
queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, useCsub bool, insecureSkipTLSVerify bool, args []string) (ociRegistryOptions, csub_client.Client, error) {
var opts ociRegistryOptions
opts.graphqlEndpoint = gqlEndpoint
opts.headerFile = headerFile
Expand All @@ -283,6 +304,7 @@ func validateOCIRegistryFlags(gqlEndpoint, headerFile, csubAddr string, csubTls,
opts.queryEOLOnIngestion = queryEOLIngestion
opts.queryDepsDevOnIngestion = queryDepsDevOnIngestion
opts.useCsub = useCsub
opts.insecureSkipTLSVerify = insecureSkipTLSVerify

var csubClient csub_client.Client

Expand Down Expand Up @@ -331,7 +353,7 @@ func validateOCIRegistryFlags(gqlEndpoint, headerFile, csubAddr string, csubTls,
}

func init() {
set, err := cli.BuildFlags([]string{"use-csub"})
set, err := cli.BuildFlags([]string{"use-csub", ociInsecureSkipTLSVerify})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err)
os.Exit(1)
Expand Down
3 changes: 3 additions & 0 deletions pkg/cli/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func init() {
set.Bool("csub-tls-skip-verify", false, "skip verifying server certificate (for self-signed certificates for example)")
set.Bool("use-csub", true, "use collectsub server for datasource")

// OCI collector options
set.Bool("insecure-skip-tls-verify", false, "skip TLS verification when connecting to OCI registries (allows HTTP)")

set.Int("csub-listen-port", 2782, "port to listen to on collect-sub service")
set.String("csub-tls-cert-file", "", "path to the TLS certificate in PEM format for collect-sub service")
set.String("csub-tls-key-file", "", "path to the TLS key in PEM format for collect-sub service")
Expand Down
72 changes: 71 additions & 1 deletion pkg/handler/collector/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/guacsec/guac/pkg/version"
"github.com/pkg/errors"
"github.com/regclient/regclient"
"github.com/regclient/regclient/config"
"github.com/regclient/regclient/types/descriptor"
"github.com/regclient/regclient/types/manifest"
"github.com/regclient/regclient/types/platform"
Expand Down Expand Up @@ -528,10 +529,79 @@ func hasNoIdentifier(r ref.Ref) bool {
return hasNoTag(r) && hasNoDigest(r)
}

func getRegClientOptions() []regclient.Opt {
// GetRegClientOptions returns the default regclient options with Docker credentials,
// certificates, and user agent configured.
func GetRegClientOptions() []regclient.Opt {
rcOpts := []regclient.Opt{}
rcOpts = append(rcOpts, regclient.WithDockerCreds())
rcOpts = append(rcOpts, regclient.WithDockerCerts())
rcOpts = append(rcOpts, regclient.WithUserAgent(version.UserAgent))
return rcOpts
}

func getRegClientOptions() []regclient.Opt {
return GetRegClientOptions()
}

// OCIClientOptions contains configuration for OCI registry connections.
// New fields should always have sensible zero-value defaults for backward compatibility.
type OCIClientOptions struct {
// InsecureSkipTLSVerify disables TLS entirely (uses HTTP)
InsecureSkipTLSVerify bool
}

// needsHostConfig returns true if any custom host configuration is needed.
func (o OCIClientOptions) needsHostConfig() bool {
return o.InsecureSkipTLSVerify
}

// toHostConfig converts options to a regclient config.Host for the given hostname.
func (o OCIClientOptions) toHostConfig(host string) config.Host {
h := config.Host{
Name: host,
Hostname: host,
}

if o.InsecureSkipTLSVerify {
h.TLS = config.TLSDisabled
}

return h
}

// BuildRegClientOptions constructs regclient options for OCI operations.
// The opts parameter allows configuration of TLS settings for the specified hosts.
func BuildRegClientOptions(hosts []string, opts OCIClientOptions) []regclient.Opt {
rcOpts := GetRegClientOptions()

if len(hosts) == 0 || !opts.needsHostConfig() {
return rcOpts
}

hostConfigs := make([]config.Host, len(hosts))
for i, host := range hosts {
hostConfigs[i] = opts.toHostConfig(host)
}
rcOpts = append(rcOpts, regclient.WithConfigHost(hostConfigs...))

return rcOpts
}

// ExtractRegistryHosts extracts unique registry hosts from image references.
// For image paths like "registry.example.com:5000/repo/image:tag", it returns "registry.example.com:5000".
func ExtractRegistryHosts(imagePaths []string) []string {
hostsMap := make(map[string]struct{})
for _, imagePath := range imagePaths {
r, err := ref.New(imagePath)
if err != nil {
continue
}
hostsMap[r.Registry] = struct{}{}
}

hosts := make([]string, 0, len(hostsMap))
for host := range hostsMap {
hosts = append(hosts, host)
}
return hosts
}
Loading