mirror of https://github.com/fluxcd/flux2.git
				
				
				
			
							parent
							
								
									f20fe76168
								
							
						
					
					
						commit
						3f79b4dd57
					
				| @ -0,0 +1,195 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2023 The Flux authors | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/fluxcd/flux2/v2/internal/utils" | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
|  | 	v1 "k8s.io/api/apps/v1" | ||||||
|  | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
|  | 
 | ||||||
|  | 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ctrlChecks = map[string]map[string]bool{ | ||||||
|  | 	"helm-controller": { | ||||||
|  | 		"insecure-kubeconfig-exec": false, | ||||||
|  | 		"insecure-kubeconfig-tls":  false, | ||||||
|  | 	}, | ||||||
|  | 	"kustomize-controller": { | ||||||
|  | 		"insecure-kubeconfig-exec": false, | ||||||
|  | 		"insecure-kubeconfig-tls":  false, | ||||||
|  | 		"no-remote-bases":          true, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var multiTenancyCtrlChecks = map[string]map[string]bool{ | ||||||
|  | 	"helm-controller": { | ||||||
|  | 		"no-cross-namespace-refs": true, | ||||||
|  | 	}, | ||||||
|  | 	"kustomize-controller": { | ||||||
|  | 		"no-cross-namespace-refs": true, | ||||||
|  | 	}, | ||||||
|  | 	"notification-controller": { | ||||||
|  | 		"no-cross-namespace-refs": true, | ||||||
|  | 	}, | ||||||
|  | 	"image-reflector-controller": { | ||||||
|  | 		"no-cross-namespace-refs": true, | ||||||
|  | 	}, | ||||||
|  | 	"image-automation-controller": { | ||||||
|  | 		"no-cross-namespace-refs": true, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var multiTenancyFlag bool | ||||||
|  | 
 | ||||||
|  | var auditCmd = &cobra.Command{ | ||||||
|  | 	Use:     "audit", | ||||||
|  | 	Short:   "Audit the Flux installation for security best practices", | ||||||
|  | 	Long:    withPreviewNote("TBD"), | ||||||
|  | 	Example: `  TBD`, | ||||||
|  | 	Args:    cobra.NoArgs, | ||||||
|  | 	RunE: func(cmd *cobra.Command, args []string) error { | ||||||
|  | 		ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) | ||||||
|  | 		defer cancel() | ||||||
|  | 
 | ||||||
|  | 		kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		logger.Actionf("Starting audit") | ||||||
|  | 
 | ||||||
|  | 		for ctrl, checks := range ctrlChecks { | ||||||
|  | 			if err := auditController(ctx, kubeClient, ctrl, checks); err != nil { | ||||||
|  | 				return fmt.Errorf("failed auditing %s: %w", ctrl, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := auditSecretDecryption(ctx, kubeClient); err != nil { | ||||||
|  | 			return fmt.Errorf("failed auditing Secret decryption: %w", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if multiTenancyFlag { | ||||||
|  | 			logger.Actionf("Multi-tenancy lock-down") | ||||||
|  | 			for ctrl, checks := range multiTenancyCtrlChecks { | ||||||
|  | 				if err := auditController(ctx, kubeClient, ctrl, checks); err != nil { | ||||||
|  | 					return fmt.Errorf("failed auditing %s for multi-tenancy lock-down: %w", ctrl, err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func auditSecretDecryption(ctx context.Context, c client.Client) error { | ||||||
|  | 	var ksl kustomizev1.KustomizationList | ||||||
|  | 	if err := c.List(ctx, &ksl); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to retrieve Kustomizations: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	success := true | ||||||
|  | 	for _, ks := range ksl.Items { | ||||||
|  | 		if ks.Status.Inventory == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if ks.Spec.Decryption != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, e := range ks.Status.Inventory.Entries { | ||||||
|  | 			parts := strings.Split(e.ID, "_") | ||||||
|  | 			if parts[2] == "" && parts[3] == "Secret" { | ||||||
|  | 				success = false | ||||||
|  | 				logger.Warningf("%s/%s doesn't have Secret decryption configured", ks.Namespace, ks.Name) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if success { | ||||||
|  | 		logger.Successf("Secret decryption is configured for all Kustomizations that create Secrets") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func auditController(ctx context.Context, c client.Client, name string, flags map[string]bool) error { | ||||||
|  | 	hcDeploys, err := getManagerArgs(ctx, c, name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to get %s flags: %w", name, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(hcDeploys) == 0 { | ||||||
|  | 		logger.Warningf("No %s Deployment found, auditing skipped", name) | ||||||
|  | 	} else { | ||||||
|  | 		for name, args := range hcDeploys { | ||||||
|  | 			for flag, desired := range flags { | ||||||
|  | 				hcExec, err := assertBoolFlagValue(args, flag, desired) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return fmt.Errorf("failed parsing %q args: %w", name, err) | ||||||
|  | 				} | ||||||
|  | 				if hcExec == desired { | ||||||
|  | 					logger.Successf("%s: %s is %t", name, flag, desired) | ||||||
|  | 				} else { | ||||||
|  | 					logger.Warningf("%s: %s should be %t", name, flag, desired) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getManagerArgs(ctx context.Context, c client.Client, component string) (map[string][]string, error) { | ||||||
|  | 	var deploys v1.DeploymentList | ||||||
|  | 	if err := c.List(ctx, &deploys, client.MatchingLabels{ | ||||||
|  | 		"app.kubernetes.io/component": component, | ||||||
|  | 	}); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to retrieve %s deployments: %w", component, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	res := make(map[string][]string, 0) | ||||||
|  | 
 | ||||||
|  | 	for _, deploy := range deploys.Items { | ||||||
|  | 		for _, ctr := range deploy.Spec.Template.Spec.Containers { | ||||||
|  | 			if ctr.Name == "manager" { | ||||||
|  | 				res[deploy.Name] = ctr.Args | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return res, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func assertBoolFlagValue(args []string, flagName string, value bool) (bool, error) { | ||||||
|  | 	fs := pflag.NewFlagSet("tmp", pflag.ContinueOnError) | ||||||
|  | 	fs.ParseErrorsWhitelist.UnknownFlags = true | ||||||
|  | 	f := fs.BoolP(flagName, "", false, "") | ||||||
|  | 	if err := fs.Parse(args); err != nil { | ||||||
|  | 		return false, fmt.Errorf("failed parsing args: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return *f, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	auditCmd.Flags().BoolVar(&multiTenancyFlag, "multi-tenancy", false, "Enable additional audit checks for multi-tenant clusters.") | ||||||
|  | 	rootCmd.AddCommand(auditCmd) | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in New Issue