@ -10,13 +10,15 @@ import (
"github.com/hashicorp/boundary/api/recovery"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/errors"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/perms"
@ -25,6 +27,9 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/internal/types/subtypes"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/boundary/internal/util/template"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
"github.com/mr-tron/base58"
@ -56,6 +61,9 @@ type key int
var verifierKey key
type VerifyResults struct {
UserData template . Data
// This is copied out from UserData above but used to avoid nil checks in
// lots of places that embed this value
UserId string
AuthTokenId string
Error error
@ -92,15 +100,47 @@ type VerifyResults struct {
}
type verifier struct {
iamRepoFn common . IamRepoFactory
authTokenRepoFn common . AuthTokenRepoFactory
serversRepoFn common . ServersRepoFactory
kms * kms . Kms
requestInfo * authpb . RequestInfo
res * perms . Resource
act action . Type
ctx context . Context
acl perms . ACL
iamRepoFn common . IamRepoFactory
authTokenRepoFn common . AuthTokenRepoFactory
serversRepoFn common . ServersRepoFactory
passwordAuthRepoFn common . PasswordAuthRepoFactory
oidcAuthRepoFn common . OidcAuthRepoFactory
kms * kms . Kms
requestInfo * authpb . RequestInfo
res * perms . Resource
act action . Type
ctx context . Context
acl perms . ACL
}
// TODO (jefferai 10/2022): NewVerifierContextWithAccounts performs the function
// of NewVerifierContext (see the docs for that function) but with extra
// parameters that can be used to look up account information. This is not
// intended to be a long-lived function; see
// https://hashicorp.atlassian.net/browse/ICU-6571 and
// https://hashicorp.atlassian.net/browse/ICU-6572
//
// This is being added for a quick turnaround purpose and to avoid making large
// numbers of changes to tests when we may do a much bigger refactor; when those
// items are addressed this can be removed.
func NewVerifierContextWithAccounts ( ctx context . Context ,
iamRepoFn common . IamRepoFactory ,
authTokenRepoFn common . AuthTokenRepoFactory ,
serversRepoFn common . ServersRepoFactory ,
passwordAuthRepoFn common . PasswordAuthRepoFactory ,
oidcAuthRepoFn common . OidcAuthRepoFactory ,
kms * kms . Kms ,
requestInfo * authpb . RequestInfo ,
) context . Context {
return context . WithValue ( ctx , verifierKey , & verifier {
iamRepoFn : iamRepoFn ,
authTokenRepoFn : authTokenRepoFn ,
serversRepoFn : serversRepoFn ,
passwordAuthRepoFn : passwordAuthRepoFn ,
oidcAuthRepoFn : oidcAuthRepoFn ,
kms : kms ,
requestInfo : requestInfo ,
} )
}
// NewVerifierContext creates a context that carries a verifier object from the
@ -114,13 +154,7 @@ func NewVerifierContext(ctx context.Context,
kms * kms . Kms ,
requestInfo * authpb . RequestInfo ,
) context . Context {
return context . WithValue ( ctx , verifierKey , & verifier {
iamRepoFn : iamRepoFn ,
authTokenRepoFn : authTokenRepoFn ,
serversRepoFn : serversRepoFn ,
kms : kms ,
requestInfo : requestInfo ,
} )
return NewVerifierContextWithAccounts ( ctx , iamRepoFn , authTokenRepoFn , serversRepoFn , nil , nil , kms , requestInfo )
}
// Verify takes in a context that has expected parameters as values and runs an
@ -209,6 +243,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
}
}
ret . UserId = v . requestInfo . UserIdOverride
ret . UserData . User . Id = util . Pointer ( ret . UserId )
if reqInfo != nil {
reqInfo . UserId = ret . UserId
}
@ -235,14 +270,17 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
var authResults perms . ACLResults
var grantTuples [ ] perms . GrantTuple
var accountId, userName , userEmail string
var userData template . Data
var err error
authResults , ret . User Id, accountId , userName , userEmail , ret . Scope , v . acl , grantTuples , err = v . performAuthCheck ( ctx )
authResults , ret . User Data , ret . Scope , v . acl , grantTuples , err = v . performAuthCheck ( ctx )
if err != nil {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error performing authn/authz check" ) )
return
}
if ret . UserData . User . Id != nil {
ret . UserId = * ret . UserData . User . Id
}
ret . AuthTokenId = v . requestInfo . PublicId
ret . AuthenticationFinished = authResults . AuthenticationFinished
if ! authResults . Authorized {
@ -288,14 +326,20 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
} )
}
ea . UserInfo = & event . UserInfo {
UserId : ret . UserId ,
AuthAccountId : accountId ,
UserId : ret . UserId ,
}
if userData . Account . Id != nil {
ea . UserInfo . AuthAccountId = * userData . Account . Id
}
ea . GrantsInfo = & event . GrantsInfo {
Grants : grants ,
}
ea . UserName = userName
ea . UserEmail = userEmail
if userData . User . FullName != nil {
ea . UserName = * userData . User . FullName
}
if userData . User . Email != nil {
ea . UserEmail = * userData . User . Email
}
if reqInfo != nil {
reqInfo . UserId = ret . UserId
@ -434,10 +478,7 @@ func (v *verifier) decryptToken(ctx context.Context) {
func ( v verifier ) performAuthCheck ( ctx context . Context ) (
aclResults perms . ACLResults ,
userId string ,
accountId string ,
userName string ,
userEmail string ,
userData template . Data ,
scopeInfo * scopes . ScopeInfo ,
retAcl perms . ACL ,
grantTuples [ ] perms . GrantTuple ,
@ -449,7 +490,10 @@ func (v verifier) performAuthCheck(ctx context.Context) (
// Make the linter happy
_ = retErr
scopeInfo = new ( scopes . ScopeInfo )
userId = AnonymousUserId
// This will always be set, so further down below we can switch on whether
// it's empty
userData . User . Id = util . Pointer ( AnonymousUserId )
// Validate the token and fetch the corresponding user ID
switch v . requestInfo . TokenFormat {
@ -459,7 +503,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (
case uint32 ( AuthTokenTypeRecoveryKms ) :
// We validated the encrypted token in decryptToken and handled the
// nonces there, so just set the user
user Id = "u_recovery"
user Data. User . Id = util . Pointer ( "u_recovery" )
case uint32 ( AuthTokenTypeBearer ) , uint32 ( AuthTokenTypeSplitCookie ) :
if v . requestInfo . Token == "" {
@ -479,12 +523,12 @@ func (v verifier) performAuthCheck(ctx context.Context) (
break
}
if at != nil {
accountId = at. GetAuthAccountId ( )
user Id = at. GetIamUserId ( )
if user Id == "" {
userD ata. A ccount. Id = util. Pointer ( at. GetAuthAccountId ( ) )
user Data. User . Id = util. Pointer ( at. GetIamUserId ( ) )
if * user Data. User . Id == "" {
event . WriteError ( ctx , op , stderrors . New ( "perform auth check: valid token did not map to a user, likely because no account is associated with the user any longer; continuing as u_anon" ) , event . WithInfo ( "token_id" , at . GetPublicId ( ) ) )
user Id = AnonymousUserId
accountId = ""
user Data. User . Id = util. Pointer ( AnonymousUserId)
userData. Account . Id = nil
}
}
}
@ -495,13 +539,51 @@ func (v verifier) performAuthCheck(ctx context.Context) (
return
}
u , _ , err := iamRepo . LookupUser ( ctx , user Id)
u , _ , err := iamRepo . LookupUser ( ctx , * user Data. User . Id)
if err != nil {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to lookup user" ) )
return
}
userEmail = u . Email
userName = u . FullName
userData . User . Name = util . Pointer ( u . Name )
userData . User . Email = util . Pointer ( u . Email )
userData . User . FullName = util . Pointer ( u . FullName )
if userData . Account . Id != nil && * userData . Account . Id != "" && v . passwordAuthRepoFn != nil && v . oidcAuthRepoFn != nil {
const domain = "auth"
var acct auth . Account
var err error
switch subtypes . SubtypeFromId ( domain , * userData . Account . Id ) {
case password . Subtype :
repo , repoErr := v . passwordAuthRepoFn ( )
if repoErr != nil {
retErr = errors . Wrap ( ctx , repoErr , op , errors . WithMsg ( "failed to get password auth repo" ) )
return
}
acct , err = repo . LookupAccount ( ctx , * userData . Account . Id )
case oidc . Subtype :
repo , repoErr := v . oidcAuthRepoFn ( )
if repoErr != nil {
retErr = errors . Wrap ( ctx , repoErr , op , errors . WithMsg ( "failed to get oidc auth repo" ) )
return
}
acct , err = repo . LookupAccount ( ctx , * userData . Account . Id )
default :
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "unrecognized account id type" ) )
return
}
if err != nil {
if errors . IsNotFoundError ( err ) {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "account doesn't exist" ) )
return
}
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "error looking up account" ) )
return
}
userData . Account . Name = util . Pointer ( acct . GetName ( ) )
userData . Account . Email = util . Pointer ( acct . GetEmail ( ) )
userData . Account . LoginName = util . Pointer ( acct . GetLoginName ( ) )
userData . Account . Subject = util . Pointer ( acct . GetSubject ( ) )
}
// Look up scope details to return. We can skip a lookup when using the
// global scope
@ -546,7 +628,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (
// Fetch and parse grants for this user ID (which may include grants for
// u_anon and u_auth)
grantTuples , err = iamRepo . GrantsForUser ( v . ctx , user Id)
grantTuples , err = iamRepo . GrantsForUser ( v . ctx , * user Data. User . Id)
if err != nil {
retErr = errors . Wrap ( ctx , err , op )
return
@ -556,12 +638,17 @@ func (v verifier) performAuthCheck(ctx context.Context) (
// that we've since restricted, e.g. "id=foo;actions=create,read". These
// will simply not have an effect.
for _ , pair := range grantTuples {
permsOpts := [ ] perms . Option {
perms . WithUserId ( * userData . User . Id ) ,
perms . WithSkipFinalValidation ( true ) ,
}
if userData . Account . Id != nil {
permsOpts = append ( permsOpts , perms . WithAccountId ( * userData . Account . Id ) )
}
parsed , err := perms . Parse (
pair . ScopeId ,
pair . Grant ,
perms . WithUserId ( userId ) ,
perms . WithAccountId ( accountId ) ,
perms . WithSkipFinalValidation ( true ) )
permsOpts ... )
if err != nil {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed to parse grant %#v" , pair . Grant ) ) )
return
@ -570,7 +657,7 @@ func (v verifier) performAuthCheck(ctx context.Context) (
}
retAcl = perms . NewACL ( parsedGrants ... )
aclResults = retAcl . Allowed ( * v . res , v . act , user Id)
aclResults = retAcl . Allowed ( * v . res , v . act , * user Data. User . Id)
// We don't set authenticated above because setting this but not authorized
// is used for further permissions checks, such as during recursive listing.
// So we want to make sure any code relying on that has the full set of
@ -601,6 +688,13 @@ func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActi
return availableActions
}
// If there is no user ID set by definition there are no actions to fetch.
// This shouldn't happen because we should always fall back to at least the
// anonymous user so it's defense in depth.
if r . UserData . User . Id == nil {
return nil
}
opts := getOpts ( opt ... )
res := opts . withResource
// If not passed in, use what's already been populated through verification
@ -620,7 +714,7 @@ func (r *VerifyResults) fetchActions(id string, typ resource.Type, availableActi
ret := make ( action . ActionSet , 0 , len ( availableActions ) )
for _ , act := range availableActions {
if r . v . acl . Allowed ( * res , act , r . User Id) . Authorized {
if r . v . acl . Allowed ( * res , act , * r . User Data. User . Id) . Authorized {
ret = append ( ret , act )
}
}
@ -636,9 +730,14 @@ func (r *VerifyResults) FetchOutputFields(res perms.Resource, act action.Type) p
return perms . OutputFieldsMap { "*" : true }
case r . v . requestInfo . DisableAuthEntirely :
return nil
case r . UserData . User . Id == nil :
// If there is no user ID set by definition there are no actions to fetch.
// This shouldn't happen because we should always fall back to at least the
// anonymous user so it's defense in depth.
return nil
}
return r . v . acl . Allowed ( res , act , r . UserId ) . OutputFields
return r . v . acl . Allowed ( res , act , * r . User Data. User . Id) . OutputFields
}
// ACL returns the perms.ACL of the verifier.