From bee4e9c033a6a334b62ea0ce0f6a18017ad5f7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Carlos=20Mej=C3=ADas=20Rodr=C3=ADguez?= Date: Mon, 14 Oct 2019 02:01:48 -0400 Subject: [PATCH] Add commands to manage access to Docker resources (containers, networks, secrets, services and volumes) --- cmd/container.go | 15 +++++ cmd/containerAccess.go | 10 +++ cmd/network.go | 15 +++++ cmd/networkAccess.go | 10 +++ cmd/secret.go | 15 +++++ cmd/secretAccess.go | 10 +++ cmd/service.go | 15 +++++ cmd/serviceAccess.go | 10 +++ cmd/volume.go | 15 +++++ cmd/volumeAccess.go | 22 +++++++ common/accessControl.go | 132 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 269 insertions(+) create mode 100644 cmd/container.go create mode 100644 cmd/containerAccess.go create mode 100644 cmd/network.go create mode 100644 cmd/networkAccess.go create mode 100644 cmd/secret.go create mode 100644 cmd/secretAccess.go create mode 100644 cmd/service.go create mode 100644 cmd/serviceAccess.go create mode 100644 cmd/volume.go create mode 100644 cmd/volumeAccess.go create mode 100644 common/accessControl.go diff --git a/cmd/container.go b/cmd/container.go new file mode 100644 index 0000000..9c46079 --- /dev/null +++ b/cmd/container.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// containerCmd represents the container command +var containerCmd = &cobra.Command{ + Use: "container", + Short: "Manage containers", +} + +func init() { + rootCmd.AddCommand(containerCmd) +} diff --git a/cmd/containerAccess.go b/cmd/containerAccess.go new file mode 100644 index 0000000..ff486eb --- /dev/null +++ b/cmd/containerAccess.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "github.com/greenled/portainer-stack-utils/client" + "github.com/greenled/portainer-stack-utils/common" +) + +func init() { + common.AccessCmdInitFunc(containerCmd, client.ResourceContainer) +} diff --git a/cmd/network.go b/cmd/network.go new file mode 100644 index 0000000..7ecc17d --- /dev/null +++ b/cmd/network.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// networkCmd represents the network command +var networkCmd = &cobra.Command{ + Use: "network", + Short: "Manage networks", +} + +func init() { + rootCmd.AddCommand(networkCmd) +} diff --git a/cmd/networkAccess.go b/cmd/networkAccess.go new file mode 100644 index 0000000..e3331f5 --- /dev/null +++ b/cmd/networkAccess.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "github.com/greenled/portainer-stack-utils/client" + "github.com/greenled/portainer-stack-utils/common" +) + +func init() { + common.AccessCmdInitFunc(networkCmd, client.ResourceNetwork) +} diff --git a/cmd/secret.go b/cmd/secret.go new file mode 100644 index 0000000..1e0d429 --- /dev/null +++ b/cmd/secret.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// secretCmd represents the secret command +var secretCmd = &cobra.Command{ + Use: "secret", + Short: "Manage secrets", +} + +func init() { + rootCmd.AddCommand(secretCmd) +} diff --git a/cmd/secretAccess.go b/cmd/secretAccess.go new file mode 100644 index 0000000..85491bd --- /dev/null +++ b/cmd/secretAccess.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "github.com/greenled/portainer-stack-utils/client" + "github.com/greenled/portainer-stack-utils/common" +) + +func init() { + common.AccessCmdInitFunc(secretCmd, client.ResourceSecret) +} diff --git a/cmd/service.go b/cmd/service.go new file mode 100644 index 0000000..7a1e25e --- /dev/null +++ b/cmd/service.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// serviceCmd represents the service command +var serviceCmd = &cobra.Command{ + Use: "service", + Short: "Manage services", +} + +func init() { + rootCmd.AddCommand(serviceCmd) +} diff --git a/cmd/serviceAccess.go b/cmd/serviceAccess.go new file mode 100644 index 0000000..99eb4d6 --- /dev/null +++ b/cmd/serviceAccess.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "github.com/greenled/portainer-stack-utils/client" + "github.com/greenled/portainer-stack-utils/common" +) + +func init() { + common.AccessCmdInitFunc(serviceCmd, client.ResourceService) +} diff --git a/cmd/volume.go b/cmd/volume.go new file mode 100644 index 0000000..7e4f324 --- /dev/null +++ b/cmd/volume.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// volumeCmd represents the volume command +var volumeCmd = &cobra.Command{ + Use: "volume", + Short: "Manage volumes", +} + +func init() { + rootCmd.AddCommand(volumeCmd) +} diff --git a/cmd/volumeAccess.go b/cmd/volumeAccess.go new file mode 100644 index 0000000..a8f699b --- /dev/null +++ b/cmd/volumeAccess.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/greenled/portainer-stack-utils/client" + "github.com/greenled/portainer-stack-utils/common" + "github.com/spf13/viper" +) + +func init() { + volumeAccessCmd := common.NewAccessCmd(client.ResourceVolume, "volumeName") + + volumeCmd.AddCommand(volumeAccessCmd) + + volumeAccessCmd.Flags().String("endpoint", "", "Endpoint name.") + volumeAccessCmd.Flags().Bool("admins", false, "Permit access to this volume to administrators only.") + volumeAccessCmd.Flags().Bool("private", false, "Permit access to this volume to the current user only.") + volumeAccessCmd.Flags().Bool("public", false, "Permit access to this volume to any user.") + viper.BindPFlag("volume.access.endpoint", volumeAccessCmd.Flags().Lookup("endpoint")) + viper.BindPFlag("volume.access.admins", volumeAccessCmd.Flags().Lookup("admins")) + viper.BindPFlag("volume.access.private", volumeAccessCmd.Flags().Lookup("private")) + viper.BindPFlag("volume.access.public", volumeAccessCmd.Flags().Lookup("public")) +} diff --git a/common/accessControl.go b/common/accessControl.go new file mode 100644 index 0000000..2ba19df --- /dev/null +++ b/common/accessControl.go @@ -0,0 +1,132 @@ +package common + +import ( + "fmt" + "net/http" + + "github.com/greenled/portainer-stack-utils/client" + portainer "github.com/portainer/portainer/api" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// NewAccessCmd creates a new Cobra command for Docker resource access control management +func NewAccessCmd(resourceType client.ResourceType, argumentName string) *cobra.Command { + return &cobra.Command{ + Use: fmt.Sprintf("access <%s>", argumentName), + Short: fmt.Sprintf("Set access control for %s", resourceType), + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + portainerClient, clientRetrievalErr := GetClient() + CheckError(clientRetrievalErr) + + resourceID := args[0] + + setAdmins := viper.GetBool(fmt.Sprintf("%s.access.admins", resourceType)) + setPrivate := viper.GetBool(fmt.Sprintf("%s.access.private", resourceType)) + setPublic := viper.GetBool(fmt.Sprintf("%s.access.public", resourceType)) + + if (setAdmins && setPrivate) || (setPrivate && setPublic) || (setPublic && setAdmins) { + logrus.Fatal("only one of --admins, --private or --public flags can be used") + } + + var endpoint portainer.Endpoint + if endpointName := viper.GetString(fmt.Sprintf("%s.access.endpoint", resourceType)); endpointName == "" { + // Guess endpoint if not set + logrus.WithFields(logrus.Fields{ + "implications": "Command will fail if there is not exactly one endpoint available", + }).Warning("Endpoint not set") + var endpointRetrievalErr error + endpoint, endpointRetrievalErr = GetDefaultEndpoint() + CheckError(endpointRetrievalErr) + endpointName = endpoint.Name + logrus.WithFields(logrus.Fields{ + "endpoint": endpointName, + }).Debug("Using the only available endpoint") + } else { + // Get endpoint by name + var endpointRetrievalErr error + endpoint, endpointRetrievalErr = GetEndpointByName(endpointName) + CheckError(endpointRetrievalErr) + } + + logrus.WithFields(logrus.Fields{ + "endpoint": endpoint.Name, + }).Debug(fmt.Sprintf("Getting %s access control info", resourceType)) + + if setAdmins { + // We are removing an access control + resourceControl, err := GetDockerResourcePortainerAccessControl(endpoint.ID, resourceID, resourceType) + if err == nil { + err = portainerClient.ResourceControlDelete(resourceControl.ID) + } else if err != ErrAccessControlNotFound { + CheckError(err) + } + } else { + // We may be creating a new access control + resourceControlCreateOptions := client.ResourceControlCreateOptions{ + ResourceID: resourceID, + Type: resourceType, + } + if setPrivate { + currentUser, err := GetUserByName(portainerClient.GetUsername()) + CheckError(err) + resourceControlCreateOptions.Users = []portainer.UserID{currentUser.ID} + } + if setPublic { + resourceControlCreateOptions.Public = true + } + _, err := portainerClient.ResourceControlCreate(resourceControlCreateOptions) + + if err != nil { + genericError, isGenericError := err.(*client.GenericError) + if isGenericError && genericError.Code == http.StatusConflict { + // We are updating an existing access control + resourceControl, err := GetDockerResourcePortainerAccessControl(endpoint.ID, resourceID, resourceType) + if err == nil { + resourceControlUpdateOptions := client.ResourceControlUpdateOptions{ + ID: resourceControl.ID, + } + if setPrivate { + currentUser, err := GetUserByName(portainerClient.GetUsername()) + CheckError(err) + resourceControlUpdateOptions.Users = []portainer.UserID{currentUser.ID} + } + if setPublic { + resourceControlUpdateOptions.Public = true + } + _, err := portainerClient.ResourceControlUpdate(resourceControlUpdateOptions) + CheckError(err) + } else { + // Something else happened + CheckError(err) + } + } else { + // Something else happened + CheckError(err) + } + } + } + + logrus.WithFields(logrus.Fields{ + string(resourceType): resourceID, + }).Info("Access control set") + }, + } +} + +// AccessCmdInitFunc creates an access command for a given Docker resource type +func AccessCmdInitFunc(parentCmd *cobra.Command, resourceControlType client.ResourceType) { + accessCmd := NewAccessCmd(resourceControlType, fmt.Sprintf("|%sId", resourceControlType)) + parentCmd.AddCommand(accessCmd) + + accessCmd.Flags().String("endpoint", "", "Endpoint name.") + accessCmd.Flags().Bool("admins", false, fmt.Sprintf("Permit access to this %s to administrators only.", resourceControlType)) + accessCmd.Flags().Bool("private", false, fmt.Sprintf("Permit access to this %s to the current user only.", resourceControlType)) + accessCmd.Flags().Bool("public", false, fmt.Sprintf("Permit access to this %s to any user.", resourceControlType)) + viper.BindPFlag(fmt.Sprintf("%s.access.endpoint", resourceControlType), accessCmd.Flags().Lookup("endpoint")) + viper.BindPFlag(fmt.Sprintf("%s.access.admins", resourceControlType), accessCmd.Flags().Lookup("admins")) + viper.BindPFlag(fmt.Sprintf("%s.access.private", resourceControlType), accessCmd.Flags().Lookup("private")) + viper.BindPFlag(fmt.Sprintf("%s.access.public", resourceControlType), accessCmd.Flags().Lookup("public")) +}