diff --git a/pkg/backup/service_account_action.go b/pkg/backup/service_account_action.go new file mode 100644 index 00000000000..6ef0e97f4e4 --- /dev/null +++ b/pkg/backup/service_account_action.go @@ -0,0 +1,110 @@ +/* +Copyright 2018 the Heptio Ark contributors. + +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 backup + +import ( + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + rbac "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/kubernetes" + + "github.com/heptio/ark/pkg/apis/ark/v1" +) + +// serviceAccountAction implements ItemAction. +type serviceAccountAction struct { + log logrus.FieldLogger + clientset kubernetes.Interface +} + +// NewServiceAccountAction creates a new ItemAction for service accounts. +func NewServiceAccountAction(log logrus.FieldLogger, clientset kubernetes.Interface) ItemAction { + return &serviceAccountAction{ + log: log, + clientset: clientset, + } +} + +// AppliesTo returns a ResourceSelector that applies only to service accounts. +func (a *serviceAccountAction) AppliesTo() (ResourceSelector, error) { + return ResourceSelector{ + IncludedResources: []string{"serviceaccounts"}, + }, nil +} + +var ( + crbGroupResource = schema.GroupResource{Group: "rbac.authorization.k8s.io", Resource: "clusterrolebindings"} + crGroupResource = schema.GroupResource{Group: "rbac.authorization.k8s.io", Resource: "clusterroles"} +) + +// Execute checks for any ClusterRoleBindings that have this service account as a subject, and +// adds the ClusterRoleBinding and associated ClusterRole to the list of additional items to +// be backed up. +func (a *serviceAccountAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []ResourceIdentifier, error) { + a.log.Info("Running serviceAccountAction") + defer a.log.Info("Done running serviceAccountAction") + + crbList, err := a.clientset.RbacV1().ClusterRoleBindings().List(metav1.ListOptions{}) + if err != nil { + return nil, nil, errors.WithStack(err) + } + + objectMeta, err := meta.Accessor(item) + if err != nil { + return nil, nil, errors.WithStack(err) + } + + var ( + namespace = objectMeta.GetNamespace() + name = objectMeta.GetName() + bindings = sets.NewString() + roles = sets.NewString() + ) + + for _, crb := range crbList.Items { + for _, subj := range crb.Subjects { + if subj.Kind == rbac.ServiceAccountKind && subj.Namespace == namespace && subj.Name == name { + bindings.Insert(crb.Name) + roles.Insert(crb.RoleRef.Name) + break + } + } + } + + var additionalItems []ResourceIdentifier + for binding := range bindings { + additionalItems = append(additionalItems, ResourceIdentifier{ + GroupResource: crbGroupResource, + Name: binding, + }) + } + + for role := range roles { + additionalItems = append(additionalItems, ResourceIdentifier{ + GroupResource: crGroupResource, + Name: role, + }) + } + + return item, additionalItems, nil +} diff --git a/pkg/cmd/ark/ark.go b/pkg/cmd/ark/ark.go index 8db5a60cafd..2a814777388 100644 --- a/pkg/cmd/ark/ark.go +++ b/pkg/cmd/ark/ark.go @@ -62,7 +62,7 @@ operations can also be performed as 'ark backup get' and 'ark schedule create'.` get.NewCommand(f), describe.NewCommand(f), create.NewCommand(f), - runplugin.NewCommand(), + runplugin.NewCommand(f), plugin.NewCommand(f), delete.NewCommand(f), cliclient.NewCommand(), diff --git a/pkg/cmd/server/plugin/plugin.go b/pkg/cmd/server/plugin/plugin.go index ec575bdd8e7..696264afa1a 100644 --- a/pkg/cmd/server/plugin/plugin.go +++ b/pkg/cmd/server/plugin/plugin.go @@ -22,40 +22,19 @@ import ( "github.com/spf13/cobra" "github.com/heptio/ark/pkg/backup" + "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cloudprovider" "github.com/heptio/ark/pkg/cloudprovider/aws" "github.com/heptio/ark/pkg/cloudprovider/azure" "github.com/heptio/ark/pkg/cloudprovider/gcp" + "github.com/heptio/ark/pkg/cmd" arkplugin "github.com/heptio/ark/pkg/plugin" "github.com/heptio/ark/pkg/restore" ) -func NewCommand() *cobra.Command { +func NewCommand(f client.Factory) *cobra.Command { logger := arkplugin.NewLogger() - objectStores := map[string]cloudprovider.ObjectStore{ - "aws": aws.NewObjectStore(), - "gcp": gcp.NewObjectStore(), - "azure": azure.NewObjectStore(), - } - - blockStores := map[string]cloudprovider.BlockStore{ - "aws": aws.NewBlockStore(), - "gcp": gcp.NewBlockStore(logger), - "azure": azure.NewBlockStore(), - } - - backupItemActions := map[string]backup.ItemAction{ - "pv": backup.NewBackupPVAction(logger), - "pod": backup.NewPodAction(logger), - } - - restoreItemActions := map[string]restore.ItemAction{ - "job": restore.NewJobAction(logger), - "pod": restore.NewPodAction(logger), - "svc": restore.NewServiceAction(logger), - } - c := &cobra.Command{ Use: "run-plugin [KIND] [NAME]", Hidden: true, @@ -72,18 +51,24 @@ func NewCommand() *cobra.Command { GRPCServer: plugin.DefaultGRPCServer, } - logger.Debugf("Executing run-plugin command") + logger.Debug("Executing run-plugin command") switch kind { case "cloudprovider": - objectStore, found := objectStores[name] - if !found { - logger.Fatalf("Unrecognized plugin name") - } - - blockStore, found := blockStores[name] - if !found { - logger.Fatalf("Unrecognized plugin name") + var ( + objectStore cloudprovider.ObjectStore + blockStore cloudprovider.BlockStore + ) + + switch name { + case "aws": + objectStore, blockStore = aws.NewObjectStore(), aws.NewBlockStore() + case "azure": + objectStore, blockStore = azure.NewObjectStore(), azure.NewBlockStore() + case "gcp": + objectStore, blockStore = gcp.NewObjectStore(), gcp.NewBlockStore(logger) + default: + logger.Fatal("Unrecognized plugin name") } serveConfig.Plugins = map[string]plugin.Plugin{ @@ -91,25 +76,44 @@ func NewCommand() *cobra.Command { string(arkplugin.PluginKindBlockStore): arkplugin.NewBlockStorePlugin(blockStore), } case arkplugin.PluginKindBackupItemAction.String(): - action, found := backupItemActions[name] - if !found { - logger.Fatalf("Unrecognized plugin name") + var action backup.ItemAction + + switch name { + case "pv": + action = backup.NewBackupPVAction(logger) + case "pod": + action = backup.NewPodAction(logger) + case "serviceaccount": + clientset, err := f.KubeClient() + cmd.CheckError(err) + + action = backup.NewServiceAccountAction(logger, clientset) + default: + logger.Fatal("Unrecognized plugin name") } serveConfig.Plugins = map[string]plugin.Plugin{ kind: arkplugin.NewBackupItemActionPlugin(action), } case arkplugin.PluginKindRestoreItemAction.String(): - action, found := restoreItemActions[name] - if !found { - logger.Fatalf("Unrecognized plugin name") + var action restore.ItemAction + + switch name { + case "job": + action = restore.NewJobAction(logger) + case "pod": + action = restore.NewPodAction(logger) + case "svc": + action = restore.NewServiceAction(logger) + default: + logger.Fatal("Unrecognized plugin name") } serveConfig.Plugins = map[string]plugin.Plugin{ kind: arkplugin.NewRestoreItemActionPlugin(action), } default: - logger.Fatalf("Unsupported plugin kind") + logger.Fatal("Unsupported plugin kind") } plugin.Serve(serveConfig) diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 7bd6a637a9b..3136bb9ed0e 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -184,6 +184,7 @@ func (m *manager) registerPlugins() error { } m.pluginRegistry.register("pv", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "pv"}, PluginKindBackupItemAction) m.pluginRegistry.register("backup-pod", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "pod"}, PluginKindBackupItemAction) + m.pluginRegistry.register("serviceaccount", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "serviceaccount"}, PluginKindBackupItemAction) m.pluginRegistry.register("job", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "job"}, PluginKindRestoreItemAction) m.pluginRegistry.register("restore-pod", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "pod"}, PluginKindRestoreItemAction)