Skip to content

Commit

Permalink
feat: influxd recovery-cli allows creating recovery user/token
Browse files Browse the repository at this point in the history
Closes #12051
  • Loading branch information
lesam committed Oct 6, 2021
1 parent 7c19225 commit 6fd8715
Show file tree
Hide file tree
Showing 15 changed files with 910 additions and 29 deletions.
8 changes: 4 additions & 4 deletions cmd/influxd/launcher/replication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ func TestValidateReplication_Invalid(t *testing.T) {

// Create a new bucket.
bucket2 := influxdb.Bucket{
OrgID: l.Org.ID,
Name: "bucket2",
RetentionPeriod: 0,
ShardGroupDuration: 0,
OrgID: l.Org.ID,
Name: "bucket2",
RetentionPeriod: 0,
ShardGroupDuration: 0,
}
require.NoError(t, l.BucketService(t).CreateBucket(ctx, &bucket2))
bucket2Id := bucket2.ID.String()
Expand Down
2 changes: 2 additions & 0 deletions cmd/influxd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/cmd/influxd/inspect"
"github.com/influxdata/influxdb/v2/cmd/influxd/launcher"
"github.com/influxdata/influxdb/v2/cmd/influxd/recovery"
"github.com/influxdata/influxdb/v2/cmd/influxd/upgrade"
_ "github.com/influxdata/influxdb/v2/tsdb/engine/tsm1"
_ "github.com/influxdata/influxdb/v2/tsdb/index/tsi1"
Expand Down Expand Up @@ -48,6 +49,7 @@ func main() {
}
rootCmd.AddCommand(inspectCmd)
rootCmd.AddCommand(versionCmd())
rootCmd.AddCommand(recovery.NewCommand())

rootCmd.SilenceUsage = true
if err := rootCmd.Execute(); err != nil {
Expand Down
232 changes: 232 additions & 0 deletions cmd/influxd/recovery/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package auth

import (
"context"
"fmt"
"io"
"os"
"path/filepath"

"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/authorization"
"github.com/influxdata/influxdb/v2/bolt"
"github.com/influxdata/influxdb/v2/internal/tabwriter"
"github.com/influxdata/influxdb/v2/logger"
"github.com/influxdata/influxdb/v2/tenant"
"github.com/spf13/cobra"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

func NewAuthCommand() *cobra.Command {
base := &cobra.Command{
Use: "auth",
Short: "On-disk authorization management commands, for recovery",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cmd.PrintErrf("See '%s -h' for help\n", cmd.CommandPath())
},
}

base.AddCommand(NewAuthListCommand())
base.AddCommand(NewAuthCreateCommand())

return base
}

type authListCommand struct {
logger *zap.Logger
boltPath string
out io.Writer
}

func NewAuthListCommand() *cobra.Command {
var authCmd authListCommand
cmd := &cobra.Command{
Use: "list",
Short: "List authorizations",
RunE: func(cmd *cobra.Command, args []string) error {
config := logger.NewConfig()
config.Level = zapcore.InfoLevel

newLogger, err := config.New(cmd.ErrOrStderr())
if err != nil {
return err
}
authCmd.logger = newLogger
authCmd.out = cmd.OutOrStdout()
return authCmd.run()
},
}

defaultPath := filepath.Join(os.Getenv("HOME"), ".influxdbv2", "influxd.bolt")

cmd.Flags().StringVar(&authCmd.boltPath, "bolt-path", defaultPath, "Path to the TSM data directory.")

return cmd
}

func (cmd *authListCommand) run() error {
ctx := context.Background()
store := bolt.NewKVStore(cmd.logger.With(zap.String("system", "bolt-kvstore")), cmd.boltPath)
if err := store.Open(ctx); err != nil {
return err
}
defer store.Close()
tenantStore := tenant.NewStore(store)
tenantService := tenant.NewService(tenantStore)
authStore, err := authorization.NewStore(store)
if err != nil {
return err
}
auth := authorization.NewService(authStore, tenantService)
filter := influxdb.AuthorizationFilter{}
auths, _, err := auth.FindAuthorizations(ctx, filter)
if err != nil {
return err
}

return PrintAuth(ctx, cmd.out, auths, tenantService)
}

type authCreateCommand struct {
logger *zap.Logger
boltPath string
out io.Writer
username string
org string
}

func NewAuthCreateCommand() *cobra.Command {
var authCmd authCreateCommand
cmd := &cobra.Command{
Use: "create-operator",
Short: "Create new operator token for a user",
RunE: func(cmd *cobra.Command, args []string) error {
config := logger.NewConfig()
config.Level = zapcore.InfoLevel

newLogger, err := config.New(cmd.ErrOrStderr())
if err != nil {
return err
}
authCmd.logger = newLogger
authCmd.out = cmd.OutOrStdout()
return authCmd.run()
},
}

defaultPath := filepath.Join(os.Getenv("HOME"), ".influxdbv2", "influxd.bolt")
cmd.Flags().StringVar(&authCmd.boltPath, "bolt-path", defaultPath, "Path to the TSM data directory")
cmd.Flags().StringVar(&authCmd.username, "username", "", "Name of the user")
cmd.Flags().StringVar(&authCmd.org, "org", "", "Name of the org (if not provided one will be selected)")

return cmd
}

func (cmd *authCreateCommand) run() error {
ctx := context.Background()
store := bolt.NewKVStore(cmd.logger.With(zap.String("system", "bolt-kvstore")), cmd.boltPath)
if err := store.Open(ctx); err != nil {
return err
}
defer store.Close()
tenantStore := tenant.NewStore(store)
tenantService := tenant.NewService(tenantStore)
authStore, err := authorization.NewStore(store)
if err != nil {
return err
}
auth := authorization.NewService(authStore, tenantService)

if cmd.username == "" {
return fmt.Errorf("must provide --username")
}

// Find the user
user, err := tenantService.FindUser(ctx, influxdb.UserFilter{Name: &cmd.username})
if err != nil {
return fmt.Errorf("finding user %q: %w", cmd.username, err)
}

// Find an organization
var org *influxdb.Organization
if cmd.org == "" {
orgs, _, err := tenantService.FindOrganizations(ctx, influxdb.OrganizationFilter{})
if err != nil {
return fmt.Errorf("could not find any organization: %w", err)
}
if len(orgs) == 0 {
return fmt.Errorf("could not find any organization: %w", err)
}
org = orgs[0]
} else {
orgs, _, err := tenantService.FindOrganizations(ctx, influxdb.OrganizationFilter{
Name: &cmd.org,
})
if err != nil {
return fmt.Errorf("finding org %q: %w", cmd.org, err)
}
org = orgs[0]
}

// Create operator token
authToCreate := &influxdb.Authorization{
Description: fmt.Sprintf("%s's Recovery Token", cmd.username),
Permissions: influxdb.OperPermissions(),
UserID: user.ID,
OrgID: org.ID,
}
if err := auth.CreateAuthorization(ctx, authToCreate); err != nil {
return err
}

// Print all authorizations now that we have added one
filter := influxdb.AuthorizationFilter{}
auths, _, err := auth.FindAuthorizations(ctx, filter)
if err != nil {
return err
}
return PrintAuth(ctx, cmd.out, auths, tenantService)
}

func PrintAuth(ctx context.Context, w io.Writer, v []*influxdb.Authorization, userSvc influxdb.UserService) error {
headers := []string{
"ID",
"User Name",
"User ID",
"Description",
"Token",
"Permissions",
}

var rows []map[string]interface{}
for _, t := range v {
user, err := userSvc.FindUserByID(ctx, t.UserID)
userName := ""
if err == nil && user != nil {
userName = user.Name
}
row := map[string]interface{}{
"ID": t.ID,
"Description": t.Description,
"User Name": userName,
"User ID": t.UserID,
"Token": t.Token,
"Permissions": t.Permissions,
}
rows = append(rows, row)
}

writer := tabwriter.NewTabWriter(w, false)
defer writer.Flush()
if err := writer.WriteHeaders(headers...); err != nil {
return err
}
for _, row := range rows {
if err := writer.Write(row); err != nil {
return err
}
}
return nil
}
34 changes: 34 additions & 0 deletions cmd/influxd/recovery/auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package auth

import (
"testing"

"github.com/influxdata/influxdb/v2/cmd/influxd/recovery/testhelper"
"github.com/stretchr/testify/assert"
)

func Test_Auth_Basic(t *testing.T) {
db := testhelper.NewTestBoltDb(t)
defer db.Close()
assert.Equal(t, ""+
`ID User Name User ID Description Token Permissions`+"\n"+
`08371db24dcc8000 testuser 08371db1dd8c8000 testuser's Token A9Ovdl8SmP-rfp8wQ2vJoPUsZoQQJ3EochD88SlJcgrcLw4HBwgUqpSHQxc9N9Drg0_aY6Lp1jutBRcKhbV7aQ== [read:authorizations write:authorizations read:buckets write:buckets read:dashboards write:dashboards read:orgs write:orgs read:sources write:sources read:tasks write:tasks read:telegrafs write:telegrafs read:users write:users read:variables write:variables read:scrapers write:scrapers read:secrets write:secrets read:labels write:labels read:views write:views read:documents write:documents read:notificationRules write:notificationRules read:notificationEndpoints write:notificationEndpoints read:checks write:checks read:dbrp write:dbrp read:notebooks write:notebooks read:annotations write:annotations]`+"\n"+
`08371deae98c8000 testuser 08371db1dd8c8000 testuser's read buckets token 4-pZrlm84u9uiMVrPBeITe46KxfdEnvTX5H2CZh38BtAsXX4O47b8QwZ9jHL_Cek2w-VbVfRxDpo0Mu8ORiqyQ== [read:orgs/dd7cd2292f6e974a/buckets]`+"\n",
testhelper.MustRunCommand(t, NewAuthCommand(), "list", "--bolt-path", db.Name()))

// org name not created
assert.EqualError(t, testhelper.RunCommand(t, NewAuthCommand(), "create-operator", "--bolt-path", db.Name(), "--org", "not-exist", "--username", "testuser"), "finding org \"not-exist\": organization name \"not-exist\" not found")

// user not created
assert.EqualError(t, testhelper.RunCommand(t, NewAuthCommand(), "create-operator", "--bolt-path", db.Name(), "--org", "my-org", "--username", "testuser2"), "finding user \"testuser2\": user not found")

// existing user creates properly
assert.NoError(t, testhelper.RunCommand(t, NewAuthCommand(), "create-operator", "--bolt-path", db.Name(), "--username", "testuser"))

assert.Regexp(t, ""+
`ID User Name User ID Description Token Permissions`+"\n"+
`08371db24dcc8000 testuser 08371db1dd8c8000 testuser's Token A9Ovdl8SmP-rfp8wQ2vJoPUsZoQQJ3EochD88SlJcgrcLw4HBwgUqpSHQxc9N9Drg0_aY6Lp1jutBRcKhbV7aQ== \[read:authorizations write:authorizations read:buckets write:buckets read:dashboards write:dashboards read:orgs write:orgs read:sources write:sources read:tasks write:tasks read:telegrafs write:telegrafs read:users write:users read:variables write:variables read:scrapers write:scrapers read:secrets write:secrets read:labels write:labels read:views write:views read:documents write:documents read:notificationRules write:notificationRules read:notificationEndpoints write:notificationEndpoints read:checks write:checks read:dbrp write:dbrp read:notebooks write:notebooks read:annotations write:annotations\]`+"\n"+
`08371deae98c8000 testuser 08371db1dd8c8000 testuser's read buckets token 4-pZrlm84u9uiMVrPBeITe46KxfdEnvTX5H2CZh38BtAsXX4O47b8QwZ9jHL_Cek2w-VbVfRxDpo0Mu8ORiqyQ== \[read:orgs/dd7cd2292f6e974a/buckets\]`+"\n"+
`[^\t]* testuser [^\t]* testuser's Recovery Token [^\t]* \[read:authorizations write:authorizations read:buckets write:buckets read:dashboards write:dashboards read:orgs write:orgs read:sources write:sources read:tasks write:tasks read:telegrafs write:telegrafs read:users write:users read:variables write:variables read:scrapers write:scrapers read:secrets write:secrets read:labels write:labels read:views write:views read:documents write:documents read:notificationRules write:notificationRules read:notificationEndpoints write:notificationEndpoints read:checks write:checks read:dbrp write:dbrp read:notebooks write:notebooks read:annotations write:annotations\]`+"\n",
testhelper.MustRunCommand(t, NewAuthCommand(), "list", "--bolt-path", db.Name()))
}
Loading

0 comments on commit 6fd8715

Please sign in to comment.