Skip to content

Commit

Permalink
Merge pull request #75 from viccuad/audit-certs
Browse files Browse the repository at this point in the history
feat: Add `--insecure-ssl`, `--filepath-cacert` flags, consume ca cert
  • Loading branch information
viccuad committed Jul 28, 2023
2 parents 92af87d + a92af0e commit f268b33
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
27 changes: 21 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Scanner interface {
ScanClusterWideResources() error
}

// log level
var level logconfig.Level

// print result of scan as JSON to stdout
Expand All @@ -31,8 +32,11 @@ var printJSON bool
// list of namespaces to be skipped from scan
var skippedNs []string

// rootCmd represents the base command when called without any subcommands
// skip SSL cert validation when connecting to PolicyServers endpoints
var insecureSSL bool

var (
// rootCmd represents the base command when called without any subcommands
rootCmd = &cobra.Command{
Use: "audit-scanner",
Short: "Reports evaluation of existing Kubernetes resources with your already deployed Kubewarden policies",
Expand All @@ -58,6 +62,11 @@ There will be a ClusterPolicyReport with results for cluster-wide resources.`,
if err != nil {
return err
}
caCertFile, err := cmd.Flags().GetString("extra-ca")
if err != nil {
return err
}

policiesFetcher, err := policies.NewFetcher(kubewardenNamespace, skippedNs)
if err != nil {
return err
Expand All @@ -66,7 +75,7 @@ There will be a ClusterPolicyReport with results for cluster-wide resources.`,
if err != nil {
return err
}
scanner, err := scanner.NewScanner(policiesFetcher, resourcesFetcher, printJSON)
scanner, err := scanner.NewScanner(policiesFetcher, resourcesFetcher, printJSON, insecureSSL, caCertFile)
if err != nil {
return err
}
Expand All @@ -79,6 +88,10 @@ There will be a ClusterPolicyReport with results for cluster-wide resources.`,
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
// make sure we always get json formatted errors, even for flag errors
rootCmd.SilenceErrors = true
rootCmd.SilenceUsage = true

if err := rootCmd.Execute(); err != nil {
log.Fatal().Err(err).Msg("Error on cmd.Execute()")
}
Expand Down Expand Up @@ -107,10 +120,12 @@ func startScanner(namespace string, clusterWide bool, scanner Scanner) error {

func init() {
rootCmd.Flags().StringP("namespace", "n", "", "namespace to be evaluated")
rootCmd.Flags().BoolP("cluster", "c", false, "Scan cluster wide resources")
rootCmd.Flags().StringP("kubewarden-namespace", "k", defaultKubewardenNamespace, "namespace where the Kubewarden components (e.g. Policy Server) are installed (required)")
rootCmd.Flags().StringP("policy-server-url", "u", "", "Full URL to the PolicyServers, for example https://localhost:3000. Audit scanner will query the needed HTTP path. Useful for out-of-cluster debugging")
rootCmd.Flags().BoolP("cluster", "c", false, "scan cluster wide resources")
rootCmd.Flags().StringP("kubewarden-namespace", "k", defaultKubewardenNamespace, "namespace where the Kubewarden components (e.g. PolicyServer) are installed (required)")
rootCmd.Flags().StringP("policy-server-url", "u", "", "URI to the PolicyServers the Audit Scanner will query. Example: https://localhost:3000. Useful for out-of-cluster debugging")
rootCmd.Flags().VarP(&level, "loglevel", "l", fmt.Sprintf("level of the logs. Supported values are: %v", logconfig.SupportedValues))
rootCmd.Flags().BoolVarP(&printJSON, "print", "p", false, "print result of scan in JSON to stdout")
rootCmd.Flags().StringSliceVarP(&skippedNs, "ignore-namespaces", "i", nil, "Comma separated list of namespace names to be skipped from scan")
rootCmd.Flags().StringSliceVarP(&skippedNs, "ignore-namespaces", "i", nil, "comma separated list of namespace names to be skipped from scan. This flag can be repeated")
rootCmd.Flags().BoolVar(&insecureSSL, "insecure-ssl", false, "skip SSL cert validation when connecting to PolicyServers endpoints. Useful for development")
rootCmd.Flags().StringP("extra-ca", "f", "", "File path to CA cert in PEM format of PolicyServer endpoints")
}
2 changes: 1 addition & 1 deletion internal/policies/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (f *Fetcher) getAdmissionPolicies(namespace string) ([]policiesv1.Admission
return policies.Items, nil
}

func newClient() (client.Client, error) { //nolint:ireturn
func newClient() (client.Client, error) { //nolint
config := ctrl.GetConfigOrDie()
customScheme := scheme.Scheme
customScheme.AddKnownTypes(
Expand Down
2 changes: 1 addition & 1 deletion internal/policies/fetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ var nsKubewarden = v1.Namespace{
},
}

func mockClient(initObjs ...k8sClient.Object) k8sClient.Client { //nolint:ireturn
func mockClient(initObjs ...k8sClient.Object) k8sClient.Client { //nolint
customScheme := scheme.Scheme
customScheme.AddKnownTypes(schema.GroupVersion{Group: constants.KubewardenPoliciesGroup, Version: constants.KubewardenPoliciesVersion}, &policiesv1.ClusterAdmissionPolicy{}, &policiesv1.AdmissionPolicy{}, &policiesv1.ClusterAdmissionPolicyList{}, &policiesv1.AdmissionPolicyList{})
metav1.AddToGroupVersion(customScheme, schema.GroupVersion{Group: constants.KubewardenPoliciesGroup, Version: constants.KubewardenPoliciesVersion})
Expand Down
2 changes: 1 addition & 1 deletion internal/resources/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewFetcher(kubewardenNamespace string, policyServerURL string) (*Fetcher, e
dynamicClient := dynamic.NewForConfigOrDie(config)
clientset := kubernetes.NewForConfigOrDie(config)
if policyServerURL != "" {
log.Info().Msg(fmt.Sprintf("Querying PolicyServers at %s for debugging purposes. Don't forget to start `kwctl port-forward` if needed", policyServerURL))
log.Info().Msg(fmt.Sprintf("querying PolicyServers at %s for debugging purposes. Don't forget to start `kwctl port-forward` if needed", policyServerURL))
}
return &Fetcher{dynamicClient, kubewardenNamespace, policyServerURL, clientset}, nil
}
Expand Down
54 changes: 49 additions & 5 deletions internal/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"

"github.com/kubewarden/audit-scanner/internal/constants"
"github.com/kubewarden/audit-scanner/internal/report"
Expand Down Expand Up @@ -52,13 +54,56 @@ type Scanner struct {
printJSON bool
}

// NewScanner creates a new scanner with the PoliciesFetcher provided
func NewScanner(policiesFetcher PoliciesFetcher, resourcesFetcher ResourcesFetcher, printJSON bool) (*Scanner, error) {
// NewScanner creates a new scanner with the PoliciesFetcher provided. If
// insecureClient is false, it will read the caCertFile and add it to the in-app
// cert trust store. This gets used by the httpCLient when connection to
// PolicyServers endpoints.
func NewScanner(policiesFetcher PoliciesFetcher, resourcesFetcher ResourcesFetcher,
printJSON bool,
insecureClient bool, caCertFile string) (*Scanner, error) {
report, err := report.NewPolicyReportStore()
if err != nil {
return nil, err
}
return &Scanner{policiesFetcher, resourcesFetcher, *report, http.Client{}, printJSON}, nil

// Get the SystemCertPool to build an in-app cert pool from it
// Continue with an empty pool on error
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}

if caCertFile != "" {
certs, err := os.ReadFile(caCertFile)
if err != nil {
return nil, fmt.Errorf("failed to read file %q with CA cert: %w", caCertFile, err)
}
// Append our cert to the in-app cert pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
return nil, errors.New("failed to append cert to in-app RootCAs trust store")
}
log.Debug().Str("ca-cert-file", caCertFile).
Msg("appended cert file to in-app RootCAs trust store")
}

// initialize httpClient while conserving default settings
httpClient := *http.DefaultClient
httpClient.Transport = http.DefaultTransport
transport, ok := httpClient.Transport.(*http.Transport)
if !ok {
return nil, errors.New("failed to build httpClient: failed http.Transport type assertion")
}
transport.TLSClientConfig = &tls.Config{
RootCAs: rootCAs, // our augmented in-app cert pool
MinVersion: tls.VersionTLS12,
}

if insecureClient {
transport.TLSClientConfig.InsecureSkipVerify = true
log.Warn().Msg("connecting to PolicyServers endpoints without validating TLS connection")
}

return &Scanner{policiesFetcher, resourcesFetcher, *report, httpClient, printJSON}, nil
}

// ScanNamespace scans resources for a given namespace.
Expand Down Expand Up @@ -293,8 +338,7 @@ func sendAdmissionReviewToPolicyServer(url *url.URL, admissionRequest *admv1.Adm
if err != nil {
return nil, err
}
// TODO remove the following line and properly configure the certificates
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint

req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, url.String(), bytes.NewBuffer(payload))
res, err := httpClient.Do(req)
if err != nil {
Expand Down

0 comments on commit f268b33

Please sign in to comment.