github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/project_allowlist.go (about)

     1  package admin
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/spf13/cobra"
    11  	rbacv1 "k8s.io/api/rbac/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  	"k8s.io/apimachinery/pkg/runtime/schema"
    15  	"k8s.io/client-go/discovery"
    16  	"k8s.io/client-go/kubernetes/scheme"
    17  	"k8s.io/client-go/tools/clientcmd"
    18  	"sigs.k8s.io/yaml"
    19  
    20  	"github.com/argoproj/argo-cd/v3/util/errors"
    21  
    22  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    23  	"github.com/argoproj/argo-cd/v3/util/cli"
    24  
    25  	// load the gcp plugin (required to authenticate against GKE clusters).
    26  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    27  	// load the oidc plugin (required to authenticate with OpenID Connect).
    28  	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
    29  	// load the azure plugin (required to authenticate with AKS clusters).
    30  	_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
    31  
    32  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    33  )
    34  
    35  // NewProjectAllowListGenCommand generates a project from clusterRole
    36  func NewProjectAllowListGenCommand() *cobra.Command {
    37  	var (
    38  		clientConfig clientcmd.ClientConfig
    39  		out          string
    40  	)
    41  	command := &cobra.Command{
    42  		Use:   "generate-allow-list CLUSTERROLE_PATH PROJ_NAME",
    43  		Short: "Generates project allow list from the specified clusterRole file",
    44  		Example: `# Generates project allow list from the specified clusterRole file
    45  argocd admin proj generate-allow-list /path/to/clusterrole.yaml my-project`,
    46  		Run: func(c *cobra.Command, args []string) {
    47  			if len(args) != 2 {
    48  				c.HelpFunc()(c, args)
    49  				os.Exit(1)
    50  			}
    51  			clusterRoleFileName := args[0]
    52  			projName := args[1]
    53  
    54  			var writer io.Writer
    55  			if out == "-" {
    56  				writer = os.Stdout
    57  			} else {
    58  				f, err := os.Create(out)
    59  				errors.CheckError(err)
    60  				bw := bufio.NewWriter(f)
    61  				writer = bw
    62  				defer func() {
    63  					err = bw.Flush()
    64  					errors.CheckError(err)
    65  					err = f.Close()
    66  					errors.CheckError(err)
    67  				}()
    68  			}
    69  
    70  			resourceList, err := getResourceList(clientConfig)
    71  			errors.CheckError(err)
    72  			globalProj, err := generateProjectAllowList(resourceList, clusterRoleFileName, projName)
    73  			errors.CheckError(err)
    74  
    75  			yamlBytes, err := yaml.Marshal(globalProj)
    76  			errors.CheckError(err)
    77  
    78  			_, err = writer.Write(yamlBytes)
    79  			errors.CheckError(err)
    80  		},
    81  	}
    82  	clientConfig = cli.AddKubectlFlagsToCmd(command)
    83  	command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
    84  
    85  	return command
    86  }
    87  
    88  func getResourceList(clientConfig clientcmd.ClientConfig) ([]*metav1.APIResourceList, error) {
    89  	config, err := clientConfig.ClientConfig()
    90  	if err != nil {
    91  		return nil, fmt.Errorf("error while creating client config: %w", err)
    92  	}
    93  	disco, err := discovery.NewDiscoveryClientForConfig(config)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("error while creating discovery client: %w", err)
    96  	}
    97  	serverResources, err := disco.ServerPreferredResources()
    98  	if err != nil {
    99  		return nil, fmt.Errorf("error while getting server resources: %w", err)
   100  	}
   101  	return serverResources, nil
   102  }
   103  
   104  func generateProjectAllowList(serverResources []*metav1.APIResourceList, clusterRoleFileName string, projName string) (*v1alpha1.AppProject, error) {
   105  	yamlBytes, err := os.ReadFile(clusterRoleFileName)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("error reading cluster role file: %w", err)
   108  	}
   109  	var obj unstructured.Unstructured
   110  	err = yaml.Unmarshal(yamlBytes, &obj)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("error unmarshalling cluster role file yaml: %w", err)
   113  	}
   114  
   115  	clusterRole := &rbacv1.ClusterRole{}
   116  	err = scheme.Scheme.Convert(&obj, clusterRole, nil)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("error converting cluster role yaml into ClusterRole struct: %w", err)
   119  	}
   120  
   121  	resourceList := make([]metav1.GroupKind, 0)
   122  	for _, rule := range clusterRole.Rules {
   123  		if len(rule.APIGroups) == 0 {
   124  			continue
   125  		}
   126  
   127  		canCreate := false
   128  		for _, verb := range rule.Verbs {
   129  			if strings.EqualFold(verb, "Create") {
   130  				canCreate = true
   131  				break
   132  			}
   133  		}
   134  
   135  		if !canCreate {
   136  			continue
   137  		}
   138  
   139  		ruleAPIGroup := rule.APIGroups[0]
   140  		for _, ruleResource := range rule.Resources {
   141  			for _, apiResourcesList := range serverResources {
   142  				gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
   143  				if err != nil {
   144  					gv = schema.GroupVersion{}
   145  				}
   146  				if ruleAPIGroup == gv.Group {
   147  					for _, apiResource := range apiResourcesList.APIResources {
   148  						if apiResource.Name == ruleResource {
   149  							resourceList = append(resourceList, metav1.GroupKind{Group: ruleAPIGroup, Kind: apiResource.Kind})
   150  						}
   151  					}
   152  				}
   153  			}
   154  		}
   155  	}
   156  	globalProj := v1alpha1.AppProject{
   157  		TypeMeta: metav1.TypeMeta{
   158  			Kind:       application.AppProjectKind,
   159  			APIVersion: "argoproj.io/v1alpha1",
   160  		},
   161  		ObjectMeta: metav1.ObjectMeta{Name: projName},
   162  		Spec:       v1alpha1.AppProjectSpec{},
   163  	}
   164  	globalProj.Spec.NamespaceResourceWhitelist = resourceList
   165  	return &globalProj, nil
   166  }