Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSCT Tokens Feature [OSS] #14109

Merged
merged 9 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ proto: bootstrap
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative sdk/database/dbplugin/*.proto
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative sdk/database/dbplugin/v5/proto/*.proto
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative sdk/plugin/pb/*.proto
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative vault/tokens/token.proto

# No additional sed expressions should be added to this list. Going forward
# we should just use the variable names choosen by protobuf. These are left
Expand Down
3 changes: 3 additions & 0 deletions changelog/14109.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Server Side Consistent Tokens: Service tokens now use SSC token format and token prefixes are updated."
```
4 changes: 4 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ const (
// path to a license file on disk
EnvVaultLicensePath = "VAULT_LICENSE_PATH"

// DisableSSCTokens is an env var used to disable index bearing
// token functionality
DisableSSCTokens = "VAULT_DISABLE_SERVER_SIDE_CONSISTENT_TOKENS"

// flagNameAddress is the flag used in the base command to read in the
// address of the Vault server.
flagNameAddress = "address"
Expand Down
9 changes: 7 additions & 2 deletions command/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"github.com/hashicorp/vault/vault"
)

// minTokenLengthExternal is the minimum size of SSC
// tokens we are currently handing out to end users, without any
// namespace information
const minTokenLengthExternal = 91

func testLoginCommand(tb testing.TB) (*cli.MockUi, *LoginCommand) {
tb.Helper()

Expand Down Expand Up @@ -82,7 +87,7 @@ func TestLoginCommand_Run(t *testing.T) {
t.Fatal(err)
}

if l, exp := len(storedToken), vault.TokenLength+2; l != exp {
if l, exp := len(storedToken), minTokenLengthExternal+vault.TokenPrefixLength; l != exp {
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved
t.Errorf("expected token to be %d characters, was %d: %q", exp, l, storedToken)
}
})
Expand Down Expand Up @@ -209,7 +214,7 @@ func TestLoginCommand_Run(t *testing.T) {

// Verify only the token was printed
token := ui.OutputWriter.String()
if l, exp := len(token), vault.TokenLength+2; l != exp {
if l, exp := len(token), minTokenLengthExternal+vault.TokenPrefixLength; l != exp {
t.Errorf("expected token to be %d characters, was %d: %q", exp, l, token)
}

Expand Down
4 changes: 2 additions & 2 deletions command/operator_generate_root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Fatal(err)
}

if l, exp := len(token), vault.TokenLength+2; l != exp {
if l, exp := len(token), vault.TokenLength+vault.TokenPrefixLength; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})
Expand Down Expand Up @@ -521,7 +521,7 @@ func TestOperatorGenerateRootCommand_Run(t *testing.T) {
t.Fatal(err)
}

if l, exp := len(token), vault.TokenLength+2; l != exp {
if l, exp := len(token), vault.TokenLength+vault.TokenPrefixLength; l != exp {
t.Errorf("expected %d to be %d: %s", l, exp, token)
}
})
Expand Down
2 changes: 1 addition & 1 deletion command/operator_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ func TestOperatorInitCommand_Run(t *testing.T) {
root := match[0][1]
decryptedRoot := testPGPDecrypt(t, pgpkeys.TestPrivKey1, root)

if l, exp := len(decryptedRoot), vault.TokenLength+2; l != exp {
if l, exp := len(decryptedRoot), vault.TokenLength+vault.TokenPrefixLength; l != exp {
t.Errorf("expected %d to be %d", l, exp)
}
})
Expand Down
11 changes: 11 additions & 0 deletions command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,15 @@ func (c *ServerCommand) Run(args []string) int {
if envLicense := os.Getenv(EnvVaultLicense); envLicense != "" {
config.License = envLicense
}
if disableSSC := os.Getenv(DisableSSCTokens); disableSSC != "" {
var err error
config.DisableSSCTokens, err = strconv.ParseBool(disableSSC)
if err != nil {
c.UI.Warn(wrapAtLength("WARNING! failed to parse " +
"VAULT_DISABLE_SERVER_SIDE_CONSISTENT_TOKENS env var: " +
"setting to default value false"))
}
}

// If mlockall(2) isn't supported, show a warning. We disable this in dev
// because it is quite scary to see when first using Vault. We also disable
Expand Down Expand Up @@ -2484,6 +2493,8 @@ func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.
EnableResponseHeaderRaftNodeID: config.EnableResponseHeaderRaftNodeID,
License: config.License,
LicensePath: config.LicensePath,
DisableSSCTokens: config.DisableSSCTokens,
ForwardToActive: config.ForwardToActive,
}
if c.flagDev {
coreConfig.EnableRaw = true
Expand Down
6 changes: 4 additions & 2 deletions command/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ type Config struct {
EnableResponseHeaderRaftNodeID bool `hcl:"-"`
EnableResponseHeaderRaftNodeIDRaw interface{} `hcl:"enable_response_header_raft_node_id"`

License string `hcl:"-"`
LicensePath string `hcl:"license_path"`
License string `hcl:"-"`
LicensePath string `hcl:"license_path"`
DisableSSCTokens bool `hcl:"-"`
ncabatoff marked this conversation as resolved.
Show resolved Hide resolved
ForwardToActive string `hcl:"forward_to_active"`
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved
}

const (
Expand Down
8 changes: 8 additions & 0 deletions helper/namespace/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"strings"

"github.com/hashicorp/vault/sdk/helper/consts"
)

type contextValues struct{}
Expand Down Expand Up @@ -105,6 +107,12 @@ func SplitIDFromString(input string) (string, string) {
case strings.HasPrefix(input, "s."):
prefix = "s."
input = input[2:]
case strings.HasPrefix(input, consts.BatchTokenPrefix):
prefix = consts.BatchTokenPrefix
input = input[4:]
case strings.HasPrefix(input, consts.ServiceTokenPrefix):
prefix = consts.ServiceTokenPrefix
input = input[4:]

case slashIdx > 0:
// Leases will never have a b./s. to start
Expand Down
1 change: 1 addition & 0 deletions http/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ func TestHandler_error(t *testing.T) {

func TestHandler_requestAuth(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
token, _ = core.DecodeSSCToken(token)
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved

rootCtx := namespace.RootContext(nil)
te, err := core.LookupToken(rootCtx, token)
Expand Down
14 changes: 12 additions & 2 deletions http/sys_generate_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,20 @@ func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r
respondError(w, http.StatusInternalServerError, err)
return
}
var otpLength int
if core.DisableSSCTokens() {
otpLength = vault.TokenLength + vault.OldTokenPrefixLength
} else {
otpLength = vault.TokenLength + vault.TokenPrefixLength
}

// Format the status
status := &GenerateRootStatusResponse{
Started: false,
Progress: progress,
Required: sealConfig.SecretThreshold,
Complete: false,
OTPLength: vault.TokenLength + 2,
OTPLength: otpLength,
OTP: otp,
}
if generationConfig != nil {
Expand All @@ -98,7 +104,11 @@ func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r
case len(req.PGPKey) > 0, len(req.OTP) > 0:
default:
genned = true
req.OTP, err = base62.Random(vault.TokenLength + 2)
if core.DisableSSCTokens() {
req.OTP, err = base62.Random(vault.TokenLength + vault.OldTokenPrefixLength)
} else {
req.OTP, err = base62.Random(vault.TokenLength + vault.TokenPrefixLength)
}
if err != nil {
respondError(w, http.StatusInternalServerError, err)
return
Expand Down
14 changes: 8 additions & 6 deletions http/sys_generate_root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/hashicorp/vault/vault"
)

const tokenLength string = "28"
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved

func TestSysGenerateRootAttempt_Status(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
Expand All @@ -40,7 +42,7 @@ func TestSysGenerateRootAttempt_Status(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "",
"nonce": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand Down Expand Up @@ -68,7 +70,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
"encoded_token": "",
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand All @@ -93,7 +95,7 @@ func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand Down Expand Up @@ -129,7 +131,7 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
"encoded_root_token": "",
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
"otp": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand Down Expand Up @@ -159,7 +161,7 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
"encoded_token": "",
"encoded_root_token": "",
"pgp_fingerprint": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand Down Expand Up @@ -191,7 +193,7 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
"pgp_fingerprint": "",
"nonce": "",
"otp": "",
"otp_length": json.Number("26"),
"otp_length": json.Number(tokenLength),
}
testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual)
Expand Down
10 changes: 10 additions & 0 deletions sdk/helper/consts/token_consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package consts

const (
ServiceTokenPrefix = "hvs."
BatchTokenPrefix = "hvb."
RecoveryTokenPrefix = "hvr."
LegacyServiceTokenPrefix = "s."
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved
LegacyBatchTokenPrefix = "b."
LegacyRecoveryTokenPrefix = "r."
)
4 changes: 4 additions & 0 deletions sdk/logical/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ type Request struct {
// this will be the sha256(sorted policies + namespace) associated with the
// client token.
ClientID string `json:"client_id" structs:"client_id" mapstructure:"client_id" sentinel:""`

// RequestSSCToken is the token that arrives on an inbound request, supplied
// by the vault user.
RequestSSCToken string
HridoyRoy marked this conversation as resolved.
Show resolved Hide resolved
}

// Clone returns a deep copy of the request by using copystructure
Expand Down
4 changes: 4 additions & 0 deletions sdk/logical/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ type TokenEntry struct {
// ID of this entry, generally a random UUID
ID string `json:"id" mapstructure:"id" structs:"id" sentinel:""`

// ExternalID is the ID of a newly created service
// token that will be returned to a user
ExternalID string `json:"-"`

// Accessor for this token, a random UUID
Accessor string `json:"accessor" mapstructure:"accessor" structs:"accessor" sentinel:""`

Expand Down
18 changes: 18 additions & 0 deletions vault/audit_broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ func (a *AuditBroker) LogRequest(ctx context.Context, in *logical.LogInput, head
defer metrics.MeasureSince([]string{"audit", "log_request"}, time.Now())
a.RLock()
defer a.RUnlock()
if in.Request.RequestSSCToken != "" {
if in.Auth != nil {
reqAuthToken := in.Auth.ClientToken
in.Auth.ClientToken = in.Request.RequestSSCToken
defer func() {
in.Auth.ClientToken = reqAuthToken
}()
}
}

var retErr *multierror.Error

Expand Down Expand Up @@ -153,6 +162,15 @@ func (a *AuditBroker) LogResponse(ctx context.Context, in *logical.LogInput, hea
defer metrics.MeasureSince([]string{"audit", "log_response"}, time.Now())
a.RLock()
defer a.RUnlock()
if in.Request.RequestSSCToken != "" {
if in.Auth != nil {
reqAuthToken := in.Auth.ClientToken
in.Auth.ClientToken = in.Request.RequestSSCToken
defer func() {
in.Auth.ClientToken = reqAuthToken
}()
}
}

var retErr *multierror.Error

Expand Down
34 changes: 34 additions & 0 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ const (
coreKeyringCanaryPath = "core/canary-keyring"

indexHeaderHMACKeyPath = "core/index-header-hmac-key"

// ForwardSSCTokenToActive is the value that must be set in the
// forwardToActive to trigger forwarding if a perf standby encounters
// an SSC Token that it does not have the WAL state for.
ForwardSSCTokenToActive = "new_token"
)

var (
Expand Down Expand Up @@ -580,6 +585,13 @@ type Core struct {
enableResponseHeaderHostname bool
enableResponseHeaderRaftNodeID bool

// disableSSCTokens is used to disable server side consistent token creation/usage
disableSSCTokens bool
// forwardToActive is a server config used to determine when the node should forward
// requests to the active node. This value must be set to "new_token" in order for
// forwarding to take effect.
forwardToActive string

// versionTimestamps is a map of vault versions to timestamps when the version
// was first run. Note that because perf standbys should be upgraded first, and
// only the active node will actually write the new version timestamp, a perf
Expand Down Expand Up @@ -706,6 +718,12 @@ type CoreConfig struct {
// Whether to send headers in the HTTP response showing hostname or raft node ID
EnableResponseHeaderHostname bool
EnableResponseHeaderRaftNodeID bool

// DisableSSCTokens is used to disable the use of server side consistent tokens
DisableSSCTokens bool
// ForwardToActive is a server configuration that will determine when requests
// should be forwarded to the active node
ForwardToActive string
}

// GetServiceRegistration returns the config's ServiceRegistration, or nil if it does
Expand Down Expand Up @@ -848,6 +866,8 @@ func CreateCore(conf *CoreConfig) (*Core, error) {
disableAutopilot: conf.DisableAutopilot,
enableResponseHeaderHostname: conf.EnableResponseHeaderHostname,
enableResponseHeaderRaftNodeID: conf.EnableResponseHeaderRaftNodeID,
disableSSCTokens: conf.DisableSSCTokens,
forwardToActive: conf.ForwardToActive,
mountMigrationTracker: &sync.Map{},
}
c.standbyStopCh.Store(make(chan struct{}))
Expand Down Expand Up @@ -1103,6 +1123,17 @@ func (c *Core) RaftNodeIDHeaderEnabled() bool {
return c.enableResponseHeaderRaftNodeID
}

// DisableSSCTokens determines whether to use server side consistent tokens or not.
func (c *Core) DisableSSCTokens() bool {
return c.disableSSCTokens
}

// ForwardToActive returns the core configuration that determines whether requests
// should be forwarded to the active node.
func (c *Core) ForwardToActive() string {
return c.forwardToActive
}

// Shutdown is invoked when the Vault instance is about to be terminated. It
// should not be accessible as part of an API call as it will cause an availability
// problem. It is only used to gracefully quit in the case of HA so that failover
Expand Down Expand Up @@ -2069,6 +2100,9 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
if err := c.setupQuotas(ctx, false); err != nil {
return err
}
if err := c.setupHeaderHMACKey(ctx, false); err != nil {
return err
}
if !c.IsDRSecondary() {
if err := c.startRollback(); err != nil {
return err
Expand Down
Loading