github.com/joelanford/operator-sdk@v0.8.2/internal/pkg/scaffold/helm/role.go (about) 1 // Copyright 2019 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package helm 16 17 import ( 18 "fmt" 19 "path/filepath" 20 "sort" 21 "strings" 22 23 "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" 24 25 "github.com/ghodss/yaml" 26 log "github.com/sirupsen/logrus" 27 rbacv1 "k8s.io/api/rbac/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/version" 31 "k8s.io/client-go/discovery" 32 "k8s.io/client-go/rest" 33 "k8s.io/helm/pkg/chartutil" 34 "k8s.io/helm/pkg/manifest" 35 "k8s.io/helm/pkg/proto/hapi/chart" 36 "k8s.io/helm/pkg/renderutil" 37 "k8s.io/helm/pkg/tiller" 38 ) 39 40 // CreateRoleScaffold generates a role scaffold from the provided helm chart. It 41 // renders a release manifest using the chart's default values and uses the Kubernetes 42 // discovery API to lookup each resource in the resulting manifest. 43 // The role scaffold will have IsClusterScoped=true if the chart lists cluster scoped resources 44 func CreateRoleScaffold(cfg *rest.Config, chart *chart.Chart) (*scaffold.Role, error) { 45 log.Info("Generating RBAC rules") 46 47 roleScaffold := &scaffold.Role{ 48 IsClusterScoped: false, 49 SkipDefaultRules: true, 50 // TODO: enable metrics in helm operator 51 SkipMetricsRules: true, 52 CustomRules: []rbacv1.PolicyRule{ 53 // We need this rule so tiller can read namespaces to ensure they exist 54 { 55 APIGroups: []string{""}, 56 Resources: []string{"namespaces"}, 57 Verbs: []string{"get"}, 58 }, 59 60 // We need this rule for leader election and release state storage to work 61 { 62 APIGroups: []string{""}, 63 Resources: []string{"configmaps", "secrets"}, 64 Verbs: []string{rbacv1.VerbAll}, 65 }, 66 }, 67 } 68 69 clusterResourceRules, namespacedResourceRules, err := generateRoleRules(cfg, chart) 70 if err != nil { 71 log.Warnf("Using default RBAC rules: failed to generate RBAC rules: %s", err) 72 roleScaffold.SkipDefaultRules = false 73 return roleScaffold, nil 74 } 75 76 // Use a ClusterRole if cluster scoped resources are listed in the chart 77 if len(clusterResourceRules) > 0 { 78 log.Info("Scaffolding ClusterRole and ClusterRolebinding for cluster scoped resources in the helm chart") 79 roleScaffold.IsClusterScoped = true 80 } 81 roleScaffold.CustomRules = append(roleScaffold.CustomRules, append(clusterResourceRules, namespacedResourceRules...)...) 82 83 log.Warn("The RBAC rules generated in deploy/role.yaml are based on the chart's default manifest." + 84 " Some rules may be missing for resources that are only enabled with custom values, and" + 85 " some existing rules may be overly broad. Double check the rules generated in deploy/role.yaml" + 86 " to ensure they meet the operator's permission requirements.") 87 88 return roleScaffold, nil 89 } 90 91 func generateRoleRules(cfg *rest.Config, chart *chart.Chart) ([]rbacv1.PolicyRule, []rbacv1.PolicyRule, error) { 92 kubeVersion, serverResources, err := getServerVersionAndResources(cfg) 93 if err != nil { 94 return nil, nil, fmt.Errorf("failed to get server info: %s", err) 95 } 96 97 manifests, err := getDefaultManifests(chart, kubeVersion) 98 if err != nil { 99 return nil, nil, fmt.Errorf("failed to get default manifest: %s", err) 100 } 101 102 // Use maps of sets of resources, keyed by their group. This helps us 103 // de-duplicate resources within a group as we traverse the manifests. 104 clusterGroups := map[string]map[string]struct{}{} 105 namespacedGroups := map[string]map[string]struct{}{} 106 107 for _, m := range manifests { 108 name := m.Name 109 content := strings.TrimSpace(m.Content) 110 111 // Ignore NOTES.txt, helper manifests, and empty manifests. 112 b := filepath.Base(name) 113 if b == "NOTES.txt" { 114 continue 115 } 116 if strings.HasPrefix(b, "_") { 117 continue 118 } 119 if content == "" || content == "---" { 120 continue 121 } 122 123 // Extract the gvk from the template 124 resource := unstructured.Unstructured{} 125 err := yaml.Unmarshal([]byte(content), &resource) 126 if err != nil { 127 log.Warnf("Skipping rule generation for %s. Failed to parse manifest: %s", name, err) 128 continue 129 } 130 groupVersion := resource.GetAPIVersion() 131 group := resource.GroupVersionKind().Group 132 kind := resource.GroupVersionKind().Kind 133 134 // If we don't have the group or the kind, we won't be able to 135 // create a valid role rule, log a warning and continue. 136 if groupVersion == "" { 137 log.Warnf("Skipping rule generation for %s. Failed to determine resource apiVersion.", name) 138 continue 139 } 140 if kind == "" { 141 log.Warnf("Skipping rule generation for %s. Failed to determine resource kind.", name) 142 continue 143 } 144 145 if resourceName, namespaced, ok := getResource(serverResources, groupVersion, kind); ok { 146 if !namespaced { 147 if clusterGroups[group] == nil { 148 clusterGroups[group] = map[string]struct{}{} 149 } 150 clusterGroups[group][resourceName] = struct{}{} 151 } else { 152 if namespacedGroups[group] == nil { 153 namespacedGroups[group] = map[string]struct{}{} 154 } 155 namespacedGroups[group][resourceName] = struct{}{} 156 } 157 } else { 158 log.Warnf("Skipping rule generation for %s. Failed to determine resource scope for %s.", name, resource.GroupVersionKind()) 159 continue 160 } 161 } 162 163 // convert map[string]map[string]struct{} to []rbacv1.PolicyRule 164 clusterRules := buildRulesFromGroups(clusterGroups) 165 namespacedRules := buildRulesFromGroups(namespacedGroups) 166 167 return clusterRules, namespacedRules, nil 168 } 169 170 func getServerVersionAndResources(cfg *rest.Config) (*version.Info, []*metav1.APIResourceList, error) { 171 dc, err := discovery.NewDiscoveryClientForConfig(cfg) 172 if err != nil { 173 return nil, nil, fmt.Errorf("failed to create discovery client: %s", err) 174 } 175 kubeVersion, err := dc.ServerVersion() 176 if err != nil { 177 return nil, nil, fmt.Errorf("failed to get kubernetes server version: %s", err) 178 } 179 serverResources, err := dc.ServerResources() 180 if err != nil { 181 return nil, nil, fmt.Errorf("failed to get kubernetes server resources: %s", err) 182 } 183 return kubeVersion, serverResources, nil 184 } 185 186 func getDefaultManifests(c *chart.Chart, kubeVersion *version.Info) ([]tiller.Manifest, error) { 187 v := strings.TrimSuffix(fmt.Sprintf("%s.%s", kubeVersion.Major, kubeVersion.Minor), "+") 188 renderOpts := renderutil.Options{ 189 ReleaseOptions: chartutil.ReleaseOptions{ 190 IsInstall: true, 191 IsUpgrade: false, 192 }, 193 KubeVersion: v, 194 } 195 196 renderedTemplates, err := renderutil.Render(c, &chart.Config{}, renderOpts) 197 if err != nil { 198 return nil, fmt.Errorf("failed to render chart templates: %s", err) 199 } 200 return tiller.SortByKind(manifest.SplitManifests(renderedTemplates)), nil 201 } 202 203 func getResource(namespacedResourceList []*metav1.APIResourceList, groupVersion, kind string) (string, bool, bool) { 204 for _, apiResourceList := range namespacedResourceList { 205 if apiResourceList.GroupVersion == groupVersion { 206 for _, apiResource := range apiResourceList.APIResources { 207 if apiResource.Kind == kind { 208 return apiResource.Name, apiResource.Namespaced, true 209 } 210 } 211 } 212 } 213 return "", false, false 214 } 215 216 func buildRulesFromGroups(groups map[string]map[string]struct{}) []rbacv1.PolicyRule { 217 rules := []rbacv1.PolicyRule{} 218 for group, resourceNames := range groups { 219 resources := []string{} 220 for resource := range resourceNames { 221 resources = append(resources, resource) 222 } 223 sort.Strings(resources) 224 rules = append(rules, rbacv1.PolicyRule{ 225 APIGroups: []string{group}, 226 Resources: resources, 227 Verbs: []string{rbacv1.VerbAll}, 228 }) 229 } 230 return rules 231 }