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 }