Add read:self and cancel:self to sessions; add dev unpriv user (#923)
* Sessions support for read:self and cancel:self permissions. This is automatic with fallback; no API change is needed. * Strips out values from listing if only self actions are allowed on the resource and the user ID is different. This prevents a default ACL of id=*;type=session;actions=read:self,cancel:self from causing all values to be visible when listing. * Adds an unprivileged user to development/testing instances. This user has only default permissions plus the ability to authorize sessions in the default target.
This commit is contained in:
1 parent
d279a906ce
commit
22462cdd84
28 files changed
+626
-196
No files matched your search
@@ -48,7 +48,7 @@ func (b *Server) CreateInitialLoginRole(ctx context.Context) (*iam.Role, error)
|
||||
|
||||
pr, err := iam.NewRole(scope.Global.String(),
|
||||
iam.WithName("Login and Default Grants"),
|
||||
iam.WithDescription(`Role created for login capability and account self-management for users of the global scope at its creation time`),
|
||||
iam.WithDescription(`Role created for login capability, account self-management, and other default grants for users of the global scope at its creation time`),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
||||
@@ -125,78 +125,110 @@ func (b *Server) CreateInitialAuthMethod(ctx context.Context) (*password.AuthMet
|
||||
b.InfoKeys = append(b.InfoKeys, "generated auth method id")
|
||||
b.Info["generated auth method id"] = b.DevAuthMethodId
|
||||
|
||||
// Create the dev user
|
||||
if b.DevLoginName == "" {
|
||||
b.DevLoginName, err = base62.Random(10)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to generate login name: %w", err)
|
||||
createUser := func(loginName, loginPassword, userId string, admin bool) (*iam.User, error) {
|
||||
// Create the dev admin user
|
||||
if loginName == "" {
|
||||
b.DevLoginName, err = base62.Random(10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate login name: %w", err)
|
||||
}
|
||||
loginName = strings.ToLower(b.DevLoginName)
|
||||
}
|
||||
b.DevLoginName = strings.ToLower(b.DevLoginName)
|
||||
}
|
||||
if b.DevPassword == "" {
|
||||
b.DevPassword, err = base62.Random(20)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to generate password: %w", err)
|
||||
if b.DevPassword == "" {
|
||||
b.DevPassword, err = base62.Random(20)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate password: %w", err)
|
||||
}
|
||||
loginPassword = b.DevPassword
|
||||
}
|
||||
}
|
||||
b.InfoKeys = append(b.InfoKeys, "generated auth method password")
|
||||
b.Info["generated auth method password"] = b.DevPassword
|
||||
typeStr := "admin"
|
||||
if !admin {
|
||||
typeStr = "unprivileged"
|
||||
}
|
||||
b.InfoKeys = append(b.InfoKeys, fmt.Sprintf("generated %s password", typeStr))
|
||||
b.Info[fmt.Sprintf("generated %s password", typeStr)] = loginPassword
|
||||
|
||||
acct, err := password.NewAccount(b.DevAuthMethodId, password.WithLoginName(b.DevLoginName))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating new in memory auth account: %w", err)
|
||||
}
|
||||
acct, err = pwRepo.CreateAccount(cancelCtx, scope.Global.String(), acct, password.WithPassword(b.DevPassword))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error saving auth account to the db: %w", err)
|
||||
}
|
||||
b.InfoKeys = append(b.InfoKeys, "generated auth method login name")
|
||||
b.Info["generated auth method login name"] = acct.GetLoginName()
|
||||
acct, err := password.NewAccount(am.PublicId, password.WithLoginName(loginName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating new in memory auth account: %w", err)
|
||||
}
|
||||
acct, err = pwRepo.CreateAccount(cancelCtx, scope.Global.String(), acct, password.WithPassword(loginPassword))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error saving auth account to the db: %w", err)
|
||||
}
|
||||
b.InfoKeys = append(b.InfoKeys, fmt.Sprintf("generated %s login name", typeStr))
|
||||
b.Info[fmt.Sprintf("generated %s login name", typeStr)] = acct.GetLoginName()
|
||||
|
||||
iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create repo for org id: %w", err)
|
||||
iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create repo for org id: %w", err)
|
||||
}
|
||||
|
||||
// Create a new user and associate it with the account
|
||||
if userId == "" {
|
||||
userId, err = db.NewPublicId(iam.UserPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating initial user id: %w", err)
|
||||
}
|
||||
}
|
||||
opts := []iam.Option{
|
||||
iam.WithPublicId(userId),
|
||||
}
|
||||
if admin {
|
||||
opts = append(opts,
|
||||
iam.WithName("admin"),
|
||||
iam.WithDescription(`Initial admin user within the "global" scope`),
|
||||
)
|
||||
} else {
|
||||
opts = append(opts,
|
||||
iam.WithName("user"),
|
||||
iam.WithDescription("Initial unprivileged user"),
|
||||
)
|
||||
}
|
||||
u, err := iam.NewUser(scope.Global.String(), opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating in memory user: %w", err)
|
||||
}
|
||||
if u, err = iamRepo.CreateUser(cancelCtx, u, opts...); err != nil {
|
||||
return nil, fmt.Errorf("error creating initial %s user: %w", typeStr, err)
|
||||
}
|
||||
if _, err = iamRepo.AddUserAccounts(cancelCtx, u.GetPublicId(), u.GetVersion(), []string{acct.GetPublicId()}); err != nil {
|
||||
return nil, fmt.Errorf("error associating initial %s user with account: %w", typeStr, err)
|
||||
}
|
||||
if !admin {
|
||||
return u, nil
|
||||
}
|
||||
// Create a role tying them together
|
||||
pr, err := iam.NewRole(scope.Global.String(),
|
||||
iam.WithName("Administration"),
|
||||
iam.WithDescription(`Provides admin grants within the "global" scope to the initial user`),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
||||
}
|
||||
defPermsRole, err := iamRepo.CreateRole(cancelCtx, pr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating role for default generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddRoleGrants(cancelCtx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;type=*;actions=*"}); err != nil {
|
||||
return nil, fmt.Errorf("error creating grant for default generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddPrincipalRoles(cancelCtx, defPermsRole.PublicId, defPermsRole.Version+1, []string{u.GetPublicId()}, nil); err != nil {
|
||||
return nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Create a new user and associate it with the account
|
||||
if b.DevUserId == "" {
|
||||
b.DevUserId, err = db.NewPublicId(iam.UserPrefix)
|
||||
if b.DevUnprivilegedLoginName != "" {
|
||||
unprivUser, err := createUser(b.DevUnprivilegedLoginName, b.DevUnprivilegedPassword, b.DevUnprivilegedUserId, false)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error generating initial user id: %w", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
b.DevUnprivilegedUserId = unprivUser.GetPublicId()
|
||||
}
|
||||
opts := []iam.Option{
|
||||
iam.WithName("admin"),
|
||||
iam.WithDescription(`Initial admin user within the "global" scope`),
|
||||
iam.WithPublicId(b.DevUserId),
|
||||
}
|
||||
u, err := iam.NewUser(scope.Global.String(), opts...)
|
||||
u, err := createUser(b.DevLoginName, b.DevPassword, b.DevUserId, true)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating in memory user: %w", err)
|
||||
}
|
||||
if u, err = iamRepo.CreateUser(cancelCtx, u, opts...); err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating initial admin user: %w", err)
|
||||
}
|
||||
if _, err = iamRepo.AddUserAccounts(cancelCtx, u.GetPublicId(), u.GetVersion(), []string{acct.GetPublicId()}); err != nil {
|
||||
return nil, nil, fmt.Errorf("error associating initial admin user with account: %w", err)
|
||||
}
|
||||
// Create a role tying them together
|
||||
pr, err := iam.NewRole(scope.Global.String(),
|
||||
iam.WithName("Administration"),
|
||||
iam.WithDescription(`Provides admin grants within the "global" scope to the initial user`),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
||||
}
|
||||
defPermsRole, err := iamRepo.CreateRole(cancelCtx, pr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating role for default generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddRoleGrants(cancelCtx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;type=*;actions=*"}); err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating grant for default generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddPrincipalRoles(cancelCtx, defPermsRole.PublicId, defPermsRole.Version+1, []string{u.GetPublicId()}, nil); err != nil {
|
||||
return nil, nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return am, u, nil
|
||||
@@ -456,5 +488,64 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error)
|
||||
b.InfoKeys = append(b.InfoKeys, "generated target id")
|
||||
b.Info["generated target id"] = b.DevTargetId
|
||||
|
||||
// If we have an unprivileged dev user, add user to the role that grants
|
||||
// list/read:self/cancel:self, and an authorize-session role
|
||||
if b.DevUnprivilegedUserId != "" {
|
||||
iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create repo for unprivileged user target connection role: %w", err)
|
||||
}
|
||||
|
||||
roles, err := iamRepo.ListRoles(ctx, []string{b.DevProjectId})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list existing roles in project: %w", err)
|
||||
}
|
||||
if len(roles) != 2 {
|
||||
panic("")
|
||||
return nil, fmt.Errorf("unexpected number of roles in default project, expected 2, got %d", len(roles))
|
||||
}
|
||||
var idx int = -1
|
||||
for i, r := range roles {
|
||||
// Hacky, I know, but saves a DB trip to look up other
|
||||
// characteristics like "if any principals are currently attached".
|
||||
// No matter what we pick here it's a bit heuristical.
|
||||
if r.Name == "Default Grants" {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return nil, fmt.Errorf("couldn't find default grants role in default project")
|
||||
}
|
||||
if _, err := iamRepo.AddPrincipalRoles(ctx,
|
||||
roles[idx].PublicId,
|
||||
roles[idx].Version,
|
||||
[]string{b.DevUnprivilegedUserId}); err != nil {
|
||||
return nil, fmt.Errorf("error adding unpriv user ID to project default role: %w", err)
|
||||
}
|
||||
|
||||
pr, err := iam.NewRole(b.DevProjectId,
|
||||
iam.WithName("Unprivileged User Session Authorization"),
|
||||
iam.WithDescription(`Provides grants within the dev project scope to allow the initial unprivileged user to authorize sessions against the dev target`),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
||||
}
|
||||
sessionRole, err := iamRepo.CreateRole(cancelCtx, pr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating role for unprivileged user generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddRoleGrants(cancelCtx,
|
||||
sessionRole.PublicId,
|
||||
sessionRole.Version,
|
||||
[]string{fmt.Sprintf("id=%s;actions=authorize-session", b.DevTargetId)},
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("error creating grant for unprivileged user generated grants: %w", err)
|
||||
}
|
||||
if _, err := iamRepo.AddPrincipalRoles(cancelCtx, sessionRole.PublicId, sessionRole.Version+1, []string{b.DevUnprivilegedUserId}, nil); err != nil {
|
||||
return nil, fmt.Errorf("error adding principal to role for unprivileged user generated grants: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return tt, nil
|
||||
}
|
||||
@@ -71,6 +71,9 @@ type Server struct {
|
||||
DevLoginName string
|
||||
DevPassword string
|
||||
DevUserId string
|
||||
DevUnprivilegedLoginName string
|
||||
DevUnprivilegedPassword string
|
||||
DevUnprivilegedUserId string
|
||||
DevOrgId string
|
||||
DevProjectId string
|
||||
DevHostCatalogId string
|
||||
|
||||
@@ -218,6 +218,17 @@ func (c *Command) Run(args []string) int {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
fmt.Print("Please enter it one more time for confirmation: ")
|
||||
confirmation, err := password.Read(os.Stdin)
|
||||
fmt.Print("\n")
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
if strings.TrimSpace(value) != strings.TrimSpace(confirmation) {
|
||||
c.UI.Error("Entered password and confirmation value did not match.")
|
||||
return 2
|
||||
}
|
||||
c.flagPassword = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
@@ -240,6 +251,17 @@ func (c *Command) Run(args []string) int {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
fmt.Print("Please enter it one more time for confirmation: ")
|
||||
confirmation, err := password.Read(os.Stdin)
|
||||
fmt.Print("\n")
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
if strings.TrimSpace(value) != strings.TrimSpace(confirmation) {
|
||||
c.UI.Error("Entered password and confirmation value did not match.")
|
||||
return 2
|
||||
}
|
||||
c.flagNewPassword = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,17 @@ func (c *PasswordCommand) Run(args []string) int {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
fmt.Print("Please enter it one more time for confirmation: ")
|
||||
confirmation, err := password.Read(os.Stdin)
|
||||
fmt.Print("\n")
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
||||
return 2
|
||||
}
|
||||
if strings.TrimSpace(value) != strings.TrimSpace(confirmation) {
|
||||
c.UI.Error("Entered password and confirmation value did not match.")
|
||||
return 2
|
||||
}
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(strings.TrimSpace(value)))
|
||||
default:
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(c.flagPassword))
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/servers/worker"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
@@ -43,6 +44,8 @@ type Command struct {
|
||||
flagCombineLogs bool
|
||||
flagLoginName string
|
||||
flagPassword string
|
||||
flagUnprivilegedLoginName string
|
||||
flagUnprivilegedPassword string
|
||||
flagIdSuffix string
|
||||
flagHostAddress string
|
||||
flagTargetDefaultPort int
|
||||
@@ -111,7 +114,7 @@ func (c *Command) Flags() *base.FlagSets {
|
||||
Target: &c.flagPassword,
|
||||
Default: "password",
|
||||
EnvVar: "BOUNDARY_DEV_PASSWORD",
|
||||
Usage: "Initial login password. If set to the empty string, one will be autogenerated.",
|
||||
Usage: "Initial admin login password. If set to the empty string, one will be autogenerated.",
|
||||
})
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
@@ -122,6 +125,22 @@ func (c *Command) Flags() *base.FlagSets {
|
||||
Usage: "Initial admin login name. If set to the empty string, one will be autogenerated.",
|
||||
})
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "unprivileged-password",
|
||||
Target: &c.flagUnprivilegedPassword,
|
||||
Default: "password",
|
||||
EnvVar: "BOUNDARY_DEV_UNPRIVILEGED_PASSWORD",
|
||||
Usage: "Initial unprivileged user login password. If set to the empty string, one will be autogenerated.",
|
||||
})
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "unprivileged-login-name",
|
||||
Target: &c.flagUnprivilegedLoginName,
|
||||
Default: "user",
|
||||
EnvVar: "BOUNDARY_DEV_UNPRIVILEGED_LOGIN_NAME",
|
||||
Usage: "Initial unprivileged user name. If set to the empty string, one will be autogenerated.",
|
||||
})
|
||||
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: "api-listen-address",
|
||||
Target: &c.flagControllerAPIListenAddr,
|
||||
@@ -261,6 +280,7 @@ func (c *Command) Run(args []string) int {
|
||||
}
|
||||
c.DevAuthMethodId = fmt.Sprintf("%s_%s", password.AuthMethodPrefix, c.flagIdSuffix)
|
||||
c.DevUserId = fmt.Sprintf("%s_%s", iam.UserPrefix, c.flagIdSuffix)
|
||||
c.DevUnprivilegedUserId = "u_" + strutil.Reverse(strings.TrimPrefix(c.DevUserId, "u_"))
|
||||
c.DevOrgId = fmt.Sprintf("%s_%s", scope.Org.Prefix(), c.flagIdSuffix)
|
||||
c.DevProjectId = fmt.Sprintf("%s_%s", scope.Project.Prefix(), c.flagIdSuffix)
|
||||
c.DevHostCatalogId = fmt.Sprintf("%s_%s", static.HostCatalogPrefix, c.flagIdSuffix)
|
||||
@@ -274,6 +294,12 @@ func (c *Command) Run(args []string) int {
|
||||
if c.flagPassword != "" {
|
||||
c.DevPassword = c.flagPassword
|
||||
}
|
||||
if c.flagUnprivilegedLoginName != "" {
|
||||
c.DevUnprivilegedLoginName = c.flagUnprivilegedLoginName
|
||||
}
|
||||
if c.flagUnprivilegedPassword != "" {
|
||||
c.DevUnprivilegedPassword = c.flagUnprivilegedPassword
|
||||
}
|
||||
c.DevTargetDefaultPort = c.flagTargetDefaultPort
|
||||
host, port, err := net.SplitHostPort(c.flagHostAddress)
|
||||
if err != nil {
|
||||
|
||||
@@ -126,7 +126,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
var defaultRoleMetadata oplog.Metadata
|
||||
var defaultRole *Role
|
||||
var defaultRoleRaw interface{}
|
||||
if !opts.withSkipDefaultRoleCreation && s.Type == scope.Org.String() {
|
||||
if !opts.withSkipDefaultRoleCreation {
|
||||
defaultRole, err = NewRole(scopePublicId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("error instantiating new default role"))
|
||||
@@ -136,8 +136,14 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("error generating public id for new default role"))
|
||||
}
|
||||
defaultRole.PublicId = defaultRolePublicId
|
||||
defaultRole.Name = "Login and Default Grants"
|
||||
defaultRole.Description = fmt.Sprintf("Role created for login capability and account self-management for users of scope %s at its creation time", scopePublicId)
|
||||
switch s.Type {
|
||||
case scope.Project.String():
|
||||
defaultRole.Name = "Default Grants"
|
||||
defaultRole.Description = fmt.Sprintf("Role created to provide default grants to users of scope %s at its creation time", scopePublicId)
|
||||
default:
|
||||
defaultRole.Name = "Login and Default Grants"
|
||||
defaultRole.Description = fmt.Sprintf("Role created for login capability, account self-management, and other default grants for users of scope %s at its creation time", scopePublicId)
|
||||
}
|
||||
defaultRoleRaw = defaultRole
|
||||
defaultRoleMetadata = oplog.Metadata{
|
||||
"resource-public-id": []string{defaultRolePublicId},
|
||||
@@ -280,23 +286,34 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
// Grants
|
||||
{
|
||||
grants := []interface{}{}
|
||||
roleGrant, err := NewRoleGrant(defaultRolePublicId, "id=*;type=scope;actions=list,read")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
|
||||
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id=*;type=auth-method;actions=authenticate,list")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
switch s.Type {
|
||||
case scope.Project.String():
|
||||
roleGrant, err := NewRoleGrant(defaultRolePublicId, "id=*;type=session;actions=list,read:self,cancel:self")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
|
||||
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id={{account.id}};actions=read,change-password")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
default:
|
||||
roleGrant, err := NewRoleGrant(defaultRolePublicId, "id=*;type=scope;actions=list,read")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
|
||||
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id=*;type=auth-method;actions=authenticate,list")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
|
||||
roleGrant, err = NewRoleGrant(defaultRolePublicId, "id={{account.id}};actions=read,change-password")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role grant"))
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
}
|
||||
grants = append(grants, roleGrant)
|
||||
|
||||
roleGrantOplogMsgs := make([]*oplog.Message, 0, 3)
|
||||
if err := w.CreateItems(ctx, grants, db.NewOplogMsgs(&roleGrantOplogMsgs)); err != nil {
|
||||
@@ -308,7 +325,11 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o
|
||||
// Principals
|
||||
{
|
||||
principals := []interface{}{}
|
||||
rolePrincipal, err := NewUserRole(defaultRolePublicId, "u_anon")
|
||||
userId := "u_anon"
|
||||
if s.Type == scope.Project.String() {
|
||||
userId = "u_auth"
|
||||
}
|
||||
rolePrincipal, err := NewUserRole(defaultRolePublicId, userId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to create in memory role user"))
|
||||
}
|
||||
|
||||
@@ -717,7 +717,7 @@ func TestCreate(t *testing.T) {
|
||||
require.NoError(err)
|
||||
switch tc.scopeId {
|
||||
case defaultOrg.PublicId:
|
||||
require.Len(roles, 1)
|
||||
require.Len(roles, 2)
|
||||
case "global":
|
||||
require.Len(roles, 2)
|
||||
}
|
||||
@@ -726,7 +726,9 @@ func TestCreate(t *testing.T) {
|
||||
case "Administration":
|
||||
assert.Equal(fmt.Sprintf("Role created for administration of scope %s by user %s at its creation time", got.GetItem().GetId(), userId), role.GetDescription())
|
||||
case "Login and Default Grants":
|
||||
assert.Equal(fmt.Sprintf("Role created for login capability and account self-management for users of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription())
|
||||
assert.Equal(fmt.Sprintf("Role created for login capability, account self-management, and other default grants for users of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription())
|
||||
case "Default Grants":
|
||||
assert.Equal(fmt.Sprintf("Role created to provide default grants to users of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription())
|
||||
default:
|
||||
t.Fatal("unexpected role name", role.GetName())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ var (
|
||||
// individual resources
|
||||
IdActions = action.ActionSet{
|
||||
action.Read,
|
||||
action.ReadSelf,
|
||||
action.Cancel,
|
||||
action.CancelSelf,
|
||||
}
|
||||
|
||||
// CollectionActions contains the set of actions that can be performed on
|
||||
@@ -61,7 +63,7 @@ func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*p
|
||||
if err := validateGetRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetId(), action.Read)
|
||||
authResults := s.authResult(ctx, req.GetId(), action.ReadSelf)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
@@ -69,8 +71,24 @@ func (s Service) GetSession(ctx context.Context, req *pbs.GetSessionRequest) (*p
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
|
||||
// Check to see if we need to verify Read vs. just ReadSelf
|
||||
if ses.GetUserId() != authResults.UserId {
|
||||
var found bool
|
||||
for _, v := range authzdActions {
|
||||
if v == action.Read {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
}
|
||||
|
||||
ses.Scope = authResults.Scope
|
||||
ses.AuthorizedActions = authResults.FetchActionSetForId(ctx, ses.Id, IdActions).Strings()
|
||||
ses.AuthorizedActions = authzdActions.Strings()
|
||||
return &pbs.GetSessionResponse{Item: ses}, nil
|
||||
}
|
||||
|
||||
@@ -101,9 +119,19 @@ func (s Service) ListSessions(ctx context.Context, req *pbs.ListSessionsRequest)
|
||||
for _, item := range seslist {
|
||||
item.Scope = scopeInfoMap[item.GetScopeId()]
|
||||
res.ScopeId = item.Scope.Id
|
||||
item.AuthorizedActions = authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res)).Strings()
|
||||
if len(item.AuthorizedActions) > 0 {
|
||||
finalItems = append(finalItems, item)
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.Id, IdActions, auth.WithResource(res))
|
||||
if len(authorizedActions) > 0 {
|
||||
onlySelf := true
|
||||
for _, v := range authorizedActions {
|
||||
if v != action.ReadSelf && v != action.CancelSelf {
|
||||
onlySelf = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !onlySelf || item.GetUserId() == authResults.UserId {
|
||||
item.AuthorizedActions = authorizedActions.Strings()
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,16 +143,36 @@ func (s Service) CancelSession(ctx context.Context, req *pbs.CancelSessionReques
|
||||
if err := validateCancelRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authResults := s.authResult(ctx, req.GetId(), action.Cancel)
|
||||
authResults := s.authResult(ctx, req.GetId(), action.CancelSelf)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
ses, err := s.cancelInRepo(ctx, req.GetId(), req.GetVersion())
|
||||
|
||||
ses, err := s.getFromRepo(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authzdActions := authResults.FetchActionSetForId(ctx, ses.Id, IdActions)
|
||||
// Check to see if we need to verify Read vs. just ReadSelf
|
||||
if ses.GetUserId() != authResults.UserId {
|
||||
var found bool
|
||||
for _, v := range authzdActions {
|
||||
if v == action.Cancel {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, handlers.ForbiddenError()
|
||||
}
|
||||
}
|
||||
|
||||
ses, err = s.cancelInRepo(ctx, req.GetId(), req.GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ses.Scope = authResults.Scope
|
||||
ses.AuthorizedActions = authResults.FetchActionSetForId(ctx, ses.Id, IdActions).Strings()
|
||||
ses.AuthorizedActions = authzdActions.Strings()
|
||||
return &pbs.CancelSessionResponse{Item: ses}, nil
|
||||
}
|
||||
|
||||
@@ -196,7 +244,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.
|
||||
res.Error = handlers.NotFoundError()
|
||||
return res
|
||||
}
|
||||
case action.Read, action.Cancel:
|
||||
case action.Read, action.ReadSelf, action.Cancel, action.CancelSelf:
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
res.Error = err
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestGetSession(t *testing.T) {
|
||||
States: []*pb.SessionState{{Status: session.StatusPending.String(), StartTime: sess.CreateTime.GetTimestamp()}},
|
||||
Certificate: sess.Certificate,
|
||||
Type: target.TcpSubType.String(),
|
||||
AuthorizedActions: []string{"read", "cancel"},
|
||||
AuthorizedActions: []string{"read", "read:self", "cancel", "cancel:self"},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -210,7 +210,7 @@ func TestList(t *testing.T) {
|
||||
States: states,
|
||||
Certificate: sess.Certificate,
|
||||
Type: target.TcpSubType.String(),
|
||||
AuthorizedActions: []string{"read", "cancel"},
|
||||
AuthorizedActions: []string{"read", "read:self", "cancel", "cancel:self"},
|
||||
})
|
||||
|
||||
totalSession = append(totalSession, wantSession[i])
|
||||
@@ -245,7 +245,7 @@ func TestList(t *testing.T) {
|
||||
States: states,
|
||||
Certificate: sess.Certificate,
|
||||
Type: target.TcpSubType.String(),
|
||||
AuthorizedActions: []string{"read", "cancel"},
|
||||
AuthorizedActions: []string{"read", "read:self", "cancel", "cancel:self"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ func TestCancel(t *testing.T) {
|
||||
Status: session.StatusCanceling.String(),
|
||||
Certificate: sess.Certificate,
|
||||
Type: target.TcpSubType.String(),
|
||||
AuthorizedActions: []string{"read", "cancel"},
|
||||
AuthorizedActions: []string{"read", "read:self", "cancel", "cancel:self"},
|
||||
}
|
||||
|
||||
version := wireSess.GetVersion()
|
||||
|
||||
@@ -15,18 +15,12 @@ import (
|
||||
|
||||
func TestAuthenticationMulti(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
user := "user"
|
||||
password := "passpass"
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: user,
|
||||
DefaultPassword: password,
|
||||
Logger: logger.Named("c1"),
|
||||
Logger: logger.Named("c1"),
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
@@ -36,14 +30,14 @@ func TestAuthenticationMulti(t *testing.T) {
|
||||
defer c2.Shutdown()
|
||||
|
||||
auth := authmethods.NewClient(c1.Client())
|
||||
token1Result, err := auth.Authenticate(c1.Context(), amId, map[string]interface{}{"login_name": user, "password": password})
|
||||
token1Result, err := auth.Authenticate(c1.Context(), c1.Server().DevAuthMethodId, map[string]interface{}{"login_name": c1.Server().DevLoginName, "password": c1.Server().DevPassword})
|
||||
require.Nil(err)
|
||||
token1 := token1Result.Item
|
||||
require.NotNil(token1)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
auth = authmethods.NewClient(c2.Client())
|
||||
token2Result, err := auth.Authenticate(c2.Context(), amId, map[string]interface{}{"login_name": user, "password": password})
|
||||
token2Result, err := auth.Authenticate(c2.Context(), c2.Server().DevAuthMethodId, map[string]interface{}{"login_name": c2.Server().DevLoginName, "password": c2.Server().DevPassword})
|
||||
require.Nil(err)
|
||||
token2 := token2Result.Item
|
||||
require.NotNil(token2)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/sdk/strutil"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||
@@ -25,9 +26,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTestAuthMethodId = "ampw_1234567890"
|
||||
DefaultTestLoginName = "user"
|
||||
DefaultTestPassword = "passpass"
|
||||
DefaultTestAuthMethodId = "ampw_1234567890"
|
||||
DefaultTestLoginName = "admin"
|
||||
DefaultTestUnprivilegedLoginName = "user"
|
||||
DefaultTestPassword = "passpass"
|
||||
DefaultTestUserId = "u_1234567890"
|
||||
)
|
||||
|
||||
// TestController wraps a base.Server and Controller to provide a
|
||||
@@ -46,6 +49,11 @@ type TestController struct {
|
||||
opts *TestControllerOpts
|
||||
}
|
||||
|
||||
// Server returns the underlying base server
|
||||
func (tc *TestController) Server() *base.Server {
|
||||
return tc.b
|
||||
}
|
||||
|
||||
// Controller returns the underlying controller
|
||||
func (tc *TestController) Controller() *Controller {
|
||||
return tc.c
|
||||
@@ -135,6 +143,26 @@ func (tc *TestController) Token() *authtokens.AuthToken {
|
||||
return token.Item
|
||||
}
|
||||
|
||||
func (tc *TestController) UnprivilegedToken() *authtokens.AuthToken {
|
||||
if tc.opts.DisableAuthMethodCreation {
|
||||
tc.t.Error("no default auth method ID configured")
|
||||
return nil
|
||||
}
|
||||
token, err := authmethods.NewClient(tc.Client()).Authenticate(
|
||||
tc.Context(),
|
||||
tc.b.DevAuthMethodId,
|
||||
map[string]interface{}{
|
||||
"login_name": tc.b.DevUnprivilegedLoginName,
|
||||
"password": tc.b.DevUnprivilegedPassword,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
tc.t.Error(fmt.Errorf("error logging in: %w", err))
|
||||
return nil
|
||||
}
|
||||
return token.Item
|
||||
}
|
||||
|
||||
func (tc *TestController) addrs(purpose string) []string {
|
||||
var prefix string
|
||||
switch purpose {
|
||||
@@ -242,10 +270,13 @@ type TestControllerOpts struct {
|
||||
// DefaultAuthMethodId is the default auth method ID to use, if set.
|
||||
DefaultAuthMethodId string
|
||||
|
||||
// DefaultLoginName is the login name used when creating the default account.
|
||||
// DefaultLoginName is the login name used when creating the default admin account.
|
||||
DefaultLoginName string
|
||||
|
||||
// DefaultPassword is the password used when creating the default account.
|
||||
// DefaultUnprivilegedLoginName is the login name used when creating the default unprivileged account.
|
||||
DefaultUnprivilegedLoginName string
|
||||
|
||||
// DefaultPassword is the password used when creating the default accounts.
|
||||
DefaultPassword string
|
||||
|
||||
// DisableInitialLoginRoleCreation can be set true to disable creating the
|
||||
@@ -360,10 +391,17 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
} else {
|
||||
tc.b.DevLoginName = DefaultTestLoginName
|
||||
}
|
||||
if opts.DefaultUnprivilegedLoginName != "" {
|
||||
tc.b.DevUnprivilegedLoginName = opts.DefaultUnprivilegedLoginName
|
||||
} else {
|
||||
tc.b.DevUnprivilegedLoginName = DefaultTestUnprivilegedLoginName
|
||||
}
|
||||
if opts.DefaultPassword != "" {
|
||||
tc.b.DevPassword = opts.DefaultPassword
|
||||
tc.b.DevUnprivilegedPassword = opts.DefaultPassword
|
||||
} else {
|
||||
tc.b.DevPassword = DefaultTestPassword
|
||||
tc.b.DevUnprivilegedPassword = DefaultTestPassword
|
||||
}
|
||||
|
||||
// Start a logger
|
||||
@@ -396,7 +434,11 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
tc.b.DevProjectId = "p_" + suffix
|
||||
tc.b.DevTargetId = "ttcp_" + suffix
|
||||
tc.b.DevUserId = "u_" + suffix
|
||||
tc.b.DevUnprivilegedUserId = "u_" + strutil.Reverse(strings.TrimPrefix(tc.b.DevUserId, "u_"))
|
||||
} else {
|
||||
tc.b.DevUserId = DefaultTestUserId
|
||||
}
|
||||
tc.b.DevUnprivilegedUserId = "u_" + strutil.Reverse(strings.TrimPrefix(tc.b.DevUserId, "u_"))
|
||||
|
||||
// Set up KMSes
|
||||
switch {
|
||||
@@ -509,6 +551,8 @@ func (tc *TestController) AddClusterControllerMember(t *testing.T, opts *TestCon
|
||||
RecoveryKms: tc.c.conf.RecoveryKms,
|
||||
Name: opts.Name,
|
||||
Logger: tc.c.conf.Logger,
|
||||
DefaultLoginName: tc.b.DevLoginName,
|
||||
DefaultPassword: tc.b.DevPassword,
|
||||
DisableKmsKeyCreation: true,
|
||||
DisableAuthMethodCreation: true,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package accounts_test
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
@@ -17,8 +16,6 @@ import (
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
os.Setenv("BOUNDARY_LOG_URLS", "1")
|
||||
os.Setenv("BOUNDARY_DEV_SKIP_AUTHZ", "1")
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
@@ -126,23 +123,37 @@ func TestCustomMethods(t *testing.T) {
|
||||
client.SetToken(token.Token)
|
||||
amId := token.AuthMethodId
|
||||
|
||||
accountClient := accounts.NewClient(client)
|
||||
adminAccountClient := accounts.NewClient(client)
|
||||
client = client.Clone()
|
||||
client.SetToken(tc.UnprivilegedToken().Token)
|
||||
userAccountClient := accounts.NewClient(client)
|
||||
|
||||
al, err := accountClient.List(tc.Context(), amId)
|
||||
al, err := adminAccountClient.List(tc.Context(), amId)
|
||||
require.NoError(err)
|
||||
require.Len(al.Items, 1)
|
||||
require.Len(al.Items, 2)
|
||||
|
||||
acct := al.Items[0]
|
||||
userAcct := al.Items[0]
|
||||
if userAcct.Name == "admin" {
|
||||
userAcct = al.Items[1]
|
||||
}
|
||||
|
||||
setAcct, err := accountClient.SetPassword(tc.Context(), acct.Id, "setpassword", acct.Version)
|
||||
_, err = userAccountClient.SetPassword(tc.Context(), userAcct.Id, "setpassword", userAcct.Version)
|
||||
require.Error(err)
|
||||
|
||||
setAcct, err := adminAccountClient.SetPassword(tc.Context(), userAcct.Id, "setpassword", userAcct.Version)
|
||||
require.NoError(err)
|
||||
require.NotNil(setAcct)
|
||||
assert.Equal(acct.Version+1, setAcct.Item.Version)
|
||||
assert.Equal(userAcct.Version+1, setAcct.Item.Version)
|
||||
|
||||
changeAcct, err := accountClient.ChangePassword(tc.Context(), acct.Id, "setpassword", "changepassword", setAcct.Item.Version)
|
||||
changeAcct, err := adminAccountClient.ChangePassword(tc.Context(), userAcct.Id, "setpassword", "changepassword", setAcct.Item.Version)
|
||||
require.NoError(err)
|
||||
require.NotNil(changeAcct)
|
||||
assert.Equal(setAcct.Item.Version+1, changeAcct.Item.Version)
|
||||
assert.Equal(userAcct.Version+2, changeAcct.Item.Version)
|
||||
|
||||
changeAcct, err = userAccountClient.ChangePassword(tc.Context(), userAcct.Id, "changepassword", "password2", changeAcct.Item.Version)
|
||||
require.NoError(err)
|
||||
require.NotNil(changeAcct)
|
||||
assert.Equal(userAcct.Version+3, changeAcct.Item.Version)
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
|
||||
@@ -13,22 +13,17 @@ import (
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: "user",
|
||||
DefaultPassword: "passpass",
|
||||
})
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
methods := authmethods.NewClient(client)
|
||||
|
||||
tok, err := methods.Authenticate(tc.Context(), amId, map[string]interface{}{"login_name": "user", "password": "passpass"})
|
||||
tok, err := methods.Authenticate(tc.Context(), tc.Server().DevAuthMethodId, map[string]interface{}{"login_name": "user", "password": "passpass"})
|
||||
require.NoError(err)
|
||||
assert.NotNil(tok)
|
||||
|
||||
_, err = methods.Authenticate(tc.Context(), amId, map[string]interface{}{"login_name": "user", "password": "wrong"})
|
||||
_, err = methods.Authenticate(tc.Context(), tc.Server().DevAuthMethodId, map[string]interface{}{"login_name": "user", "password": "wrong"})
|
||||
require.Error(err)
|
||||
apiErr := api.AsServerError(err)
|
||||
require.NotNil(apiErr)
|
||||
|
||||
@@ -19,12 +19,7 @@ import (
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: "user",
|
||||
DefaultPassword: "passpass",
|
||||
})
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
token := tc.Token()
|
||||
@@ -36,7 +31,7 @@ func TestList(t *testing.T) {
|
||||
amResult, err := amClient.Create(tc.Context(), "password", org.GetPublicId())
|
||||
require.NoError(err)
|
||||
require.NotNil(amResult)
|
||||
amId = amResult.Item.Id
|
||||
amId := amResult.Item.Id
|
||||
|
||||
rolesClient := roles.NewClient(client)
|
||||
role, err := rolesClient.Create(tc.Context(), org.GetPublicId())
|
||||
@@ -106,12 +101,7 @@ func comparableSlice(in []*authtokens.AuthToken) []authtokens.AuthToken {
|
||||
|
||||
func TestCrud(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: "user",
|
||||
DefaultPassword: "passpass",
|
||||
})
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
@@ -120,7 +110,7 @@ func TestCrud(t *testing.T) {
|
||||
tokens := authtokens.NewClient(client)
|
||||
methods := authmethods.NewClient(client)
|
||||
|
||||
want, err := methods.Authenticate(tc.Context(), amId, map[string]interface{}{"login_name": "user", "password": "passpass"})
|
||||
want, err := methods.Authenticate(tc.Context(), tc.Server().DevAuthMethodId, map[string]interface{}{"login_name": "user", "password": "passpass"})
|
||||
require.NoError(err)
|
||||
|
||||
at, err := tokens.Read(tc.Context(), want.Item.Id)
|
||||
|
||||
@@ -107,7 +107,6 @@ func TestCustom(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.EqualValues(updatedRole.Item.Version, version)
|
||||
assert.Empty(updatedRole.Item.Grants)
|
||||
version++
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -148,7 +147,7 @@ func TestList(t *testing.T) {
|
||||
case "org":
|
||||
numBuiltIn = 2
|
||||
case "proj":
|
||||
numBuiltIn = 1
|
||||
numBuiltIn = 2
|
||||
}
|
||||
require.Len(p1.Items, numBuiltIn)
|
||||
expected = append(expected, p1.Items[0:numBuiltIn]...)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export BOUNDARY_ADDR="${BOUNDARY_ADDR:-http://127.0.0.1:9200}"
|
||||
export DEFAULT_PASSWORD="${DEFAULT_PASSWORD:-password}"
|
||||
export DEFAULT_LOGIN="${DEFAULT_LOGIN:-admin}"
|
||||
export DEFAULT_UNPRIVILEGED_LOGIN="${DEFAULT_UNPRIVILEGED_LOGIN:-user}"
|
||||
export DEFAULT_AMPW="${DEFAULT_AMPW:-ampw_1234567890}"
|
||||
export DEFAULT_P_ID='p_1234567890'
|
||||
export DEFAULT_O_ID='o_1234567890'
|
||||
@@ -10,6 +11,7 @@ export DEFAULT_HOST_SET='hsst_1234567890'
|
||||
export DEFAULT_HOST_CATALOG='hcst_1234567890'
|
||||
export DEFAULT_HOST='hst_1234567890'
|
||||
export DEFAULT_USER='u_1234567890'
|
||||
export DEFAULT_UNPRIVILEGED_USER='u_0987654321'
|
||||
export DEFAULT_GLOBAL='global'
|
||||
|
||||
function strip() {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
load _authorized_actions
|
||||
|
||||
function list_sessions() {
|
||||
boundary sessions list -scope-id $1 -format json
|
||||
}
|
||||
|
||||
function count_sessions() {
|
||||
list_sessions $1 | jq '. | length'
|
||||
}
|
||||
|
||||
function cancel_session() {
|
||||
boundary sessions cancel -id $1
|
||||
}
|
||||
|
||||
function read_session() {
|
||||
boundary sessions read -id $1
|
||||
}
|
||||
@@ -31,7 +31,7 @@ function assoc_host_sets() {
|
||||
boundary targets add-host-sets -id $id -host-set $hst
|
||||
}
|
||||
|
||||
function target_id() {
|
||||
function target_id_from_name() {
|
||||
local sid=$1
|
||||
local name=$2
|
||||
strip $(list_targets $sid | jq -c ".[] | select(.name | contains(\"$name\")) | .[\"id\"]")
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load _auth
|
||||
load _connect
|
||||
load _sessions
|
||||
load _helpers
|
||||
|
||||
@test "boundary/session: admin user can connect to default target" {
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Run twice so we have two values for later testing
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/session/connect: unpriv user can connect to default target" {
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Run twice so we have two values for later testing
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Note: there seems to be an issue with jq's length calculation -- an increase
|
||||
# in the returned array by 2 shows up as a 4 or more increase in jq's length count.
|
||||
# So for now, verify they're not the same.
|
||||
@test "boundary/session: verify admin and unpriv user see different counts" {
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run count_sessions $DEFAULT_P_ID
|
||||
[ "$status" -eq 0 ]
|
||||
unpriv_sessions="$output"
|
||||
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run count_sessions $DEFAULT_P_ID
|
||||
[ "$status" -eq 0 ]
|
||||
admin_sessions="$output"
|
||||
|
||||
[ "$unpriv_sessions" -lt "$admin_sessions" ]
|
||||
}
|
||||
|
||||
@test "boundary/session: verify read and cancelation permissions on admin session" {
|
||||
# Find an admin session
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run list_sessions $DEFAULT_P_ID
|
||||
[ "$status" -eq 0 ]
|
||||
id=$(echo "$output" | jq -r '[.[]|select(.user_id == "u_1234567890")][0].id')
|
||||
|
||||
# Check unpriv cannot read or cancel an admin's session
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run read_session $id
|
||||
[ "$status" -eq 1 ]
|
||||
run cancel_session $id
|
||||
[ "$status" -eq 1 ]
|
||||
|
||||
# Check that admin _can_
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run read_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
run cancel_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/session: verify read and cancelation permissions on unpriv session" {
|
||||
# Find an unpriv session
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run list_sessions $DEFAULT_P_ID
|
||||
[ "$status" -eq 0 ]
|
||||
id=$(echo "$output" | jq -r '[.[]|select(.user_id == "u_0987654321")][0].id')
|
||||
|
||||
# Check unpriv can read and cancel their own session
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run read_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
run cancel_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Check that admin can too
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
run read_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
run cancel_session $id
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
@@ -6,35 +6,61 @@ load _targets
|
||||
load _helpers
|
||||
|
||||
|
||||
@test "boundary/login: can login as default user" {
|
||||
@test "boundary/login: can login as admin user" {
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target/connect: default user can connect to default target" {
|
||||
@test "boundary/target/connect: admin user can connect to default target" {
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can create target" {
|
||||
@test "boundary/target: admin user can read default target" {
|
||||
run read_target $DEFAULT_TARGET
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/login: can login as unpriv user" {
|
||||
run login $DEFAULT_UNPRIVILEGED_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target/connect: unpriv user can connect to default target" {
|
||||
run connect_nc $DEFAULT_TARGET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: unpriv user can not read default target" {
|
||||
run read_target $DEFAULT_TARGET
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "boundary/login: login back in as admin user" {
|
||||
run login $DEFAULT_LOGIN
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: admin user can create target" {
|
||||
run create_tcp_target $DEFAULT_P_ID 22 $TGT_NAME
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can not create already created target" {
|
||||
@test "boundary/target: admin user can not create already created target" {
|
||||
run create_tcp_target $DEFAULT_P_ID 22 $TGT_NAME
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can read created target" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
@test "boundary/target: admin user can read created target" {
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run read_target $id
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: the $TGT_NAME target contains default authorized-actions" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
local out=$(read_target $id)
|
||||
|
||||
run has_default_target_actions "$out"
|
||||
@@ -42,35 +68,35 @@ load _helpers
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can add default host set to created target" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
@test "boundary/target: admin user can add default host set to created target" {
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run assoc_host_sets $id $DEFAULT_HOST_SET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: created target has default host set" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run target_has_host_set_id $id $DEFAULT_HOST_SET
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target/connect: default user can connect to created target" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run connect_nc $id
|
||||
echo "connecting to $id: $output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can delete target" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run delete_target $id
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "boundary/target: default user can not read deleted target" {
|
||||
local id=$(target_id $DEFAULT_P_ID $TGT_NAME)
|
||||
local id=$(target_id_from_name $DEFAULT_P_ID $TGT_NAME)
|
||||
run read_target $id
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
@@ -17,9 +17,6 @@ import (
|
||||
|
||||
func TestIPv6Listener(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
user := "user"
|
||||
password := "passpass"
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
@@ -38,11 +35,8 @@ func TestIPv6Listener(t *testing.T) {
|
||||
}
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: user,
|
||||
DefaultPassword: password,
|
||||
Logger: logger.Named("c1"),
|
||||
Config: conf,
|
||||
Logger: logger.Named("c1"),
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ import (
|
||||
|
||||
func TestMultiControllerMultiWorkerConnections(t *testing.T) {
|
||||
require := require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
user := "user"
|
||||
password := "passpass"
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
@@ -24,11 +21,8 @@ func TestMultiControllerMultiWorkerConnections(t *testing.T) {
|
||||
require.NoError(err)
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: user,
|
||||
DefaultPassword: password,
|
||||
Logger: logger.Named("c1"),
|
||||
Config: conf,
|
||||
Logger: logger.Named("c1"),
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
|
||||
@@ -20,9 +20,6 @@ import (
|
||||
|
||||
func TestUnixListener(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
amId := "ampw_1234567890"
|
||||
user := "user"
|
||||
password := "passpass"
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
@@ -50,11 +47,8 @@ func TestUnixListener(t *testing.T) {
|
||||
}
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: user,
|
||||
DefaultPassword: password,
|
||||
Logger: logger.Named("c1"),
|
||||
Config: conf,
|
||||
Logger: logger.Named("c1"),
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ import (
|
||||
)
|
||||
|
||||
func TestWorkerTagging(t *testing.T) {
|
||||
amId := "ampw_1234567890"
|
||||
user := "user"
|
||||
password := "passpass"
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
@@ -29,9 +26,6 @@ func TestWorkerTagging(t *testing.T) {
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
DefaultAuthMethodId: amId,
|
||||
DefaultLoginName: user,
|
||||
DefaultPassword: password,
|
||||
InitialResourcesSuffix: "1234567890",
|
||||
Logger: logger.Named("c1"),
|
||||
})
|
||||
|
||||
@@ -689,14 +689,14 @@ var session = &Resource{
|
||||
},
|
||||
{
|
||||
Name: "read:self",
|
||||
Description: "Read a session, which must belong to the calling user",
|
||||
Description: "Read a session, which must be associated with the calling user",
|
||||
Examples: []string{
|
||||
"id=*;type=session;actions=read:self",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cancel:self",
|
||||
Description: "Cancel a session, which must belong to the calling user",
|
||||
Description: "Cancel a session, which must be associated with the calling user",
|
||||
Examples: []string{
|
||||
"id=*;type=session;actions=cancel:self",
|
||||
},
|
||||
|
||||
@@ -464,3 +464,13 @@ func StringListToInterfaceList(in []string) []interface{} {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Reverse reverses the input string
|
||||
func Reverse(in string) string {
|
||||
l := len(in)
|
||||
out := make([]byte, l)
|
||||
for i := 0; i <= l/2; i++ {
|
||||
out[i], out[l-1-i] = in[l-1-i], in[i]
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
@@ -633,3 +633,25 @@ func TestPrintable(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
in: "abc",
|
||||
out: "cba",
|
||||
},
|
||||
{
|
||||
in: "abcd",
|
||||
out: "dcba",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
require.Equal(t, Reverse(tc.in), tc.out)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,22 @@ Roles are composable; a user's final set of grants will be composed of various
|
||||
roles that each contribute grants to a set of principals that include that user
|
||||
or groups of which that user is a member.
|
||||
|
||||
## Subactions
|
||||
|
||||
Starting in Boundary 0.1.6, some subactions are supported. These actions have a
|
||||
format `top_level_action:subaction`, such as `read:self`. Being granted the top
|
||||
level action infers being granted all subactions. Thus, if a grant conveys
|
||||
`read`, it also matches the API actions `read` and `read:self`. However, if a
|
||||
grant conveys `read:self`, it will match the API action `read:self` but will not
|
||||
match `read`.
|
||||
|
||||
Note that not all API subactions are available for grants. As an example, auth
|
||||
methods require one or more steps to perform authentication. These are separated
|
||||
into separate API actions based on the required workflow for that authentication
|
||||
mechanism, e.g. `authenticate:login`. However, the grant system does not
|
||||
currently expose subactions for the `authenticate` top level action, as it
|
||||
generally does not make sense to allow only one step out of many.
|
||||
|
||||
## Applicable Resource Types
|
||||
|
||||
Boundary's [domain model](/docs/concepts/domain-model) is based on resource
|
||||
@@ -1251,8 +1267,8 @@ wildcard or templated grant strings.
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<code>read:self</code>: Read a session, which must belong to the
|
||||
calling user
|
||||
<code>read:self</code>: Read a session, which must be associated
|
||||
with the calling user
|
||||
</li>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -1260,8 +1276,8 @@ wildcard or templated grant strings.
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<code>cancel:self</code>: Cancel a session, which must belong to the
|
||||
calling user
|
||||
<code>cancel:self</code>: Cancel a session, which must be associated
|
||||
with the calling user
|
||||
</li>
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
Reference in new issue
Block a user