2020-07-16 22:12:26 -04:00
package auth
import (
"context"
2021-07-28 06:31:16 -04:00
stderrors "errors"
2020-07-16 22:12:26 -04:00
"fmt"
2020-07-22 00:14:28 -04:00
"net/http"
2020-07-16 22:12:26 -04:00
"strings"
2020-08-20 13:34:03 -04:00
"time"
2020-07-16 22:12:26 -04:00
2021-09-07 22:13:19 -04:00
"github.com/hashicorp/boundary/api/recovery"
2020-08-24 13:22:10 -04:00
"github.com/hashicorp/boundary/globals"
2021-02-11 13:32:45 -06:00
"github.com/hashicorp/boundary/internal/errors"
2021-10-19 17:21:41 -04:00
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
2020-09-14 12:30:11 -04:00
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
2021-10-19 17:21:41 -04:00
2022-05-09 12:45:10 -07:00
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
2020-08-20 13:34:03 -04:00
"github.com/hashicorp/boundary/internal/kms"
2021-07-28 06:31:16 -04:00
"github.com/hashicorp/boundary/internal/observability/event"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/perms"
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/internal/requests"
2021-10-27 20:32:07 -04:00
"github.com/hashicorp/boundary/internal/servers"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/action"
2020-09-16 11:15:42 -04:00
"github.com/hashicorp/boundary/internal/types/resource"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
2021-08-26 10:44:41 -04:00
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
2022-03-16 17:04:07 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
2020-09-26 14:01:49 -04:00
"github.com/mr-tron/base58"
2020-08-25 00:20:50 -04:00
"google.golang.org/protobuf/proto"
2020-07-16 22:12:26 -04:00
)
2021-10-19 17:21:41 -04:00
type TokenFormat uint32
2020-07-22 00:14:28 -04:00
const (
2020-09-08 23:57:26 -04:00
// We weren't given one or couldn't parse it
2020-07-22 00:14:28 -04:00
AuthTokenTypeUnknown TokenFormat = iota
2020-08-25 00:20:50 -04:00
// Came in via the Authentication: Bearer header
2020-07-22 00:14:28 -04:00
AuthTokenTypeBearer
2020-08-25 00:20:50 -04:00
// Came in via split cookies
2020-07-22 00:14:28 -04:00
AuthTokenTypeSplitCookie
2020-08-25 00:20:50 -04:00
// It's of recovery type
AuthTokenTypeRecoveryKms
2020-07-22 00:14:28 -04:00
)
2021-04-13 16:42:25 -07:00
const (
AnonymousUserId = "u_anon"
)
2020-07-16 22:12:26 -04:00
type key int
2020-08-24 13:22:10 -04:00
var verifierKey key
2020-07-16 22:12:26 -04:00
2020-07-22 00:14:28 -04:00
type VerifyResults struct {
2021-04-13 16:55:40 -05:00
UserId string
AuthTokenId string
Error error
Scope * scopes . ScopeInfo
// AuthenticatedFinished means that the request has passed through the
// authentication system successfully. This does _not_ indicate whether a
// token was provided on the request. Requests for `u_anon` will still have
// this set true! This is because if a request has a token that is invalid,
// we fall back to `u_anon` because the request may still be allowed for any
// anonymous user; it simply fails to validate for and look up grants for an
// actual known user.
//
// A good example is when running dev mode twice. The first time you can
// authenticate and get a token which is saved by the token helper. The
// second time, you run a command and it reads the token from the helper.
// That token is no longer valid, but if the action is granted to `u_anon`
// the action should still succeed. What happens internally is that the
// token fails to look up a non-anonymous user, so we fallback to the
// anonymous user, which is the default.
//
// If you want to know if the request had a valid token provided, use a
// switch on UserId. Anything that isn't `u_anon` will have to have had a
// valid token provided. And a valid token will never fall back to `u_anon`.
AuthenticationFinished bool
2020-09-23 23:39:44 -04:00
2020-10-22 08:40:00 -04:00
// RoundTripValue can be set to allow the function performing authentication
// (often accompanied by lookup(s)) to return a result of that lookup to the
// calling function. It is opaque to this package.
RoundTripValue interface {}
2020-09-23 23:39:44 -04:00
// Used for additional verification
v * verifier
2020-07-16 22:12:26 -04:00
}
type verifier struct {
iamRepoFn common . IamRepoFactory
authTokenRepoFn common . AuthTokenRepoFactory
2020-08-24 13:22:10 -04:00
serversRepoFn common . ServersRepoFactory
2020-08-20 13:34:03 -04:00
kms * kms . Kms
2021-10-19 17:21:41 -04:00
requestInfo * authpb . RequestInfo
2020-07-16 22:12:26 -04:00
res * perms . Resource
act action . Type
ctx context . Context
2020-09-23 23:39:44 -04:00
acl perms . ACL
2020-07-16 22:12:26 -04:00
}
// NewVerifierContext creates a context that carries a verifier object from the
// HTTP handlers to the gRPC service handlers. It should only be created in the
// HTTP handler and should exist for every request that reaches the service
// handlers.
func NewVerifierContext ( ctx context . Context ,
iamRepoFn common . IamRepoFactory ,
authTokenRepoFn common . AuthTokenRepoFactory ,
2020-08-24 13:22:10 -04:00
serversRepoFn common . ServersRepoFactory ,
2020-08-20 13:34:03 -04:00
kms * kms . Kms ,
2022-04-19 11:42:54 -04:00
requestInfo * authpb . RequestInfo ,
) context . Context {
2020-07-16 22:12:26 -04:00
return context . WithValue ( ctx , verifierKey , & verifier {
iamRepoFn : iamRepoFn ,
authTokenRepoFn : authTokenRepoFn ,
2020-08-24 13:22:10 -04:00
serversRepoFn : serversRepoFn ,
2020-08-20 13:34:03 -04:00
kms : kms ,
2020-07-16 22:12:26 -04:00
requestInfo : requestInfo ,
})
}
// Verify takes in a context that has expected parameters as values and runs an
// authn/authz check. It returns a user ID, the scope ID for the request (which
// may come from the URL and may come from the token) and whether or not to
// proceed, e.g. whether the authn/authz check resulted in failure. If an error
// occurs it's logged to the system log.
2020-07-22 15:10:44 -04:00
func Verify ( ctx context . Context , opt ... Option ) ( ret VerifyResults ) {
2021-07-28 06:31:16 -04:00
const op = "auth.Verify"
2020-08-16 00:42:36 -04:00
ret . Error = handlers . ForbiddenError ()
2020-07-16 22:12:26 -04:00
v , ok := ctx . Value ( verifierKey ).( * verifier )
if ! ok {
// We don't have a logger yet and this should never happen in any
// context we won't catch in tests
panic ( "no verifier information found in context" )
}
2020-09-23 23:39:44 -04:00
ret . v = v
2020-09-01 12:31:54 -04:00
v . ctx = ctx
2021-10-28 21:14:39 -04:00
ea := & event . Auth {}
defer event . WriteAudit ( ctx , op , event . WithAuth ( ea ))
2020-08-13 17:39:48 -04:00
opts := getOpts ( opt ... )
2020-09-01 12:31:54 -04:00
2020-07-22 00:14:28 -04:00
ret . Scope = new ( scopes . ScopeInfo )
2021-03-25 03:15:43 -04:00
2021-05-04 19:57:35 -05:00
// Note: we don't call RequestContextFromCtx here because that performs a
// SelfOrDefault. That's useful in the handlers, but here we don't want to
// do anything if it's nil. That's mainly useful in tests where
// DisableAuthEntirely is set. Later, if we don't have the value set at all,
// we have some safeguards to ensure we fail safe (e.g. no output fields at
// all). So it provides a good indication as well whether we have set this
// where and when needed.
var reqInfo * requests . RequestContext
reqInfoRaw := ctx . Value ( requests . ContextRequestInformationKey )
if reqInfoRaw != nil {
reqInfo = reqInfoRaw .( * requests . RequestContext )
}
2021-03-25 03:15:43 -04:00
// In tests we often simply disable auth so we can test the service handlers
// without fuss
2020-07-22 00:14:28 -04:00
if v . requestInfo . DisableAuthEntirely {
2021-10-28 21:14:39 -04:00
yes := true
ea . DisabledAuthEntirely = & yes
2021-03-25 03:15:43 -04:00
const op = "auth.(disabled).lookupScope"
2021-10-19 17:21:41 -04:00
ret . Scope . Id = v . requestInfo . ScopeIdOverride
2020-09-01 12:31:54 -04:00
if ret . Scope . Id == "" {
ret . Scope . Id = opts . withScopeId
}
2021-03-25 03:15:43 -04:00
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch ret . Scope . Id {
2021-11-04 09:34:01 -04:00
case scope . Global . String ():
2021-03-25 03:15:43 -04:00
ret . Scope = & scopes . ScopeInfo {
Id : scope . Global . String (),
Type : scope . Global . String (),
Name : scope . Global . String (),
Description : "Global Scope" ,
ParentScopeId : "" ,
}
default :
iamRepo , err := v . iamRepoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
ret . Error = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get iam repo" ))
2021-03-25 03:15:43 -04:00
return
}
scp , err := iamRepo . LookupScope ( v . ctx , ret . Scope . Id )
if err != nil {
2021-07-29 18:52:56 -06:00
ret . Error = errors . Wrap ( ctx , err , op )
2021-03-25 03:15:43 -04:00
return
}
if scp == nil {
2022-04-29 09:52:01 -04:00
ret . Error = errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "non-existent scope %q" , ret . Scope . Id ))
2021-03-25 03:15:43 -04:00
return
}
ret . Scope = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
2020-07-22 00:14:28 -04:00
}
2021-10-19 17:21:41 -04:00
ret . UserId = v . requestInfo . UserIdOverride
2021-05-04 19:57:35 -05:00
if reqInfo != nil {
reqInfo . UserId = ret . UserId
}
2021-10-28 21:14:39 -04:00
ea . UserInfo = & event . UserInfo { UserId : ret . UserId }
2020-08-16 00:42:36 -04:00
ret . Error = nil
2020-07-22 00:14:28 -04:00
return
}
2020-08-20 13:34:03 -04:00
2020-09-08 13:27:51 -07:00
v . act = opts . withAction
v . res = & perms . Resource {
ScopeId : opts . withScopeId ,
Id : opts . withId ,
Pin : opts . withPin ,
Type : opts . withType ,
2020-07-16 22:12:26 -04:00
}
2020-09-16 11:15:42 -04:00
// Global scope has no parent ID; account for this
if opts . withId == scope . Global . String () && opts . withType == resource . Scope {
v . res . ScopeId = scope . Global . String ()
}
2020-07-16 22:12:26 -04:00
2020-09-14 12:30:11 -04:00
if v . requestInfo . EncryptedToken != "" {
2021-07-28 06:31:16 -04:00
v . decryptToken ( ctx )
2020-09-14 12:30:11 -04:00
}
2020-09-23 23:39:44 -04:00
var authResults perms . ACLResults
2021-10-28 21:14:39 -04:00
var grantTuples [] perms . GrantTuple
var accountId , userName , userEmail string
2020-07-22 00:14:28 -04:00
var err error
2021-10-28 21:14:39 -04:00
authResults , ret . UserId , accountId , userName , userEmail , ret . Scope , v . acl , grantTuples , err = v . performAuthCheck ( ctx )
2020-07-16 22:12:26 -04:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error performing authn/authz check" ))
2020-07-16 22:12:26 -04:00
return
}
2020-09-23 23:39:44 -04:00
2020-09-19 00:18:17 -04:00
ret . AuthTokenId = v . requestInfo . PublicId
2021-04-13 16:55:40 -05:00
ret . AuthenticationFinished = authResults . AuthenticationFinished
2021-03-24 15:33:04 -04:00
if ! authResults . Authorized {
2020-07-16 22:12:26 -04:00
if v . requestInfo . DisableAuthzFailures {
2020-08-16 00:42:36 -04:00
ret . Error = nil
// TODO: Decide whether to remove this
2021-07-28 06:31:16 -04:00
err := event . WriteObservation ( ctx , op ,
event . WithHeader (
"auth-results" , struct {
Msg string `json:"msg"`
Resource * perms . Resource `json:"resource"`
UserId string `json:"user_id"`
Action string `json:"action"`
}{
Msg : "failed authz info for request" ,
Resource : v . res ,
UserId : ret . UserId ,
Action : v . act . String (),
}))
if err != nil {
event . WriteError ( ctx , op , err )
}
2020-07-16 22:12:26 -04:00
} else {
2020-09-08 23:57:26 -04:00
// If the anon user was used (either no token, or invalid (perhaps
// expired) token), return a 401. That way if it's an authn'd user
// that is not authz'd we'll return 403 to be explicit.
2021-04-13 16:42:25 -07:00
if ret . UserId == AnonymousUserId {
2020-09-08 23:57:26 -04:00
ret . Error = handlers . UnauthenticatedError ()
}
2021-10-28 21:14:39 -04:00
ea . UserInfo = & event . UserInfo {
UserId : ret . UserId ,
}
2020-07-16 22:12:26 -04:00
return
}
}
2021-10-28 21:14:39 -04:00
grants := make ([] event . Grant , 0 , len ( grantTuples ))
for _ , g := range grantTuples {
grants = append ( grants , event . Grant {
Grant : g . Grant ,
RoleId : g . RoleId ,
ScopeId : g . ScopeId ,
})
}
ea . UserInfo = & event . UserInfo {
UserId : ret . UserId ,
AuthAccountId : accountId ,
}
ea . GrantsInfo = & event . GrantsInfo {
Grants : grants ,
}
ea . UserName = userName
ea . UserEmail = userEmail
2021-05-04 19:57:35 -05:00
if reqInfo != nil {
reqInfo . UserId = ret . UserId
reqInfo . OutputFields = authResults . OutputFields
}
2020-08-16 00:42:36 -04:00
ret . Error = nil
2020-07-16 22:12:26 -04:00
return
}
2021-07-28 06:31:16 -04:00
func ( v * verifier ) decryptToken ( ctx context . Context ) {
const op = "auth.(verifier).decryptToken"
2020-12-17 10:30:40 -05:00
switch v . requestInfo . TokenFormat {
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeUnknown ):
2020-12-17 10:30:40 -05:00
// Nothing to decrypt
return
2020-09-23 23:39:44 -04:00
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeBearer ), uint32 ( AuthTokenTypeSplitCookie ):
2020-12-17 10:30:40 -05:00
if v . kms == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no KMS object available to authz system" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
tokenRepo , err := v . authTokenRepoFn ()
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "failed to get authtoken repo" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
at , err := tokenRepo . LookupAuthToken ( v . ctx , v . requestInfo . PublicId )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "failed to look up auth token by public ID" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
if at == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "nil result from looking up auth token by public ID" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
tokenWrapper , err := v . kms . GetWrapper ( v . ctx , at . GetScopeId (), kms . KeyPurposeTokens )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "unable to get wrapper for tokens; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
version := v . requestInfo . EncryptedToken [ 0 : len ( globals . ServiceTokenV1 )]
switch version {
case globals . ServiceTokenV1 :
default :
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "unknown token encryption version; continuing as anonymous user" ), event . WithInfo ( "version" , version ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
marshaledToken , err := base58 . FastBase58Decoding ( v . requestInfo . EncryptedToken [ len ( globals . ServiceTokenV1 ):])
2020-09-23 23:39:44 -04:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error unmarshaling base58 token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-09-23 23:39:44 -04:00
return
}
2022-03-16 17:04:07 -04:00
blobInfo := new ( wrapping . BlobInfo )
2020-12-17 10:30:40 -05:00
if err := proto . Unmarshal ( marshaledToken , blobInfo ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error decoding encrypted token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2022-03-16 17:04:07 -04:00
s1Bytes , err := tokenWrapper . Decrypt ( v . ctx , blobInfo , wrapping . WithAad ([] byte ( v . requestInfo . PublicId )))
2020-12-17 10:30:40 -05:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error decrypting encrypted token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
2020-09-23 23:39:44 -04:00
}
2020-12-17 10:30:40 -05:00
var s1Info tokens . S1TokenInfo
if err := proto . Unmarshal ( s1Bytes , & s1Info ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error unmarshaling token info; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2021-10-19 17:21:41 -04:00
if v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeUnknown ) || s1Info . Token == "" || v . requestInfo . PublicId == "" {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "after parsing, could not find valid token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
v . requestInfo . Token = s1Info . Token
return
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeRecoveryKms ):
2020-12-17 10:30:40 -05:00
if v . kms == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no KMS object available to authz system" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2022-05-13 13:53:22 -04:00
wrapper := v . kms . GetExternalWrappers ( ctx ). Recovery ()
2020-12-17 10:30:40 -05:00
if wrapper == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no recovery KMS is available" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-09-23 23:39:44 -04:00
return
}
2020-12-17 10:30:40 -05:00
info , err := recovery . ParseRecoveryToken ( v . ctx , wrapper , v . requestInfo . EncryptedToken )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error parsing and validating recovery token" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
// If we add the validity period to the creation time (which we've
// verified is before the current time, with a minute of fudging), and
// it's before now, it's expired and might be a replay.
if info . CreationTime . Add ( globals . RecoveryTokenValidityPeriod ). Before ( time . Now ()) {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "recovery token has expired (possible replay attack)" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
repo , err := v . serversRepoFn ()
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error fetching servers repo" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2021-10-27 20:32:07 -04:00
if err := repo . AddNonce ( v . ctx , info . Nonce , servers . NoncePurposeRecovery ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error adding nonce to database (possible replay attack)" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "recovery KMS was used to authorize a call" ), event . WithInfo ( "url" , v . requestInfo . Path , "method" , v . requestInfo . Method ))
2020-09-23 23:39:44 -04:00
}
}
2021-10-28 21:14:39 -04:00
func ( v verifier ) performAuthCheck ( ctx context . Context ) (
aclResults perms . ACLResults ,
userId string ,
accountId string ,
userName string ,
userEmail string ,
scopeInfo * scopes . ScopeInfo ,
retAcl perms . ACL ,
grantTuples [] perms . GrantTuple ,
2022-04-19 11:42:54 -04:00
retErr error ,
) {
2021-02-11 13:32:45 -06:00
const op = "auth.(verifier).performAuthCheck"
2020-07-16 22:12:26 -04:00
// Ensure we return an error by default if we forget to set this somewhere
2021-10-28 21:14:39 -04:00
retErr = errors . New ( ctx , errors . Unknown , op , "default auth error" , errors . WithoutEvent ())
2020-07-22 00:14:28 -04:00
// Make the linter happy
_ = retErr
scopeInfo = new ( scopes . ScopeInfo )
2021-04-13 16:42:25 -07:00
userId = AnonymousUserId
2020-07-16 22:12:26 -04:00
// Validate the token and fetch the corresponding user ID
2020-08-20 13:34:03 -04:00
switch v . requestInfo . TokenFormat {
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeUnknown ):
2020-09-14 12:30:11 -04:00
// Nothing; remain as the anonymous user
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeRecoveryKms ):
2020-09-14 12:30:11 -04:00
// We validated the encrypted token in decryptToken and handled the
// nonces there, so just set the user
userId = "u_recovery"
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeBearer ), uint32 ( AuthTokenTypeSplitCookie ):
2020-08-26 23:28:38 -04:00
if v . requestInfo . Token == "" {
// This will end up staying as the anonymous user
break
}
2020-08-07 10:18:40 -07:00
tokenRepo , err := v . authTokenRepoFn ()
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-08-07 10:18:40 -07:00
return
}
at , err := tokenRepo . ValidateToken ( v . ctx , v . requestInfo . PublicId , v . requestInfo . Token )
if err != nil {
2020-09-08 23:57:26 -04:00
// Continue as the anonymous user as maybe this token is expired but
// we can still perform the action
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error validating token; continuing as anonymous user" ))
2020-09-08 23:57:26 -04:00
break
2020-08-07 10:18:40 -07:00
}
if at != nil {
2020-10-01 10:51:54 -04:00
accountId = at . GetAuthAccountId ()
2020-08-07 10:18:40 -07:00
userId = at . GetIamUserId ()
2020-09-26 13:58:48 -04:00
if userId == "" {
2021-07-28 06:31:16 -04:00
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 ()))
2021-04-13 16:42:25 -07:00
userId = AnonymousUserId
2020-10-12 10:15:38 -04:00
accountId = ""
2020-09-26 13:58:48 -04:00
}
2020-08-07 10:18:40 -07:00
}
2020-07-22 00:14:28 -04:00
}
2020-07-16 22:12:26 -04:00
iamRepo , err := v . iamRepoFn ()
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get iam repo" ))
return
}
u , _ , err := iamRepo . LookupUser ( ctx , userId )
if err != nil {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to lookup user" ))
2020-07-16 22:12:26 -04:00
return
}
2021-10-28 21:14:39 -04:00
userEmail = u . Email
userName = u . FullName
2020-07-22 00:14:28 -04:00
2020-08-26 23:28:38 -04:00
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch v . res . ScopeId {
2021-11-04 09:34:01 -04:00
case scope . Global . String ():
2020-08-26 23:28:38 -04:00
scopeInfo = & scopes . ScopeInfo {
Id : scope . Global . String (),
Type : scope . Global . String (),
Name : scope . Global . String (),
Description : "Global Scope" ,
ParentScopeId : "" ,
}
default :
scp , err := iamRepo . LookupScope ( v . ctx , v . res . ScopeId )
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-08-26 23:28:38 -04:00
return
}
if scp == nil {
2021-10-28 21:14:39 -04:00
retErr = errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprint ( "non-existent scope $q" , v . res . ScopeId ))
2020-08-26 23:28:38 -04:00
return
}
scopeInfo = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
2020-07-22 00:14:28 -04:00
}
2020-08-20 13:34:03 -04:00
// At this point we don't need to look up grants since it's automatically allowed
2021-10-19 17:21:41 -04:00
if v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ) {
2021-04-13 16:55:40 -05:00
aclResults . AuthenticationFinished = true
2021-03-24 15:33:04 -04:00
aclResults . Authorized = true
2020-08-20 13:34:03 -04:00
retErr = nil
return
}
2020-07-22 00:14:28 -04:00
var parsedGrants [] perms . Grant
2020-08-26 23:28:38 -04:00
// Fetch and parse grants for this user ID (which may include grants for
// u_anon and u_auth)
2021-06-07 09:19:21 -05:00
grantTuples , err = iamRepo . GrantsForUser ( v . ctx , userId )
2020-07-16 22:12:26 -04:00
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-07-16 22:12:26 -04:00
return
}
2021-06-07 09:19:21 -05:00
parsedGrants = make ([] perms . Grant , 0 , len ( grantTuples ))
2021-02-05 12:39:55 -05:00
// Note: Below, we always skip validation so that we don't error on formats
// that we've since restricted, e.g. "id=foo;actions=create,read". These
// will simply not have an effect.
2021-06-07 09:19:21 -05:00
for _ , pair := range grantTuples {
2020-10-01 10:51:54 -04:00
parsed , err := perms . Parse (
pair . ScopeId ,
pair . Grant ,
perms . WithUserId ( userId ),
perms . WithAccountId ( accountId ),
perms . WithSkipFinalValidation ( true ))
2020-07-16 22:12:26 -04:00
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed to parse grant %#v" , pair . Grant )))
2020-07-16 22:12:26 -04:00
return
}
parsedGrants = append ( parsedGrants , parsed )
}
2020-09-23 23:39:44 -04:00
retAcl = perms . NewACL ( parsedGrants ... )
aclResults = retAcl . Allowed ( * v . res , v . act )
2021-03-24 15:33:04 -04:00
// 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
// grants successfully loaded.
2021-04-13 16:55:40 -05:00
aclResults . AuthenticationFinished = true
2020-07-16 22:12:26 -04:00
retErr = nil
return
}
2020-07-22 00:14:28 -04:00
2021-01-20 10:53:01 -05:00
// FetchActionSetForId returns the allowed actions for a given ID using the
// current set of ACLs and all other parameters the same (user, etc.)
func ( r * VerifyResults ) FetchActionSetForId ( ctx context . Context , id string , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-05-04 19:57:35 -05:00
return r . fetchActions ( id , resource . Unknown , availableActions , opt ... )
2021-01-20 10:53:01 -05:00
}
// FetchActionSetForType returns the allowed actions for a given collection type
// using the current set of ACLs and all other parameters the same (user, etc.)
func ( r * VerifyResults ) FetchActionSetForType ( ctx context . Context , typ resource . Type , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-05-04 19:57:35 -05:00
return r . fetchActions ( "" , typ , availableActions , opt ... )
2021-01-20 10:53:01 -05:00
}
2021-05-04 19:57:35 -05:00
func ( r * VerifyResults ) fetchActions ( id string , typ resource . Type , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-01-13 14:42:37 -05:00
switch {
case r . v . requestInfo . DisableAuthEntirely ,
2021-10-19 17:21:41 -04:00
r . v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ):
2021-01-13 14:42:37 -05:00
// TODO: See if we can be better about what we return with the anonymous
// user and recovery KMS, perhaps given exclusionary options for each
return availableActions
}
opts := getOpts ( opt ... )
res := opts . withResource
2021-03-24 15:33:04 -04:00
// If not passed in, use what's already been populated through verification
2021-01-13 14:42:37 -05:00
if res == nil {
res = r . v . res
}
2021-03-24 15:33:04 -04:00
// If this is being called directly we may not have a resource yet
2021-01-13 14:42:37 -05:00
if res == nil {
res = new ( perms . Resource )
}
2021-01-20 10:53:01 -05:00
if id != "" {
res . Id = id
}
if typ != resource . Unknown {
res . Type = typ
}
2021-01-13 14:42:37 -05:00
2021-01-20 10:53:01 -05:00
ret := make ( action . ActionSet , 0 , len ( availableActions ))
2021-01-13 14:42:37 -05:00
for _ , act := range availableActions {
2021-03-24 15:33:04 -04:00
if r . v . acl . Allowed ( * res , act ). Authorized {
2021-01-13 14:42:37 -05:00
ret = append ( ret , act )
}
}
if len ( ret ) == 0 {
return nil
}
return ret
}
2021-05-04 19:57:35 -05:00
func ( r * VerifyResults ) FetchOutputFields ( res perms . Resource , act action . Type ) perms . OutputFieldsMap {
switch {
2021-10-19 17:21:41 -04:00
case r . v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ):
2021-05-04 19:57:35 -05:00
return perms . OutputFieldsMap { "*" : true }
case r . v . requestInfo . DisableAuthEntirely :
return nil
}
return r . v . acl . Allowed ( res , act ). OutputFields
}
2020-07-22 15:10:44 -04:00
// GetTokenFromRequest pulls the token from either the Authorization header or
2020-07-22 00:14:28 -04:00
// split cookies and parses it. If it cannot be parsed successfully, the issue
// is logged and we return blank, so logic will continue as the anonymous user.
2020-09-14 12:30:11 -04:00
// The public ID and _encrypted_ token are returned along with the token format.
2021-10-19 17:21:41 -04:00
func GetTokenFromRequest ( ctx context . Context , kmsCache * kms . Kms , req * http . Request ) ( string , string , uint32 ) {
2021-07-28 06:31:16 -04:00
const op = "auth.GetTokenFromRequest"
2020-07-22 00:14:28 -04:00
// First, get the token, either from the authorization header or from split
// cookies
var receivedTokenType TokenFormat
var fullToken string
if authHeader := req . Header . Get ( "Authorization" ); authHeader != "" {
headerSplit := strings . SplitN ( strings . TrimSpace ( authHeader ), " " , 2 )
if len ( headerSplit ) == 2 && strings . EqualFold ( strings . TrimSpace ( headerSplit [ 0 ]), "bearer" ) {
receivedTokenType = AuthTokenTypeBearer
fullToken = strings . TrimSpace ( headerSplit [ 1 ])
}
}
if receivedTokenType != AuthTokenTypeBearer {
var httpCookiePayload string
var jsCookiePayload string
2020-09-01 09:23:41 -07:00
if hc , err := req . Cookie ( handlers . HttpOnlyCookieName ); err == nil {
2020-07-22 00:14:28 -04:00
httpCookiePayload = hc . Value
}
2020-09-01 09:23:41 -07:00
if jc , err := req . Cookie ( handlers . JsVisibleCookieName ); err == nil {
2020-07-22 00:14:28 -04:00
jsCookiePayload = jc . Value
}
if httpCookiePayload != "" && jsCookiePayload != "" {
receivedTokenType = AuthTokenTypeSplitCookie
fullToken = jsCookiePayload + httpCookiePayload
}
}
if receivedTokenType == AuthTokenTypeUnknown || fullToken == "" {
// We didn't find auth info or a client screwed up and put in a blank
// header instead of nothing at all, so return blank which will indicate
// the anonymouse user
2021-10-19 17:21:41 -04:00
return "" , "" , uint32 ( AuthTokenTypeUnknown )
2020-07-22 00:14:28 -04:00
}
2020-08-20 13:34:03 -04:00
if strings . HasPrefix ( fullToken , "r_" ) {
2021-10-19 17:21:41 -04:00
return "" , fullToken , uint32 ( AuthTokenTypeRecoveryKms )
2020-08-20 13:34:03 -04:00
}
2020-07-22 00:14:28 -04:00
splitFullToken := strings . Split ( fullToken , "_" )
if len ( splitFullToken ) != 3 {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "unexpected number of segments in token; continuing as anonymous user" ), event . WithInfo ( "expected" , 3 , "found" , len ( splitFullToken )))
2021-10-19 17:21:41 -04:00
return "" , "" , uint32 ( AuthTokenTypeUnknown )
2020-07-22 00:14:28 -04:00
}
publicId := strings . Join ( splitFullToken [ 0 : 2 ], "_" )
2020-09-14 12:30:11 -04:00
encryptedToken := splitFullToken [ 2 ]
2020-07-22 00:14:28 -04:00
2021-10-19 17:21:41 -04:00
return publicId , encryptedToken , uint32 ( receivedTokenType )
2020-09-14 12:30:11 -04:00
}