commands

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2022 License: AGPL-3.0 Imports: 31 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Run = &cli.Command{
		Name:      "run",
		Usage:     "Runs the varys server process.",
		Flags:     flagset.ExtractPrefix("varys", runConfig),
		ArgsUsage: " ",
		Action: func(ctx *cli.Context) error {
			log := zaputil.Extract(ctx.Context)

			if v := runConfig.Basic.StaticGroups.Value(); len(v) == 0 {
				_ = runConfig.Basic.StaticGroups.Set("admin:varys")
			}

			tlsConfig, err := livetls.New(ctx.Context, runConfig.TLS)
			if err != nil {
				return err
			}

			log.Info("configuring auth", zap.String("kind", runConfig.AuthType))
			var authFn auth.HandlerFunc

			switch runConfig.AuthType {
			case "basic":
				authFn, err = basicauth.Handler(ctx.Context, runConfig.Basic)
				if err != nil {
					return err
				}
			case "oidc":
				return fmt.Errorf("oidc not yet supported")
			default:
				return fmt.Errorf("unsupported auth type: %s", runConfig.AuthType)
			}

			log.Info("opening database")
			encryptionKey := sha256.Sum256([]byte(runConfig.Database.Encryption.Key))

			opts := badger.DefaultOptions(runConfig.Database.Path)
			opts.Logger = zaputil.Badger(log)
			opts.EncryptionKey = encryptionKey[:]
			opts.EncryptionKeyRotationDuration = runConfig.Database.Encryption.KeyDuration
			opts.IndexCacheSize = 128 << 20

			db, err := badger.Open(opts)
			if err != nil {
				return err
			}
			defer db.Close()

			adapter := engine.NewCasbinAdapter(db)
			model, err := model.NewModelFromString(engine.Model)
			if err != nil {
				return err
			}

			enforcer, _ := casbin.NewEnforcer()
			enforcer.SetModel(model)
			enforcer.SetAdapter(adapter)

			enforcer.EnableAutoSave(true)
			enforcer.EnableAutoBuildRoleLinks(true)

			err = enforcer.LoadPolicy()
			if err != nil {
				return err
			}

			err = engine.EnsurePolicy(enforcer, engine.DefaultPolicy)
			if err != nil {
				return err
			}

			log.Info("setting up api")
			api := engine.NewAPI(db, enforcer, runConfig.Credential.RootKey)

			router := mux.NewRouter()
			router.StrictSlash(true)
			router.SkipClean(true)
			router.Use(mux.CORSMethodMiddleware(router))

			apiRouter := router.PathPrefix("/api/").Subrouter()
			apiRouter.Use(func(handler http.Handler) http.Handler {

				handler = engine.Middleware(handler, api, runConfig.AuthType)
				handler = httpauth.Handler(handler, authFn, auth.Required())
				handler = headers.HTTP(handler)

				return handler
			})

			credentials := apiRouter.PathPrefix("/v1/credentials").Subrouter()
			credentials.HandleFunc("/{kind}/{name}", api.ListCredentials).Methods(http.MethodGet)

			services := apiRouter.PathPrefix("/v1/services").Subrouter()
			services.HandleFunc("", api.ListServices).Methods(http.MethodGet)
			services.HandleFunc("", api.CreateService).Methods(http.MethodPost)
			services.HandleFunc("/{kind}/{name}", api.GetService).Methods(http.MethodGet)
			services.HandleFunc("/{kind}/{name}", api.UpdateService).Methods(http.MethodPut)
			services.HandleFunc("/{kind}/{name}", api.DeleteService).Methods(http.MethodDelete)
			services.HandleFunc("/{kind}/{name}/credentials", api.GetServiceCredentials).Methods(http.MethodGet)
			services.HandleFunc("/{kind}/{name}/grants", api.ListGrants).Methods(http.MethodGet)
			services.HandleFunc("/{kind}/{name}/grants", api.PutGrant).Methods(http.MethodPut)
			services.HandleFunc("/{kind}/{name}/grants", api.DeleteGrant).Methods(http.MethodDelete)

			users := apiRouter.PathPrefix("/v1/users").Subrouter()
			users.HandleFunc("", api.ListUsers).Methods(http.MethodGet)
			users.HandleFunc("/self", api.GetCurrentUser).Methods(http.MethodGet)
			users.HandleFunc("/self", api.UpdateCurrentUser).Methods(http.MethodPut)

			group, done := errgroup.WithContext(ctx.Context)
			group.Go(func() error {
				listener, err := net.Listen("tcp", runConfig.BindAddress)
				if err != nil {
					return err
				}

				if tlsConfig != nil {
					log.Info("setting up tls")
					listener = tls.NewListener(listener, tlsConfig)
				}

				svr := &http.Server{
					Handler: router,
					BaseContext: func(listener net.Listener) context.Context {
						return ctx.Context
					},
				}

				return svr.Serve(listener)
			})

			log.Info("starting", zap.String("address", runConfig.BindAddress))
			if log.Core().Enabled(zapcore.DebugLevel) {
				_ = router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
					path, _ := route.GetPathTemplate()
					methods, _ := route.GetMethods()

					if len(methods) > 0 {
						log.Debug("route", zap.String("path", path), zap.Strings("methods", methods))
					}

					return nil
				})
			}

			select {
			case <-done.Done():
				return group.Wait()
			case <-ctx.Done():
			}

			return nil
		},
		HideHelpCommand: true,
	}
)
View Source
var (
	Services = &cli.Command{
		Name:  "services",
		Usage: "Perform operations against the Services API.",
		Flags: flagset.ExtractPrefix("varys", &client.DefaultConfig),
		Before: func(ctx *cli.Context) error {
			api, err := client.NewAPI(client.DefaultConfig)
			if err != nil {
				return err
			}

			ctx.Context = client.WithContext(ctx.Context, api)
			return nil
		},
		Subcommands: []*cli.Command{
			{
				Name:      "connect",
				Usage:     "Connects to a service managed by varys.",
				ArgsUsage: "<kind> <name> [program...]",
				Action: func(ctx *cli.Context) error {
					args := ctx.Args().Slice()

					switch {
					case len(args) < 2:
						return fmt.Errorf("expecting two arguments: <kind> <name>")
					case len(args) < 3:

						return fmt.Errorf("missing program to execute")
					}

					kind := args[0]
					name := args[1]
					program := args[2:]

					api := client.Extract(ctx.Context)

					serviceCreds, err := api.Services().Credentials(ctx.Context, kind, name)
					if err != nil {
						return err
					}

					pargs := make([]string, 0)
					if len(program) > 1 {
						pargs = program[1:]
					}

					prefix := strings.ToUpper(kind) + "_" + strings.ToUpper(name)

					cmd := exec.Command(program[0], pargs...)
					cmd.Stdin = os.Stdin
					cmd.Stdout = os.Stdout
					cmd.Stderr = os.Stderr

					cmd.Env = append(cmd.Env,
						prefix+"_ADDRESS="+serviceCreds.Address,
						prefix+"_USERNAME="+serviceCreds.Credentials.Username,
						prefix+"_PASSWORD="+serviceCreds.Credentials.Password,
					)

					err = cmd.Run()
					if err != nil {
						return err
					}

					return nil
				},
			},
			{
				Name:      "create",
				Usage:     "Create a new service in varys.",
				ArgsUsage: "<kind> <name>",
				Flags:     flagset.ExtractPrefix("varys_create_service", &createServiceRequest),
				Action: func(ctx *cli.Context) error {
					args := ctx.Args()

					createServiceRequest.Kind = args.Get(0)
					createServiceRequest.Name = args.Get(1)

					if createServiceRequest.Kind == "" || createServiceRequest.Name == "" {
						return fmt.Errorf("expecting two arguments: <kind> <name>")
					}

					api := client.Extract(ctx.Context)

					return api.Services().Create(ctx.Context, createServiceRequest)
				},
			},
			{
				Name:      "delete",
				Usage:     "Delete a service in varys.",
				ArgsUsage: "<kind> <name>",
				Action: func(ctx *cli.Context) error {
					args := ctx.Args()

					kind := args.Get(0)
					name := args.Get(1)

					if kind == "" || name == "" {
						return fmt.Errorf("expecting two arguments: <kind> <name>")
					}

					api := client.Extract(ctx.Context)

					return api.Services().Delete(ctx.Context, kind, name)
				},
			},
			{
				Name:      "get",
				Usage:     "Get a service from varys.",
				ArgsUsage: "<kind> <name>",
				Action: func(ctx *cli.Context) error {
					args := ctx.Args()

					kind := args.Get(0)
					name := args.Get(1)

					if kind == "" || name == "" {
						return fmt.Errorf("expecting two arguments: <kind> <name>")
					}

					api := client.Extract(ctx.Context)

					service, err := api.Services().Get(ctx.Context, kind, name)
					if err != nil {
						return err
					}

					table := newTable(ctx.App.Writer)

					table.Append([]string{"KIND", service.Kind})
					table.Append([]string{"NAME", service.Name})
					table.Append([]string{"ADDRESS", service.Address})
					table.Append([]string{"USER TEMPLATE", string(service.Templates.UserTemplate)})
					table.Append([]string{"PASSWORD TEMPLATE", string(service.Templates.PasswordTemplate)})

					table.Render()
					return nil
				},
			},
			{
				Name:  "grants",
				Usage: "Manage who has access to a given service.",
				Subcommands: []*cli.Command{
					{
						Name:      "list",
						Usage:     "List all users who have access to a service and their permissions.",
						ArgsUsage: " ",
						Action: func(ctx *cli.Context) error {
							args := ctx.Args()

							kind := args.Get(0)
							name := args.Get(1)

							if kind == "" || name == "" {
								return fmt.Errorf("expecting two arguments: <kind> <name>")
							}

							api := client.Extract(ctx.Context)

							grants, err := api.Services().Grants().List(ctx.Context, kind, name)
							if err != nil {
								return err
							}

							table := newTable(ctx.App.Writer)
							table.SetHeader([]string{"UserKind", "UserID", "UserName", "Roles"})

							for _, grant := range grants {
								table.Append([]string{
									grant.User.Kind, grant.User.Name, grant.User.ID,
									strings.Join(grant.Roles, ", "),
								})
							}

							table.Render()
							return nil
						},
					},
					{
						Name:      "update",
						Usage:     "Update a user's access to a service in varys.",
						ArgsUsage: "<kind> <name>",
						Flags:     flagset.ExtractPrefix("varys_update_service_grant", &updateGrantRequest),
						Action: func(ctx *cli.Context) error {
							args := ctx.Args()

							kind := args.Get(0)
							name := args.Get(1)

							if kind == "" || name == "" {
								return fmt.Errorf("expecting two arguments: <kind> <name>")
							}

							permissions := updateGrantRequest.Permission.Value()
							if len(permissions) == 0 {
								return fmt.Errorf("must provide at least one permission")
							}

							suffix := fmt.Sprintf("%s:%s", kind, name)

							roles := make([]string, 0)
							for _, permission := range permissions {
								roles = append(roles, permission+":"+suffix)
							}

							api := client.Extract(ctx.Context)

							return api.Services().Grants().Update(ctx.Context, kind, name, engine.UserGrant{
								User: engine.User{
									Kind: updateGrantRequest.User.Kind,
									ID:   updateGrantRequest.User.ID,
								},
								Roles: roles,
							})
						},
					},
					{
						Name:      "delete",
						Usage:     "Remove a user's access to a service in varys.",
						ArgsUsage: "<kind> <name>",
						Flags:     flagset.ExtractPrefix("varys_delete_service_grant", &deleteGrantRequest),
						Action: func(ctx *cli.Context) error {
							args := ctx.Args()

							kind := args.Get(0)
							name := args.Get(1)

							if kind == "" || name == "" {
								return fmt.Errorf("expecting two arguments: <kind> <name>")
							}

							permissions := updateGrantRequest.Permission.Value()
							if len(permissions) == 0 {
								return fmt.Errorf("must provide at least one permission")
							}

							suffix := fmt.Sprintf("%s:%s", kind, name)

							roles := make([]string, 0)
							for _, permission := range permissions {
								roles = append(roles, permission+":"+suffix)
							}

							api := client.Extract(ctx.Context)

							return api.Services().Grants().Delete(ctx.Context, kind, name, engine.UserGrant{
								User: engine.User{
									Kind: updateGrantRequest.User.Kind,
									ID:   updateGrantRequest.User.ID,
								},
								Roles: roles,
							})
						},
					},
				},
			},
			{
				Name:      "list",
				Usage:     "List all services managed by varys.",
				ArgsUsage: " ",
				Action: func(ctx *cli.Context) error {
					api := client.Extract(ctx.Context)

					services, err := api.Services().List(ctx.Context)
					if err != nil {
						return err
					}

					table := newTable(ctx.App.Writer)
					table.SetHeader([]string{"Kind", "Name", "Address"})

					for _, service := range services {
						table.Append([]string{service.Kind, service.Name, service.Address})
					}

					table.Render()
					return nil
				},
			},
			{
				Name:      "update",
				Usage:     "Update a service in varys.",
				ArgsUsage: "<kind> <name>",
				Flags:     flagset.ExtractPrefix("varys_update_service", &updateServiceRequest),
				Action: func(ctx *cli.Context) error {
					args := ctx.Args()

					kind := args.Get(0)
					name := args.Get(1)

					if kind == "" || name == "" {
						return fmt.Errorf("expecting two arguments: <kind> <name>")
					}

					api := client.Extract(ctx.Context)

					return api.Services().Update(ctx.Context, kind, name, updateServiceRequest)
				},
			},
		},
		HideHelpCommand: true,
	}
)
View Source
var (
	Users = &cli.Command{
		Name:  "users",
		Usage: "Perform operations against the Users API.",
		Flags: flagset.ExtractPrefix("varys", &client.DefaultConfig),
		Before: func(ctx *cli.Context) error {
			api, err := client.NewAPI(client.DefaultConfig)
			if err != nil {
				return err
			}

			ctx.Context = client.WithContext(ctx.Context, api)
			return nil
		},
		Subcommands: []*cli.Command{
			{
				Name:      "list",
				Usage:     "List all users known to varys.",
				ArgsUsage: " ",
				Action: func(ctx *cli.Context) error {
					api := client.Extract(ctx.Context)

					users, err := api.Users().List(ctx.Context)
					if err != nil {
						return err
					}

					table := newTable(ctx.App.Writer)
					table.SetHeader([]string{"Kind", "ID", "Name"})

					for _, user := range users {
						table.Append([]string{user.Kind, user.ID, user.Name})
					}

					table.Render()
					return nil
				},
			},
			{
				Name:  "current",
				Usage: "Interact with the currently authenticated user.",
				Subcommands: []*cli.Command{
					{
						Name:      "get",
						Usage:     "Output information about the current user.",
						ArgsUsage: " ",
						Action: func(ctx *cli.Context) error {
							api := client.Extract(ctx.Context)

							user, err := api.Users().Current().Get(ctx.Context)
							if err != nil {
								return err
							}

							table := newTable(ctx.App.Writer)

							table.Append([]string{"ID", user.Subject})
							table.Append([]string{"PROFILE", user.Profile})
							table.Append([]string{"EMAIL", user.Email})
							table.Append([]string{"EMAIL VERIFIED", strconv.FormatBool(user.EmailVerified)})
							table.Append([]string{"GROUPS", strings.Join(user.Groups, ", ")})

							table.Render()
							return nil
						},
					},
					{
						Name:      "update",
						Usage:     "Update information about the current user.",
						ArgsUsage: " ",
						Flags:     flagset.ExtractPrefix("varys_update_user", &updateUserRequest),
						Action: func(ctx *cli.Context) error {
							api := client.Extract(ctx.Context)

							return api.Users().Current().Update(ctx.Context, engine.UpdateUserRequest{
								RotateService: engine.Service{
									Kind: updateUserRequest.RotateServiceKind,
									Name: updateUserRequest.RotateServiceName,
								},
							})
						},
					},
				},
			},
		},
		HideHelpCommand: true,
	}
)
View Source
var Version = &cli.Command{
	Name:      "version",
	Usage:     "Print the binary version information.",
	UsageText: "varys version",
	Action: func(ctx *cli.Context) error {
		return template.
			Must(template.New("version").Parse(versionTemplate)).
			Execute(ctx.App.Writer, ctx.App)
	},
	HideHelpCommand: true,
}

Functions

This section is empty.

Types

type CredentialConfig

type CredentialConfig struct {
	RootKey string `json:"root_key" usage:"specify the root key used to derive credentials from"`
}

type DatabaseConfig

type DatabaseConfig struct {
	Path       string           `json:"path"       usage:"configure the path to the database" default:"db.badger"`
	Encryption EncryptionConfig `json:"encryption" `
}

type EncryptionConfig

type EncryptionConfig struct {
	Key         string        `json:"key"          usage:"specify the root encryption key used to encrypt the database"`
	KeyDuration time.Duration `json:"key_duration" usage:"how long a derived encryption key is good for" default:"120h" hidden:"true"`
}

type RunConfig

type RunConfig struct {
	BindAddress string           `json:"bind_address" usage:"specify the address to bind to" default:"localhost:3456"`
	TLS         livetls.Config   `json:"tls"`
	Database    DatabaseConfig   `json:"database"`
	Credential  CredentialConfig `json:"credential"`

	auth.Config
	Basic basicauth.Config `json:"basic"`
}

type UpdateUserRequest

type UpdateUserRequest struct {
	RotateServiceKind string `json:"rotate_service_kind" usage:"the kind of service that we're rotating the credential for"`
	RotateServiceName string `json:"rotate_service_name" usage:"the name of the service we're rotating the credential for"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL