Use scope-specific token DEKs (#342)
This commit is contained in:
1 parent
67278af339
commit
6201357902
11 files changed
+366
-108
No files matched your search
+145
-85
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
@@ -46,20 +47,16 @@ var verifierKey key
|
||||
|
||||
// RequestInfo contains request parameters necessary for checking authn/authz
|
||||
type RequestInfo struct {
|
||||
Path string
|
||||
Method string
|
||||
PublicId string
|
||||
Token string
|
||||
TokenFormat TokenFormat
|
||||
|
||||
// These are set by the service handlers via options
|
||||
scopeId string
|
||||
pin string
|
||||
|
||||
// This is used by the scopes collection
|
||||
userIdOverride string
|
||||
Path string
|
||||
Method string
|
||||
PublicId string
|
||||
EncryptedToken string
|
||||
Token string
|
||||
TokenFormat TokenFormat
|
||||
|
||||
// The following are useful for tests
|
||||
scopeIdOverride string
|
||||
userIdOverride string
|
||||
DisableAuthzFailures bool
|
||||
DisableAuthEntirely bool
|
||||
}
|
||||
@@ -122,7 +119,7 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
|
||||
ret.Scope = new(scopes.ScopeInfo)
|
||||
if v.requestInfo.DisableAuthEntirely {
|
||||
ret.Scope.Id = v.requestInfo.scopeId
|
||||
ret.Scope.Id = v.requestInfo.scopeIdOverride
|
||||
if ret.Scope.Id == "" {
|
||||
ret.Scope.Id = opts.withScopeId
|
||||
}
|
||||
@@ -147,6 +144,10 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
Type: opts.withType,
|
||||
}
|
||||
|
||||
if v.requestInfo.EncryptedToken != "" {
|
||||
v.decryptToken()
|
||||
}
|
||||
|
||||
var authResults *perms.ACLResults
|
||||
var err error
|
||||
authResults, ret.UserId, ret.Scope, err = v.performAuthCheck()
|
||||
@@ -184,6 +185,14 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
|
||||
|
||||
// Validate the token and fetch the corresponding user ID
|
||||
switch v.requestInfo.TokenFormat {
|
||||
case AuthTokenTypeUnknown:
|
||||
// Nothing; remain as the anonymous user
|
||||
|
||||
case AuthTokenTypeRecoveryKms:
|
||||
// We validated the encrypted token in decryptToken and handled the
|
||||
// nonces there, so just set the user
|
||||
userId = "u_recovery"
|
||||
|
||||
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
|
||||
if v.requestInfo.Token == "" {
|
||||
// This will end up staying as the anonymous user
|
||||
@@ -204,40 +213,6 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
|
||||
if at != nil {
|
||||
userId = at.GetIamUserId()
|
||||
}
|
||||
|
||||
case AuthTokenTypeRecoveryKms:
|
||||
userId = "u_recovery"
|
||||
if v.kms == nil {
|
||||
retErr = errors.New("perform auth check: no KMS object available to authz system")
|
||||
return
|
||||
}
|
||||
wrapper := v.kms.GetExternalWrappers().Recovery()
|
||||
if wrapper == nil {
|
||||
retErr = errors.New("perform auth check: no recovery KMS is available")
|
||||
return
|
||||
}
|
||||
info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.Token)
|
||||
if err != nil {
|
||||
retErr = fmt.Errorf("perform auth check: error validating recovery token: %w", err)
|
||||
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()) {
|
||||
retErr = errors.New("WARNING: perform auth check: recovery token has expired (possible replay attack)")
|
||||
return
|
||||
}
|
||||
repo, err := v.serversRepoFn()
|
||||
if err != nil {
|
||||
retErr = fmt.Errorf("perform auth check: error fetching servers repo: %w", err)
|
||||
return
|
||||
}
|
||||
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
|
||||
retErr = fmt.Errorf("WARNING: perform auth check: error adding nonce to database (possible replay attack): %w", err)
|
||||
return
|
||||
}
|
||||
v.logger.Warn("NOTE: recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method)
|
||||
}
|
||||
|
||||
iamRepo, err := v.iamRepoFn()
|
||||
@@ -315,7 +290,7 @@ func (v verifier) performAuthCheck() (aclResults *perms.ACLResults, userId strin
|
||||
// GetTokenFromRequest pulls the token from either the Authorization header or
|
||||
// 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.
|
||||
// The public ID and token are returned along with the token format.
|
||||
// The public ID and _encrypted_ token are returned along with the token format.
|
||||
func GetTokenFromRequest(logger hclog.Logger, kmsCache *kms.Kms, req *http.Request) (string, string, TokenFormat) {
|
||||
// First, get the token, either from the authorization header or from split
|
||||
// cookies
|
||||
@@ -361,42 +336,127 @@ func GetTokenFromRequest(logger hclog.Logger, kmsCache *kms.Kms, req *http.Reque
|
||||
}
|
||||
|
||||
publicId := strings.Join(splitFullToken[0:2], "_")
|
||||
encryptedToken := splitFullToken[2]
|
||||
|
||||
// This is hardcoded to global because at this point we don't know the
|
||||
// scope. But that's okay; this is not really a security feature so much as
|
||||
// an anti-DDoSing-the-backing-database feature.
|
||||
tokenWrapper, err := kmsCache.GetWrapper(req.Context(), scope.Global.String(), kms.KeyPurposeTokens)
|
||||
if err != nil {
|
||||
logger.Warn("get token from request: unable to get wrapper for tokens; continuing as anonymous user", "error", err)
|
||||
return "", "", AuthTokenTypeUnknown
|
||||
}
|
||||
|
||||
version := splitFullToken[2][0:len(globals.ServiceTokenV1)]
|
||||
switch version {
|
||||
case globals.ServiceTokenV1:
|
||||
default:
|
||||
logger.Trace("get token from request: unknown token encryption version; continuing as anonymous user", "version", version)
|
||||
return "", "", AuthTokenTypeUnknown
|
||||
}
|
||||
marshaledToken := base58.Decode(splitFullToken[2][len(globals.ServiceTokenV1):])
|
||||
|
||||
blobInfo := new(wrapping.EncryptedBlobInfo)
|
||||
if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil {
|
||||
logger.Trace("get token from request: error decoding encrypted token; continuing as anonymous user", "error", err)
|
||||
return "", "", AuthTokenTypeUnknown
|
||||
}
|
||||
|
||||
tokenBytes, err := tokenWrapper.Decrypt(req.Context(), blobInfo, []byte(publicId))
|
||||
if err != nil {
|
||||
logger.Trace("get token from request: error decrypting encrypted token; continuing as anonymous user", "error", err)
|
||||
return "", "", AuthTokenTypeUnknown
|
||||
}
|
||||
token := string(tokenBytes)
|
||||
|
||||
if receivedTokenType == AuthTokenTypeUnknown || token == "" || publicId == "" {
|
||||
logger.Trace("get token from request: after parsing, could not find valid token; continuing as anonymous user")
|
||||
return "", "", AuthTokenTypeUnknown
|
||||
}
|
||||
|
||||
return publicId, token, receivedTokenType
|
||||
return publicId, encryptedToken, receivedTokenType
|
||||
}
|
||||
|
||||
func (v *verifier) decryptToken() {
|
||||
switch v.requestInfo.TokenFormat {
|
||||
case AuthTokenTypeUnknown:
|
||||
// Nothing to decrypt
|
||||
return
|
||||
|
||||
case AuthTokenTypeBearer, AuthTokenTypeSplitCookie:
|
||||
if v.kms == nil {
|
||||
v.logger.Trace("decrypt token: no KMS object available to authz system")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
tokenRepo, err := v.authTokenRepoFn()
|
||||
if err != nil {
|
||||
v.logger.Warn("decrypt bearer token: failed to get authtoken repo", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
at, err := tokenRepo.LookupAuthToken(v.ctx, v.requestInfo.PublicId)
|
||||
if err != nil {
|
||||
v.logger.Trace("decrypt bearer token: failed to look up auth token by public ID", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
if at == nil {
|
||||
v.logger.Trace("decrypt bearer token: nil result from looking up auth token by public ID")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
tokenWrapper, err := v.kms.GetWrapper(v.ctx, at.GetScopeId(), kms.KeyPurposeTokens)
|
||||
if err != nil {
|
||||
v.logger.Warn("decrypt bearer token: unable to get wrapper for tokens; continuing as anonymous user", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
version := v.requestInfo.EncryptedToken[0:len(globals.ServiceTokenV1)]
|
||||
switch version {
|
||||
case globals.ServiceTokenV1:
|
||||
default:
|
||||
v.logger.Trace("decrypt bearer token: unknown token encryption version; continuing as anonymous user", "version", version)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
marshaledToken := base58.Decode(v.requestInfo.EncryptedToken[len(globals.ServiceTokenV1):])
|
||||
|
||||
blobInfo := new(wrapping.EncryptedBlobInfo)
|
||||
if err := proto.Unmarshal(marshaledToken, blobInfo); err != nil {
|
||||
v.logger.Trace("decrypt bearer token: error decoding encrypted token; continuing as anonymous user", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
s1Bytes, err := tokenWrapper.Decrypt(v.ctx, blobInfo, []byte(v.requestInfo.PublicId))
|
||||
if err != nil {
|
||||
v.logger.Trace("decrypt bearer token: error decrypting encrypted token; continuing as anonymous user", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
var s1Info tokens.S1TokenInfo
|
||||
if err := proto.Unmarshal(s1Bytes, &s1Info); err != nil {
|
||||
v.logger.Trace("decrypt bearer token: error unmarshaling token info; continuing as anonymous user", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
if v.requestInfo.TokenFormat == AuthTokenTypeUnknown || s1Info.Token == "" || v.requestInfo.PublicId == "" {
|
||||
v.logger.Trace("decrypt bearer token: after parsing, could not find valid token; continuing as anonymous user")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
|
||||
v.requestInfo.Token = s1Info.Token
|
||||
return
|
||||
|
||||
case AuthTokenTypeRecoveryKms:
|
||||
if v.kms == nil {
|
||||
v.logger.Trace("decrypt recovery token: no KMS object available to authz system")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
wrapper := v.kms.GetExternalWrappers().Recovery()
|
||||
if wrapper == nil {
|
||||
v.logger.Trace("decrypt recovery token: no recovery KMS is available")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
info, err := recovery.ParseRecoveryToken(v.ctx, wrapper, v.requestInfo.EncryptedToken)
|
||||
if err != nil {
|
||||
v.logger.Trace("decrypt recovery token: error parsing and validating recovery token", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
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()) {
|
||||
v.logger.Warn("WARNING: decrypt recovery token: recovery token has expired (possible replay attack)")
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
repo, err := v.serversRepoFn()
|
||||
if err != nil {
|
||||
v.logger.Trace("decrypt recovery token: error fetching servers repo", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
|
||||
v.logger.Warn("WARNING: decrypt recovery token: error adding nonce to database (possible replay attack)", "error", err)
|
||||
v.requestInfo.TokenFormat = AuthTokenTypeUnknown
|
||||
return
|
||||
}
|
||||
v.logger.Info("NOTE: recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method)
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func TestAuthTokenAuthenticator(t *testing.T) {
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
|
||||
encToken, err := authtoken.EncryptToken(context.Background(), kms, at.GetPublicId(), at.GetToken())
|
||||
encToken, err := authtoken.EncryptToken(context.Background(), kms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
|
||||
require.NoError(t, err)
|
||||
|
||||
tokValue := at.GetPublicId() + "_" + encToken
|
||||
@@ -113,7 +113,7 @@ func TestAuthTokenAuthenticator(t *testing.T) {
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
}
|
||||
requestInfo.PublicId, requestInfo.Token, requestInfo.TokenFormat = GetTokenFromRequest(logger, kms, req)
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(logger, kms, req)
|
||||
assert.Equal(t, tc.tokenFormat, requestInfo.TokenFormat)
|
||||
|
||||
if tc.userId == "" {
|
||||
@@ -125,6 +125,8 @@ func TestAuthTokenAuthenticator(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, v)
|
||||
|
||||
v.decryptToken()
|
||||
|
||||
at, err := tokenRepo.ValidateToken(ctx, v.requestInfo.PublicId, v.requestInfo.Token)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.userId, at.GetIamUserId())
|
||||
|
||||
@@ -6,8 +6,7 @@ import "context"
|
||||
func DisabledAuthTestContext(opt ...Option) context.Context {
|
||||
reqInfo := RequestInfo{DisableAuthEntirely: true}
|
||||
opts := getOpts(opt...)
|
||||
reqInfo.scopeId = opts.withScopeId
|
||||
reqInfo.pin = opts.withPin
|
||||
reqInfo.scopeIdOverride = opts.withScopeId
|
||||
reqInfo.userIdOverride = opts.withUserId
|
||||
return NewVerifierContext(context.Background(), nil, nil, nil, nil, opts.withKms, reqInfo)
|
||||
}
|
||||
@@ -3,13 +3,15 @@ package authtoken
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/authtoken/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/go-kms-wrapping/structwrapping"
|
||||
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||
@@ -101,17 +103,27 @@ func newAuthToken() (string, error) {
|
||||
}
|
||||
|
||||
// EncryptToken is a shared function for encrypting a token value for return to
|
||||
// the user. We always use the global scope because on authenticate we don't
|
||||
// have scope info at this point and the idea is to remove a DB lookup if the
|
||||
// token is made up/invalid so as to prevent DDoS against a third party service
|
||||
// by just randomly guessing tokens.
|
||||
func EncryptToken(ctx context.Context, kmsCache *kms.Kms, publicId, token string) (string, error) {
|
||||
tokenWrapper, err := kmsCache.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeTokens)
|
||||
// the user.
|
||||
func EncryptToken(ctx context.Context, kmsCache *kms.Kms, scopeId, publicId, token string) (string, error) {
|
||||
r := mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
s1Info := &tokens.S1TokenInfo{
|
||||
Token: token,
|
||||
Confounder: make([]byte, r.Intn(30)),
|
||||
}
|
||||
r.Read(s1Info.Confounder)
|
||||
|
||||
marshaledS1Info, err := proto.Marshal(s1Info)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error marshaling token info: %w", err)
|
||||
}
|
||||
|
||||
tokenWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeTokens)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get wrapper: %w", err)
|
||||
}
|
||||
|
||||
blobInfo, err := tokenWrapper.Encrypt(ctx, []byte(token), []byte(publicId))
|
||||
blobInfo, err := tokenWrapper.Encrypt(ctx, []byte(marshaledS1Info), []byte(publicId))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error encrypting token: %w", err)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func (c *Command) Run(args []string) int {
|
||||
|
||||
case "table":
|
||||
if len(listedScopes) == 0 {
|
||||
c.UI.Output("No scopes found")
|
||||
c.UI.Output("No child scopes found")
|
||||
return 0
|
||||
}
|
||||
var output []string
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.12.4
|
||||
// source: controller/tokens/v1/tokens.proto
|
||||
|
||||
package tokens
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type S1TokenInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Token string `protobuf:"bytes,10,opt,name=token,proto3" json:"token,omitempty"`
|
||||
Confounder []byte `protobuf:"bytes,20,opt,name=confounder,proto3" json:"confounder,omitempty"`
|
||||
}
|
||||
|
||||
func (x *S1TokenInfo) Reset() {
|
||||
*x = S1TokenInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_tokens_v1_tokens_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *S1TokenInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*S1TokenInfo) ProtoMessage() {}
|
||||
|
||||
func (x *S1TokenInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_tokens_v1_tokens_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use S1TokenInfo.ProtoReflect.Descriptor instead.
|
||||
func (*S1TokenInfo) Descriptor() ([]byte, []int) {
|
||||
return file_controller_tokens_v1_tokens_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *S1TokenInfo) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *S1TokenInfo) GetConfounder() []byte {
|
||||
if x != nil {
|
||||
return x.Confounder
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_controller_tokens_v1_tokens_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_tokens_v1_tokens_proto_rawDesc = []byte{
|
||||
0x0a, 0x21, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x14, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x43, 0x0a, 0x0b, 0x53, 0x31, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e,
|
||||
0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x42, 0x45,
|
||||
0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73,
|
||||
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x3b, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_controller_tokens_v1_tokens_proto_rawDescOnce sync.Once
|
||||
file_controller_tokens_v1_tokens_proto_rawDescData = file_controller_tokens_v1_tokens_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_controller_tokens_v1_tokens_proto_rawDescGZIP() []byte {
|
||||
file_controller_tokens_v1_tokens_proto_rawDescOnce.Do(func() {
|
||||
file_controller_tokens_v1_tokens_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_tokens_v1_tokens_proto_rawDescData)
|
||||
})
|
||||
return file_controller_tokens_v1_tokens_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_tokens_v1_tokens_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_controller_tokens_v1_tokens_proto_goTypes = []interface{}{
|
||||
(*S1TokenInfo)(nil), // 0: controller.tokens.v1.S1TokenInfo
|
||||
}
|
||||
var file_controller_tokens_v1_tokens_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_tokens_v1_tokens_proto_init() }
|
||||
func file_controller_tokens_v1_tokens_proto_init() {
|
||||
if File_controller_tokens_v1_tokens_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_controller_tokens_v1_tokens_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*S1TokenInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_tokens_v1_tokens_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_controller_tokens_v1_tokens_proto_goTypes,
|
||||
DependencyIndexes: file_controller_tokens_v1_tokens_proto_depIdxs,
|
||||
MessageInfos: file_controller_tokens_v1_tokens_proto_msgTypes,
|
||||
}.Build()
|
||||
File_controller_tokens_v1_tokens_proto = out.File
|
||||
file_controller_tokens_v1_tokens_proto_rawDesc = nil
|
||||
file_controller_tokens_v1_tokens_proto_goTypes = nil
|
||||
file_controller_tokens_v1_tokens_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package controller.tokens.v1;
|
||||
|
||||
option go_package = "github.com/hashicorp/boundary/internal/gen/controller/tokens;tokens";
|
||||
|
||||
message S1TokenInfo {
|
||||
string token = 10;
|
||||
bytes confounder = 20;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func TestHandler_CORS(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
org := iam.TestOrg(t, repo)
|
||||
|
||||
encToken, err := authtoken.EncryptToken(context.Background(), tc.c.kms, "fo_o", "bar")
|
||||
encToken, err := authtoken.EncryptToken(context.Background(), tc.c.kms, org.GetPublicId(), "fo_o", "bar")
|
||||
require.NoError(t, err)
|
||||
token := "fo_o_" + encToken
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
|
||||
DisableAuthzFailures: disableAuthzFailures,
|
||||
}
|
||||
|
||||
requestInfo.PublicId, requestInfo.Token, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(c.logger, c.kms, r)
|
||||
ctx = auth.NewVerifierContext(ctx, c.logger, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, requestInfo)
|
||||
|
||||
// Set the context back on the request
|
||||
|
||||
@@ -111,7 +111,7 @@ func (s Service) authenticateWithRepo(ctx context.Context, scopeId, authMethodId
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := authtoken.EncryptToken(ctx, s.kms, tok.GetPublicId(), tok.GetToken())
|
||||
token, err := authtoken.EncryptToken(ctx, s.kms, scopeId, tok.GetPublicId(), tok.GetToken())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const nonceLength = 32
|
||||
const (
|
||||
nonceLength = 32
|
||||
v1String = "1"
|
||||
)
|
||||
|
||||
// FutureLeeway indicates how far in the future we should allow the creation
|
||||
// time of the token to be, in order to account for clock drift
|
||||
@@ -58,18 +61,28 @@ func formatToken(ctx context.Context, wrapper wrapping.Wrapper, info *Info) (str
|
||||
return "", fmt.Errorf("error marshaling encrypted blob: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("r_%s", base58.Encode(marshaledBlob)), nil
|
||||
return fmt.Sprintf("r_%s%s", v1String, base58.Encode(marshaledBlob)), nil
|
||||
}
|
||||
|
||||
func ParseRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper, token string) (*Info, error) {
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
func ParseRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper, versionedToken string) (*Info, error) {
|
||||
versionedToken = strings.TrimSpace(versionedToken)
|
||||
if versionedToken == "" {
|
||||
return nil, errors.New("empty token")
|
||||
}
|
||||
if !strings.HasPrefix(token, "r_") {
|
||||
if !strings.HasPrefix(versionedToken, "r_") {
|
||||
return nil, errors.New("token has wrong format")
|
||||
}
|
||||
token = strings.TrimPrefix(token, "r_")
|
||||
versionedToken = strings.TrimPrefix(versionedToken, "r_")
|
||||
|
||||
ver, token := versionedToken[0:1], versionedToken[1:]
|
||||
|
||||
// Don't do anything yet, but if we ever need to adjust data based on
|
||||
// version we'd do it here
|
||||
switch ver {
|
||||
case v1String:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown recovery token version %s", ver)
|
||||
}
|
||||
|
||||
marshaledBlob := base58.Decode(token)
|
||||
if len(marshaledBlob) == 0 {
|
||||
|
||||
Reference in new issue
Block a user