diff --git a/cmd/agent.go b/cmd/agent.go index 9de8e849..0935cd5c 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -2,9 +2,12 @@ package cmd import ( "fmt" + "io/ioutil" + "log" "time" "github.com/jetstack/preflight/pkg/agent" + "github.com/jetstack/preflight/pkg/permissions" "github.com/spf13/cobra" ) @@ -27,9 +30,30 @@ var agentInfoCmd = &cobra.Command{ }, } +var agentRBACCmd = &cobra.Command{ + Use: "rbac", + Short: "print the agent's minimal RBAC manifest", + Long: `Print RBAC string by reading GVRs`, + Run: func(cmd *cobra.Command, args []string) { + + b, err := ioutil.ReadFile(agent.ConfigFilePath) + if err != nil { + log.Fatalf("Failed to read config file: %s", err) + } + config, err := agent.ParseConfig(b) + if err != nil { + log.Fatalf("Failed to parse config file: %s", err) + } + + out := permissions.GenerateFullManifest(config.DataGatherers) + fmt.Print(out) + }, +} + func init() { rootCmd.AddCommand(agentCmd) agentCmd.AddCommand(agentInfoCmd) + agentCmd.AddCommand(agentRBACCmd) agentCmd.PersistentFlags().StringVarP( &agent.ConfigFilePath, "agent-config-file", diff --git a/go.mod b/go.mod index cdc92c2e..e545010c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/aws/aws-sdk-go v1.36.19 github.com/cenkalti/backoff v2.2.1+incompatible github.com/d4l3k/messagediff v1.2.1 - github.com/fatih/color v1.12.0 // indirect + github.com/fatih/color v1.12.0 github.com/go-playground/universal-translator v0.17.0 // indirect github.com/hashicorp/go-multierror v1.1.0 github.com/jetstack/version-checker v0.2.2-0.20201118163251-4bab9ef088ef @@ -21,7 +21,7 @@ require ( github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect github.com/kylelemons/godebug v1.1.0 github.com/leodido/go-urn v1.2.0 // indirect - github.com/maxatome/go-testdeep v1.9.2 // indirect + github.com/maxatome/go-testdeep v1.9.2 github.com/pkg/errors v0.9.1 github.com/pmylund/go-cache v2.1.0+incompatible github.com/sirupsen/logrus v1.7.0 @@ -39,6 +39,7 @@ require ( k8s.io/apimachinery v0.20.1 k8s.io/client-go v11.0.0+incompatible k8s.io/utils v0.0.0-20201110183641-67b214c5f920 + sigs.k8s.io/yaml v1.2.0 ) replace k8s.io/client-go => k8s.io/client-go v0.20.1 diff --git a/pkg/permissions/generate.go b/pkg/permissions/generate.go index 61711ea3..8894cd05 100644 --- a/pkg/permissions/generate.go +++ b/pkg/permissions/generate.go @@ -2,11 +2,13 @@ package permissions import ( "fmt" + "strings" "github.com/jetstack/preflight/pkg/agent" "github.com/jetstack/preflight/pkg/datagatherer/k8s" rbac "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" ) // AgentRBACManifests is a wrapper around the various RBAC structs needed to grant the agent fine-grained permissions as per its dg configs @@ -113,3 +115,64 @@ func GenerateAgentRBACManifests(dataGatherers []agent.DataGatherer) AgentRBACMan return AgentRBACManifests } + +func createClusterRoleString(clusterRoles []rbac.ClusterRole) string { + var builder strings.Builder + for _, cb := range clusterRoles { + data, err := yaml.Marshal(cb) + if err != nil { + fmt.Print("Cluster Role fails to marshal") + } + + builder.WriteString("\n") + builder.Write(data) + builder.WriteString("---") + } + + return builder.String() +} +func createRoleBindingString(roleBindings []rbac.RoleBinding) string { + var builder strings.Builder + for _, cb := range roleBindings { + data, err := yaml.Marshal(cb) + if err != nil { + fmt.Print("Role Binding fails to marshal") + } + + builder.WriteString("\n") + builder.Write(data) + builder.WriteString("---") + } + + return builder.String() +} +func createClusterRoleBindingString(clusterRoleBindings []rbac.ClusterRoleBinding) string { + var builder strings.Builder + for _, cb := range clusterRoleBindings { + data, err := yaml.Marshal(cb) + if err != nil { + fmt.Print("Cluster Role Binding fails to marshal") + } + + builder.WriteString("\n") + builder.Write(data) + builder.WriteString("---") + } + + return builder.String() +} + +func GenerateFullManifest(dataGatherers []agent.DataGatherer) string { + agentRBACManifestsStruct := GenerateAgentRBACManifests(dataGatherers) + agentCLR := createClusterRoleString(agentRBACManifestsStruct.ClusterRoles) + agentCLRB := createClusterRoleBindingString(agentRBACManifestsStruct.ClusterRoleBindings) + agentRB := createRoleBindingString(agentRBACManifestsStruct.RoleBindings) + + out := fmt.Sprintf(`%s%s%s`, agentCLR, agentCLRB, agentRB) + out = strings.TrimPrefix(out, "\n") + out = strings.TrimSpace(out) + out = strings.ReplaceAll(out, "\n creationTimestamp: null", "") + + return out + +} diff --git a/pkg/permissions/generate_test.go b/pkg/permissions/generate_test.go index 8efbc6de..5591e4c1 100644 --- a/pkg/permissions/generate_test.go +++ b/pkg/permissions/generate_test.go @@ -11,6 +11,187 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +func TestGenerateAgentRBACManifestsString(t *testing.T) { + testCases := []struct { + description string + dataGatherers []agent.DataGatherer + expectedRBACManifests string + }{ + { + description: "Generate ClusterRole and ClusterRoleBinding for simple pod dg use case", + dataGatherers: []agent.DataGatherer{ + { + Name: "k8s/pods", + Kind: "k8s-dynamic", + Config: &k8s.ConfigDynamic{ + GroupVersionResource: schema.GroupVersionResource{ + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + expectedRBACManifests: `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jetstack-secure-agent-pods-reader +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: jetstack-secure-agent-pods-reader +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: jetstack-secure-agent-pods-reader +subjects: +- kind: ServiceAccount + name: agent + namespace: jetstack-secure +---`, + }, + { + description: "Generate ClusterRole and RoleBinding for simple pod dg with include namespace \"foobar\"", + dataGatherers: []agent.DataGatherer{ + { + Name: "k8s/pods", + Kind: "k8s-dynamic", + Config: &k8s.ConfigDynamic{ + IncludeNamespaces: []string{"foobar"}, + GroupVersionResource: schema.GroupVersionResource{ + Version: "v1", + Resource: "pods", + }, + }, + }, + }, + expectedRBACManifests: `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jetstack-secure-agent-pods-reader +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: jetstack-secure-agent-pods-reader + namespace: foobar +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: jetstack-secure-agent-pods-reader +subjects: +- kind: ServiceAccount + name: agent + namespace: jetstack-secure +---`, + }, + { + description: "Generate multiple ClusterRoles and ClusterRoleBindings for simple pod and nodes dg use case", + dataGatherers: []agent.DataGatherer{ + { + Name: "k8s/pods", + Kind: "k8s-dynamic", + Config: &k8s.ConfigDynamic{ + GroupVersionResource: schema.GroupVersionResource{ + Version: "v1", + Resource: "pods", + }, + }, + }, + { + Name: "k8s/nodes", + Kind: "k8s-dynamic", + Config: &k8s.ConfigDynamic{ + GroupVersionResource: schema.GroupVersionResource{ + Version: "v1", + Resource: "nodes", + }, + }, + }, + }, + expectedRBACManifests: `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jetstack-secure-agent-pods-reader +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jetstack-secure-agent-nodes-reader +rules: +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: jetstack-secure-agent-pods-reader +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: jetstack-secure-agent-pods-reader +subjects: +- kind: ServiceAccount + name: agent + namespace: jetstack-secure +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: jetstack-secure-agent-nodes-reader +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: jetstack-secure-agent-nodes-reader +subjects: +- kind: ServiceAccount + name: agent + namespace: jetstack-secure +---`, + }, + } + + for _, input := range testCases { + got := GenerateFullManifest(input.dataGatherers) + if input.expectedRBACManifests != got { + t.Errorf("value mismatch, \n**********expected:******************************\n%s\n**********got:******************************\n%s", input.expectedRBACManifests, got) + } + } +} + func TestGenerateAgentRBACManifests(t *testing.T) { testCases := []struct { description string