* Add ListResolvableAliases to the user service There are a few non common ways this method uses our ACL and grant system. A user is able to list resolvable aliases for another user if they are granted permission to do so. That means we had to load the grants for the user being listed for and not the requester when determining if an alias is able to resolve to a destination for which the requested user has permission. Similarly, the grant hash used for the list pagination is required to be that of the user being listed and not the requester.
880 lines
31 KiB
Go
880 lines
31 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package base
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/globals"
|
|
aliastar "github.com/hashicorp/boundary/internal/alias/target"
|
|
"github.com/hashicorp/boundary/internal/auth/password"
|
|
credstatic "github.com/hashicorp/boundary/internal/credential/static"
|
|
credstore "github.com/hashicorp/boundary/internal/credential/static/store"
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/host/static"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/hashicorp/boundary/internal/plugin"
|
|
"github.com/hashicorp/boundary/internal/target"
|
|
"github.com/hashicorp/boundary/internal/target/tcp"
|
|
"github.com/hashicorp/boundary/internal/types/scope"
|
|
"github.com/hashicorp/boundary/internal/util"
|
|
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
|
"github.com/hashicorp/go-secure-stdlib/base62"
|
|
)
|
|
|
|
func (b *Server) CreateInitialLoginRole(ctx context.Context) (*iam.Role, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create repo for initial login role: %w", err)
|
|
}
|
|
|
|
pr, err := iam.NewRole(ctx,
|
|
scope.Global.String(),
|
|
iam.WithName("Login Grants"),
|
|
iam.WithDescription(`Role created for login capability for unauthenticated users`),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
|
}
|
|
role, _, _, _, err := iamRepo.CreateRole(ctx, pr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating role for default generated grants: %w", err)
|
|
}
|
|
if _, err := iamRepo.AddRoleGrants(ctx, role.PublicId, role.Version, []string{
|
|
"ids=*;type=scope;actions=list,no-op",
|
|
"ids=*;type=auth-method;actions=list,authenticate",
|
|
"ids=*;type=auth-token;actions=read:self,delete:self",
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("error creating grant for initial login grants: %w", err)
|
|
}
|
|
if _, err := iamRepo.AddPrincipalRoles(ctx, role.PublicId, role.Version+1, []string{globals.AnonymousUserId}); err != nil {
|
|
return nil, fmt.Errorf("error adding principal to role for initial login grants: %w", err)
|
|
}
|
|
if _, _, err := iamRepo.SetRoleGrantScopes(ctx, role.PublicId, role.Version+2, []string{"this", "descendants"}); err != nil {
|
|
return nil, fmt.Errorf("error adding scope grants to role for initial login grants: %w", err)
|
|
}
|
|
|
|
return role, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialAuthenticatedUserRole(ctx context.Context, opt ...Option) (*iam.Role, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create repo for initial login role: %w", err)
|
|
}
|
|
|
|
pr, err := iam.NewRole(ctx,
|
|
scope.Global.String(),
|
|
iam.WithName("Authenticated User Grants"),
|
|
iam.WithDescription(`Role created for account self-management, and other initial authenticated user grants`),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
|
}
|
|
role, _, _, _, err := iamRepo.CreateRole(ctx, pr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating role for default generated grants: %w", err)
|
|
}
|
|
grants := []string{
|
|
"ids=*;type=scope;actions=read",
|
|
"ids=*;type=auth-token;actions=list",
|
|
"ids={{.Account.Id}};actions=read,change-password",
|
|
"ids=*;type=session;actions=list,read:self,cancel:self",
|
|
"ids={{.User.Id}};type=user;actions=list-resolvable-aliases",
|
|
}
|
|
opts := GetOpts(opt...)
|
|
if opts.withAuthUserTargetAuthorizeSessionGrant {
|
|
grants = append(grants, "ids=*;type=target;actions=list,read,authorize-session")
|
|
} else {
|
|
grants = append(grants, "ids=*;type=target;actions=list,read")
|
|
}
|
|
if _, err := iamRepo.AddRoleGrants(ctx, role.PublicId, role.Version, grants); err != nil {
|
|
return nil, fmt.Errorf("error creating grant for initial authenticated user grants: %w", err)
|
|
}
|
|
if _, err := iamRepo.AddPrincipalRoles(ctx, role.PublicId, role.Version+1, []string{globals.AnyAuthenticatedUserId}); err != nil {
|
|
return nil, fmt.Errorf("error adding principal to role for initial authenticated user grants: %w", err)
|
|
}
|
|
if _, _, err := iamRepo.SetRoleGrantScopes(ctx, role.PublicId, role.Version+2, []string{"this", "descendants"}); err != nil {
|
|
return nil, fmt.Errorf("error adding scope grants to role for initial authenticated user grants: %w", err)
|
|
}
|
|
|
|
return role, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialPasswordAuthMethod(ctx context.Context) (*password.AuthMethod, *iam.User, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
// Create the dev auth method
|
|
pwRepo, err := password.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating password repo: %w", err)
|
|
}
|
|
authMethod, err := password.NewAuthMethod(ctx, scope.Global.String(),
|
|
password.WithName("Generated global scope initial password auth method"),
|
|
password.WithDescription("Provides initial administrative and unprivileged authentication into Boundary"),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating new in memory auth method: %w", err)
|
|
}
|
|
if b.DevPasswordAuthMethodId == "" {
|
|
b.DevPasswordAuthMethodId, err = db.NewPublicId(ctx, globals.PasswordAuthMethodPrefix)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error generating initial auth method id: %w", err)
|
|
}
|
|
}
|
|
|
|
am, err := pwRepo.CreateAuthMethod(ctx, authMethod,
|
|
password.WithPublicId(b.DevPasswordAuthMethodId))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error saving auth method to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated password auth method id")
|
|
b.Info["generated password auth method id"] = b.DevPasswordAuthMethodId
|
|
|
|
// we'll designate the initial password auth method as the primary auth
|
|
// method id for the global scope, which means the auth method will create
|
|
// users on first login. Otherwise, the operator would have to create both
|
|
// a password account and a user associated with the new account, before
|
|
// users could successfully login.
|
|
iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to create iam repo: %w", err)
|
|
}
|
|
globalScope, err := iamRepo.LookupScope(ctx, scope.Global.String())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to lookup global scope: %w", err)
|
|
}
|
|
globalScope.PrimaryAuthMethodId = am.PublicId
|
|
if _, _, err := iamRepo.UpdateScope(ctx, globalScope, globalScope.Version, []string{"PrimaryAuthMethodId"}); err != nil {
|
|
return nil, nil, fmt.Errorf("unable to set primary auth method for global scope: %w", err)
|
|
}
|
|
|
|
createUser := func(loginName, loginPassword, userId, accountId string, admin bool) (*iam.User, error) {
|
|
// Create the dev admin user
|
|
if loginName == "" {
|
|
return nil, fmt.Errorf("empty login name")
|
|
}
|
|
if loginPassword == "" {
|
|
return nil, fmt.Errorf("empty login name")
|
|
}
|
|
if userId == "" {
|
|
return nil, fmt.Errorf("empty user id")
|
|
}
|
|
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(ctx, am.PublicId, password.WithLoginName(loginName))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating new in memory password auth account: %w", err)
|
|
}
|
|
acct, err = pwRepo.CreateAccount(
|
|
ctx,
|
|
scope.Global.String(),
|
|
acct,
|
|
password.WithPassword(loginPassword),
|
|
password.WithPublicId(accountId),
|
|
)
|
|
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(ctx, rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create iam repo: %w", err)
|
|
}
|
|
|
|
// Create a new user and associate it with the account
|
|
opts := []iam.Option{
|
|
iam.WithPublicId(userId),
|
|
}
|
|
if admin {
|
|
opts = append(opts,
|
|
iam.WithName("admin"),
|
|
iam.WithDescription(fmt.Sprintf(`Initial admin user within the "%s" scope`, scope.Global.String())),
|
|
)
|
|
} else {
|
|
opts = append(opts,
|
|
iam.WithName("user"),
|
|
iam.WithDescription("Initial unprivileged user"),
|
|
)
|
|
}
|
|
u, err := iam.NewUser(ctx, scope.Global.String(), opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating in memory user: %w", err)
|
|
}
|
|
if u, err = iamRepo.CreateUser(ctx, u, opts...); err != nil {
|
|
return nil, fmt.Errorf("error creating initial %s user: %w", typeStr, err)
|
|
}
|
|
if _, err = iamRepo.AddUserAccounts(ctx, 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(ctx,
|
|
scope.Global.String(),
|
|
iam.WithName("Administration"),
|
|
iam.WithDescription("Provides admin grants within all scopes to the initial admin user"),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating in memory role for generated grants: %w", err)
|
|
}
|
|
adminRole, _, _, _, err := iamRepo.CreateRole(ctx, pr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating role for default generated grants: %w", err)
|
|
}
|
|
if _, err := iamRepo.AddRoleGrants(ctx, adminRole.PublicId, adminRole.Version, []string{"ids=*;type=*;actions=*"}); err != nil {
|
|
return nil, fmt.Errorf("error creating grant for default generated grants: %w", err)
|
|
}
|
|
if _, err := iamRepo.AddPrincipalRoles(ctx, adminRole.PublicId, adminRole.Version+1, []string{u.GetPublicId()}, nil); err != nil {
|
|
return nil, fmt.Errorf("error adding principal to role for default generated grants: %w", err)
|
|
}
|
|
if _, _, err := iamRepo.SetRoleGrantScopes(ctx, adminRole.PublicId, adminRole.Version+2, []string{"this", "descendants"}); err != nil {
|
|
return nil, fmt.Errorf("error adding scope grants to role for default generated grants: %w", err)
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
switch {
|
|
case b.DevUnprivilegedLoginName == "",
|
|
b.DevUnprivilegedPassword == "",
|
|
b.DevUnprivilegedUserId == "":
|
|
default:
|
|
_, err := createUser(b.DevUnprivilegedLoginName, b.DevUnprivilegedPassword, b.DevUnprivilegedUserId, b.DevUnprivilegedPasswordAccountId, false)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
if b.DevLoginName == "" {
|
|
b.DevLoginName, err = base62.Random(10)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to generate login name: %w", err)
|
|
}
|
|
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.DevUserId == "" {
|
|
b.DevUserId, err = db.NewPublicId(ctx, globals.UserPrefix)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error generating initial user id: %w", err)
|
|
}
|
|
}
|
|
u, err := createUser(b.DevLoginName, b.DevPassword, b.DevUserId, b.DevPasswordAccountId, true)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return am, u, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialScopes(ctx context.Context, opt ...Option) (*iam.Scope, *iam.Scope, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
opts := GetOpts(opt...)
|
|
|
|
iamRepo, err := iam.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating scopes repository: %w", err)
|
|
}
|
|
|
|
// Create the scopes
|
|
if b.DevOrgId == "" {
|
|
b.DevOrgId, err = db.NewPublicId(ctx, scope.Org.Prefix())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error generating initial org id: %w", err)
|
|
}
|
|
}
|
|
iamOpts := []iam.Option{
|
|
iam.WithName("Generated org scope"),
|
|
iam.WithDescription("Provides an initial org scope in Boundary"),
|
|
iam.WithRandomReader(b.SecureRandomReader),
|
|
iam.WithPublicId(b.DevOrgId),
|
|
}
|
|
if len(opts.withIamOptions) > 0 {
|
|
iamOpts = append(iamOpts, opts.withIamOptions...)
|
|
}
|
|
orgScope, err := iam.NewOrg(ctx, iamOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating new in memory org scope: %w", err)
|
|
}
|
|
orgScope, err = iamRepo.CreateScope(ctx, orgScope, b.DevUserId, iamOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error saving org scope to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated org scope id")
|
|
b.Info["generated org scope id"] = b.DevOrgId
|
|
|
|
if b.DevProjectId == "" {
|
|
b.DevProjectId, err = db.NewPublicId(ctx, scope.Project.Prefix())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error generating initial project id: %w", err)
|
|
}
|
|
}
|
|
iamOpts = []iam.Option{
|
|
iam.WithName("Generated project scope"),
|
|
iam.WithDescription("Provides an initial project scope in Boundary"),
|
|
iam.WithRandomReader(b.SecureRandomReader),
|
|
iam.WithPublicId(b.DevProjectId),
|
|
}
|
|
if len(opts.withIamOptions) > 0 {
|
|
iamOpts = append(iamOpts, opts.withIamOptions...)
|
|
}
|
|
projScope, err := iam.NewProject(ctx, b.DevOrgId, iamOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error creating new in memory project scope: %w", err)
|
|
}
|
|
projScope, err = iamRepo.CreateScope(ctx, projScope, b.DevUserId, iamOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error saving project scope to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated project scope id")
|
|
b.Info["generated project scope id"] = b.DevProjectId
|
|
|
|
return orgScope, projScope, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialHostResources(ctx context.Context) (*static.HostCatalog, *static.HostSet, *static.Host, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
staticRepo, err := static.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error creating static repository: %w", err)
|
|
}
|
|
|
|
// Host Catalog
|
|
if b.DevHostCatalogId == "" {
|
|
b.DevHostCatalogId, err = db.NewPublicId(ctx, globals.StaticHostCatalogPrefix)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error generating initial host catalog id: %w", err)
|
|
}
|
|
}
|
|
opts := []static.Option{
|
|
static.WithName("Generated host catalog"),
|
|
static.WithDescription("Provides an initial host catalog in Boundary"),
|
|
static.WithPublicId(b.DevHostCatalogId),
|
|
}
|
|
hc, err := static.NewHostCatalog(ctx, b.DevProjectId, opts...)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error creating in memory host catalog: %w", err)
|
|
}
|
|
if hc, err = staticRepo.CreateCatalog(ctx, hc, opts...); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error saving host catalog to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated host catalog id")
|
|
b.Info["generated host catalog id"] = b.DevHostCatalogId
|
|
|
|
// Host
|
|
if b.DevHostId == "" {
|
|
b.DevHostId, err = db.NewPublicId(ctx, globals.StaticHostPrefix)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error generating initial host id: %w", err)
|
|
}
|
|
}
|
|
if b.DevHostAddress == "" {
|
|
b.DevHostAddress = "localhost"
|
|
}
|
|
opts = []static.Option{
|
|
static.WithName("Generated host"),
|
|
static.WithDescription("Provides an initial host in Boundary"),
|
|
static.WithAddress(b.DevHostAddress),
|
|
static.WithPublicId(b.DevHostId),
|
|
}
|
|
h, err := static.NewHost(ctx, hc.PublicId, opts...)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error creating in memory host: %w", err)
|
|
}
|
|
if h, err = staticRepo.CreateHost(ctx, b.DevProjectId, h, opts...); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error saving host to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated host id")
|
|
b.Info["generated host id"] = b.DevHostId
|
|
|
|
// Host Set
|
|
if b.DevHostSetId == "" {
|
|
b.DevHostSetId, err = db.NewPublicId(ctx, globals.StaticHostSetPrefix)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error generating initial host set id: %w", err)
|
|
}
|
|
}
|
|
opts = []static.Option{
|
|
static.WithName("Generated host set"),
|
|
static.WithDescription("Provides an initial host set in Boundary"),
|
|
static.WithPublicId(b.DevHostSetId),
|
|
}
|
|
hs, err := static.NewHostSet(ctx, hc.PublicId, opts...)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error creating in memory host set: %w", err)
|
|
}
|
|
if hs, err = staticRepo.CreateSet(ctx, b.DevProjectId, hs, opts...); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error saving host set to the db: %w", err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated host set id")
|
|
b.Info["generated host set id"] = b.DevHostSetId
|
|
|
|
// Associate members
|
|
if _, err := staticRepo.AddSetMembers(ctx, b.DevProjectId, b.DevHostSetId, hs.GetVersion(), []string{h.GetPublicId()}); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error associating host set to host in the db: %w", err)
|
|
}
|
|
|
|
return hc, hs, h, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialTargetWithAddress(ctx context.Context) (target.Target, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(ctx, kms.WithRootWrapper(b.RootKms)); err != nil {
|
|
return nil, fmt.Errorf("failed to add config keys to kms: %w", err)
|
|
}
|
|
|
|
targetRepo, err := target.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create target repository: %w", err)
|
|
}
|
|
|
|
// When this function is not called as part of boundary dev (eg: as part of
|
|
// boundary database init or tests), generate random target ids.
|
|
if len(b.DevTargetId) == 0 {
|
|
b.DevTargetId, err = db.NewPublicId(ctx, globals.TcpTargetPrefix)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate initial target id: %w", err)
|
|
}
|
|
}
|
|
if b.DevTargetDefaultPort == 0 {
|
|
b.DevTargetDefaultPort = 22
|
|
}
|
|
if len(b.DevTargetAddress) == 0 {
|
|
b.DevTargetAddress = "127.0.0.1"
|
|
}
|
|
opts := []target.Option{
|
|
target.WithName("Generated target with a direct address"),
|
|
target.WithDescription("Provides an initial target using an address in Boundary"),
|
|
target.WithDefaultPort(uint32(b.DevTargetDefaultPort)),
|
|
target.WithSessionMaxSeconds(uint32(b.DevTargetSessionMaxSeconds)),
|
|
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
|
target.WithPublicId(b.DevTargetId),
|
|
target.WithAddress(b.DevTargetAddress),
|
|
}
|
|
tt, err := b.createTarget(ctx, targetRepo, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated target with address id")
|
|
b.Info["generated target with address id"] = b.DevTargetId
|
|
|
|
return tt, nil
|
|
}
|
|
|
|
func (b *Server) createTarget(ctx context.Context, targetRepo *target.Repository, opts ...target.Option) (target.Target, error) {
|
|
t, err := target.New(ctx, tcp.Subtype, b.DevProjectId, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create target object: %w", err)
|
|
}
|
|
tt, err := targetRepo.CreateTarget(ctx, t, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save target to the db: %w", err)
|
|
}
|
|
|
|
return tt, nil
|
|
}
|
|
|
|
func (b *Server) CreateInitialTargetWithHostSources(ctx context.Context) (target.Target, error) {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, fmt.Errorf("failed to add config keys to kms: %w", err)
|
|
}
|
|
|
|
targetRepo, err := target.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create target repository: %w", err)
|
|
}
|
|
|
|
// When this function is not called as part of boundary dev (eg: as part of
|
|
// boundary database init or tests), generate random target ids.
|
|
if len(b.DevSecondaryTargetId) == 0 {
|
|
b.DevSecondaryTargetId, err = db.NewPublicId(ctx, globals.TcpTargetPrefix)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate initial secondary target id: %w", err)
|
|
}
|
|
}
|
|
if b.DevTargetDefaultPort == 0 {
|
|
b.DevTargetDefaultPort = 22
|
|
}
|
|
|
|
opts := []target.Option{
|
|
target.WithName("Generated target using host sources"),
|
|
target.WithDescription("Provides a target using host sources in Boundary"),
|
|
target.WithDefaultPort(uint32(b.DevTargetDefaultPort)),
|
|
target.WithSessionMaxSeconds(uint32(b.DevTargetSessionMaxSeconds)),
|
|
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
|
target.WithPublicId(b.DevSecondaryTargetId),
|
|
}
|
|
tt, err := b.createTarget(ctx, targetRepo, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tt, err = targetRepo.AddTargetHostSources(ctx, tt.GetPublicId(), tt.GetVersion(), []string{b.DevHostSetId})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to add host source %q to target: %w", b.DevHostSetId, err)
|
|
}
|
|
b.InfoKeys = append(b.InfoKeys, "generated target with host source id")
|
|
b.Info["generated target with host source id"] = b.DevSecondaryTargetId
|
|
|
|
return tt, nil
|
|
}
|
|
|
|
// Create targets that can be connected to using an alias. The three targets created are:
|
|
// - "postgres.boundary.dev": the Boundary dev postgres instance. Uses brokered credentials
|
|
// - "www.hashicorp.com": a web target
|
|
// - "ssh.boundary.dev": A localhost ssh target
|
|
func (b *Server) CreateInitialTargetsWithAlias(ctx context.Context) error {
|
|
rw := db.New(b.Database)
|
|
|
|
kmsCache, err := kms.New(ctx, rw, rw)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create kms cache: %w", err)
|
|
}
|
|
if err := kmsCache.AddExternalWrappers(
|
|
b.Context,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return fmt.Errorf("failed to add config keys to kms: %w", err)
|
|
}
|
|
|
|
targetRepo, err := target.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create target repository: %w", err)
|
|
}
|
|
|
|
credsRepo, err := credstatic.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create creds repository: %w", err)
|
|
}
|
|
|
|
aliasRepo, err := aliastar.NewRepository(ctx, rw, rw, kmsCache)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create alias repository: %w", err)
|
|
}
|
|
|
|
err = b.createPostgresAliasTarget(ctx, targetRepo, aliasRepo, credsRepo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create postgres alias target: %w", err)
|
|
}
|
|
|
|
err = b.createWebTarget(ctx, targetRepo, aliasRepo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create web target: %w", err)
|
|
}
|
|
|
|
err = b.createSshAliasTarget(ctx, targetRepo, aliasRepo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create ssh target: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Server) createSshAliasTarget(ctx context.Context, targetRepo *target.Repository, aliasRepo *aliastar.Repository) error {
|
|
opts := []target.Option{
|
|
target.WithName("Generated localhost ssh target with an alias"),
|
|
target.WithDescription("Provides an initial localhost target to SSH to using an alias in Boundary"),
|
|
target.WithDefaultPort(22),
|
|
target.WithSessionMaxSeconds(uint32(b.DevTargetSessionMaxSeconds)),
|
|
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
|
target.WithAddress("127.0.0.1"),
|
|
}
|
|
t, err := b.createTarget(ctx, targetRepo, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sshAlias := "ssh.boundary.dev"
|
|
a, err := aliastar.NewAlias(ctx, "global", sshAlias, aliastar.WithDestinationId(t.GetPublicId()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create alias object %w", err)
|
|
}
|
|
_, err = aliasRepo.CreateAlias(ctx, a)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save alias to the db %w", err)
|
|
}
|
|
|
|
b.InfoKeys = append(b.InfoKeys, "generated ssh target with alias")
|
|
b.Info["generated ssh target with alias"] = sshAlias
|
|
return nil
|
|
}
|
|
|
|
func (b *Server) createPostgresAliasTarget(ctx context.Context,
|
|
targetRepo *target.Repository,
|
|
aliasRepo *aliastar.Repository,
|
|
credsRepo *credstatic.Repository,
|
|
) error {
|
|
u, err := url.Parse(b.DatabaseUrl)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse DB url: %w", err)
|
|
}
|
|
host, portStr, err := net.SplitHostPort(u.Host)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to split host port from DB url: %w", err)
|
|
}
|
|
port, err := strconv.ParseUint(portStr, 10, 32)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse postgres port from DB url: %w", err)
|
|
}
|
|
|
|
dbname := strings.Trim(u.Path, "/")
|
|
|
|
opts := []target.Option{
|
|
target.WithName("Generated local postgres target with alias"),
|
|
target.WithDescription(fmt.Sprintf("Provides a local postgres target using aliasing in Boundary. Connect using the flag `-dbname %s`", dbname)),
|
|
target.WithDefaultPort(uint32(port)),
|
|
target.WithAddress(host),
|
|
target.WithSessionMaxSeconds(uint32(b.DevTargetSessionMaxSeconds)),
|
|
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
|
}
|
|
t, err := b.createTarget(ctx, targetRepo, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs, err := credsRepo.CreateCredentialStore(ctx,
|
|
&credstatic.CredentialStore{
|
|
CredentialStore: &credstore.CredentialStore{
|
|
ProjectId: b.DevProjectId,
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create cred store: %w", err)
|
|
}
|
|
cred, err := credstatic.NewUsernamePasswordCredential(cs.PublicId, "postgres", "password")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create cred: %w", err)
|
|
}
|
|
upCred, err := credsRepo.CreateUsernamePasswordCredential(ctx, b.DevProjectId, cred)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store cred: %w", err)
|
|
}
|
|
_, err = targetRepo.AddTargetCredentialSources(
|
|
ctx,
|
|
t.GetPublicId(),
|
|
t.GetVersion(),
|
|
target.CredentialSources{
|
|
BrokeredCredentialIds: []string{upCred.PublicId},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to associate cred to target: %w", err)
|
|
}
|
|
|
|
postgresAlias := "postgres.boundary.dev"
|
|
a, err := aliastar.NewAlias(ctx, "global", postgresAlias, aliastar.WithDestinationId(t.GetPublicId()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create alias object %w", err)
|
|
}
|
|
_, err = aliasRepo.CreateAlias(ctx, a)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save alias to the db %w", err)
|
|
}
|
|
|
|
b.InfoKeys = append(b.InfoKeys, "generated postgres target with alias")
|
|
b.Info["generated postgres target with alias"] = postgresAlias
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Server) createWebTarget(ctx context.Context,
|
|
targetRepo *target.Repository,
|
|
aliasRepo *aliastar.Repository,
|
|
) error {
|
|
opts := []target.Option{
|
|
target.WithName("www.hashicorp.com"),
|
|
target.WithDescription("Provides an initial web target using an address in Boundary. Note: Only HTTPS is supported, as this target uses port 443. Alias names for web targets should match the URL for the target."),
|
|
target.WithDefaultPort(443),
|
|
target.WithSessionMaxSeconds(5),
|
|
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
|
target.WithAddress("www.hashicorp.com"),
|
|
}
|
|
t, err := b.createTarget(ctx, targetRepo, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
webAlias := "www.hashicorp.com"
|
|
a, err := aliastar.NewAlias(ctx, "global", webAlias, aliastar.WithDestinationId(t.GetPublicId()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create alias object %w", err)
|
|
}
|
|
_, err = aliasRepo.CreateAlias(ctx, a)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save alias to the db %w", err)
|
|
}
|
|
|
|
b.InfoKeys = append(b.InfoKeys, "generated web target with alias")
|
|
b.Info["generated web target with alias"] = webAlias
|
|
|
|
return nil
|
|
}
|
|
|
|
// RegisterPlugin creates a plugin in the database if not present, and flags
|
|
// the plugin type based on the flags parameter. If the PluginTypeHost flag is
|
|
// set, it also registers the plugin in the shared map of running plugins.
|
|
// Since all boundary provided plugins must have a name, a name is required
|
|
// when calling RegisterPlugin and will be used even if WithName is provided.
|
|
// hostClient must not be nil when host flag is included.
|
|
func (b *Server) RegisterPlugin(ctx context.Context, name string, hostClient plgpb.HostPluginServiceClient, flags []plugin.PluginType, opt ...plugin.Option) (*plugin.Plugin, error) {
|
|
const op = "base.(Server).RegisterPlugin"
|
|
switch {
|
|
case len(flags) == 0:
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "plugins must be initialized with at least one plugin type")
|
|
case name == "":
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no name provided when creating plugin")
|
|
}
|
|
|
|
rw := db.New(b.Database)
|
|
|
|
if b.Kms == nil {
|
|
var err error
|
|
if b.Kms, err = kms.New(ctx, rw, rw); err != nil {
|
|
return nil, fmt.Errorf("error creating kms cache: %w", err)
|
|
}
|
|
}
|
|
if err := b.Kms.AddExternalWrappers(
|
|
ctx,
|
|
kms.WithRootWrapper(b.RootKms),
|
|
); err != nil {
|
|
return nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
|
}
|
|
|
|
hpRepo, err := plugin.NewRepository(ctx, rw, rw, b.Kms)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating plugin repository: %w", err)
|
|
}
|
|
|
|
plg, err := hpRepo.LookupPluginByName(ctx, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error looking up plugin by name: %w", err)
|
|
}
|
|
|
|
if plg == nil {
|
|
opt = append(opt, plugin.WithName(name))
|
|
plg = plugin.NewPlugin(opt...)
|
|
plg, err = hpRepo.CreatePlugin(ctx, plg, opt...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating plugin: %w", err)
|
|
}
|
|
}
|
|
|
|
// TODO: support flags should be moved to the create/update repo calls before plugins are exposed to users
|
|
for _, flag := range flags {
|
|
switch flag {
|
|
case plugin.PluginTypeHost:
|
|
if util.IsNil(hostClient) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "no host client provided when initializing host plugin")
|
|
}
|
|
if err := hpRepo.AddSupportFlag(ctx, plg, plugin.PluginTypeHost); err != nil {
|
|
return nil, err
|
|
}
|
|
if b.HostPlugins == nil {
|
|
b.HostPlugins = make(map[string]plgpb.HostPluginServiceClient)
|
|
}
|
|
b.HostPlugins[plg.GetPublicId()] = hostClient
|
|
case plugin.PluginTypeStorage:
|
|
if err := hpRepo.AddSupportFlag(ctx, plg, plugin.PluginTypeStorage); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return plg, nil
|
|
}
|