Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
342023b
feat: add optional token hashing
gwossum Feb 6, 2025
f45372d
chore: rollback changes to v1/authorization
gwossum Feb 6, 2025
226b492
chore: fix static check issues, take advantage of go1.23 feature
gwossum Feb 6, 2025
32ff23b
chore: replace deprecated call
gwossum Feb 6, 2025
2cb148b
fix: prevent duplicate tokens from being created
gwossum Feb 11, 2025
66d58d5
fix: allow token lookups by all hashing algorithms used in store
gwossum Feb 13, 2025
c40e0cf
chore: variety of minor fixes
gwossum Feb 14, 2025
997e38f
chore: improve comments, error handling, logging, and edge cases
gwossum Feb 24, 2025
d530327
chore: fix spelling and grammar mistakes, remove dead code
gwossum Feb 25, 2025
eaa140d
chore: address comments from PR
gwossum Jul 9, 2025
0b61c32
chore: add tests for misuses of NewAuthorizationHasher
gwossum Jul 14, 2025
c06e7bb
chore: turn some user-facing string literals into constants
gwossum Jul 14, 2025
5494e24
chore: add context to test cases
gwossum Jul 15, 2025
d473f24
chore: update outdated comment
gwossum Aug 25, 2025
ad62021
fix: remove timing attack when comparing raw tokens
gwossum Aug 25, 2025
a6b9cb9
chore: code improvements
gwossum Aug 27, 2025
18ec84a
chore: fix flaky test
gwossum Nov 19, 2025
339ee6d
chore: address PR comments
gwossum Nov 19, 2025
5988c96
fix: use constant time comparison when checking for unhashed tokens
gwossum Nov 19, 2025
5e88aff
feat: add logging if hashing disabled but hashed tokens found
gwossum Nov 19, 2025
19a4057
chore: abstract checking if tokens are clear or set for readability
gwossum Nov 20, 2025
d1b3e1b
chore: updates based on PR comments
gwossum Nov 20, 2025
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
3 changes: 0 additions & 3 deletions authorization/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ type postAuthorizationRequest struct {
type authResponse struct {
ID platform.ID `json:"id"`
Token string `json:"token"`
HashedToken string `json:"hashedToken"`
Status influxdb.Status `json:"status"`
Description string `json:"description"`
OrgID platform.ID `json:"orgID"`
Expand Down Expand Up @@ -167,7 +166,6 @@ func (h *AuthHandler) newAuthResponse(ctx context.Context, a *influxdb.Authoriza
res := &authResponse{
ID: a.ID,
Token: a.Token,
HashedToken: a.HashedToken,
Status: a.Status,
Description: a.Description,
OrgID: a.OrgID,
Expand Down Expand Up @@ -199,7 +197,6 @@ func (a *authResponse) toInfluxdb() *influxdb.Authorization {
res := &influxdb.Authorization{
ID: a.ID,
Token: a.Token,
HashedToken: a.HashedToken,
Status: a.Status,
Description: a.Description,
OrgID: a.OrgID,
Expand Down
10 changes: 5 additions & 5 deletions authorization/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ into BoltDB. Raw tokens are not stored.
To verify tokens when hashed tokens are enabled, the presented token's hash is calculated and used
for token index lookup. The rest of the authorization flow is unchanged.

The hashed token index is separate from the raw token index. Newer versions also verify that the token
is not a valid PHC string before starting authorization. This prevents the following attack:
The hashed token index is separate from the raw token index. In addition, the token presented by the API
is rejected if it is a PHC encoded hash before starting authorization. The separate index and rejected
PHC tokens from the API prevent the following attack:
1. Hashed token is extracted from BoltDB.
2. Token hashing is disabled.
3. The hashed token is presented to the API, which will misinterpret it as a raw token and allow access.
Expand Down Expand Up @@ -300,17 +301,16 @@ func (s *Store) hashedTokenMigration(ctx context.Context) error {
// Figure out which authorization records need to be updated.
var authsNeedingUpdate []*influxdb.Authorization
err := s.View(ctx, func(tx kv.Tx) error {
s.forEachAuthorization(ctx, tx, nil, func(a *influxdb.Authorization) bool {
return s.forEachAuthorization(ctx, tx, nil, func(a *influxdb.Authorization) bool {
if a.IsHashedTokenClear() {
if a.IsTokenSet() {
authsNeedingUpdate = append(authsNeedingUpdate, a)
} else {
s.log.Warn("found authorization without any token set during hashed token migration", zap.Uint64("ID", uint64(a.ID)), zap.String("description", a.Description))
s.log.Warn("during hashed token migration, found authorization without any token set", zap.Uint64("ID", uint64(a.ID)), zap.String("description", a.Description))
}
}
return true
})
return nil
})
if err != nil {
return err
Expand Down
1 change: 0 additions & 1 deletion authorization/storage_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
var (
ErrNilAuthorization = goerrors.New("authorization cannot be nil")
ErrHashedTokenMismatch = goerrors.New("HashedToken does not match Token")
ErrIncorrectToken = goerrors.New("token is incorrect for authorization")
ErrNoTokenAvailable = goerrors.New("no token available for authorization")
)

Expand Down
2 changes: 1 addition & 1 deletion cmd/influxd/launcher/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ func (o *InfluxdOpts) BindCliOpts() []cli.Opt {
DestP: &o.UseHashedTokens,
Flag: "use-hashed-tokens",
Default: o.UseHashedTokens,
Desc: "enable token hashing",
Desc: "enable storing hashed API tokens on disk (improves security, but prevents downgrades to < 2.8)",
},
}
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/influxd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ func (m *Launcher) run(ctx context.Context, opts *InfluxdOpts) (err error) {
var authSvc platform.AuthorizationService
{
hasherVariantName := authorization.DefaultHashVariantName // This value could come from opts in the future.
authStore, err := authorization.NewStore(ctx, m.kvStore, opts.UseHashedTokens, authorization.WithAuthorizationHashVariantName(hasherVariantName), authorization.WithLogger(m.log))
authStoreLogger := m.log.With(zap.String("store", "auth"))
authStore, err := authorization.NewStore(ctx, m.kvStore, opts.UseHashedTokens, authorization.WithAuthorizationHashVariantName(hasherVariantName), authorization.WithLogger(authStoreLogger))
if err != nil {
m.log.Error("Failed creating new authorization store", zap.Error(err), zap.Bool("UseHashedTokens", opts.UseHashedTokens), zap.String("hasherVariant", hasherVariantName))
return err
Expand Down
5 changes: 0 additions & 5 deletions pkg/crypt/algorithm/influxdb2/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ func RegisterDecoderVariant(r algorithm.DecoderRegister, variant Variant) error
return nil
}

// Decode the encoded digest into a algorithm.Digest.
func Decode(encodedDigest string) (digest algorithm.Digest, err error) {
return DecodeVariant(VariantNone)(encodedDigest)
}

// DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided plaintext.Variant. If
// plaintext.VariantNone is used all variants can be decoded.
func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) {
Expand Down
11 changes: 6 additions & 5 deletions pkg/crypt/algorithm/influxdb2/digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package influxdb2

import (
"crypto/subtle"
"errors"
"fmt"

"github.com/go-crypt/crypt/algorithm"
)

// NewDigest creates a new plaintext.Digest using the plaintext.Variant.
func NewDigest(password string) (digest Digest) {
return NewSHA256Digest(password)
}
// ErrDigestInvalid is an error returned when a hash digest has an invalid or unsupported properties. It is NOT
// returned on token or password mismatches. It is equivalent to go-crypt's algorithm.ErrPasswordInvalid
// error, but with a message that makes more sense for our usage with tokens.
var ErrDigestInvalid = errors.New("hashed token or password is invalid")

// NewSHA256Digest creates a new influxdb2.Digest using the SHA256 for the hash.
func NewSHA256Digest(password string) (digest Digest) {
Expand Down Expand Up @@ -48,7 +49,7 @@ func (d *Digest) MatchAdvanced(password string) (match bool, err error) {
// MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well.
func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) {
if len(d.key) == 0 {
return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid))
return false, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, fmt.Errorf("%w: key has 0 bytes", ErrDigestInvalid))
}

input := d.Variant.Hash(passwordBytes)
Expand Down
10 changes: 8 additions & 2 deletions testing/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/mock"
"github.com/stretchr/testify/require"
)

const (
Expand Down Expand Up @@ -706,7 +707,6 @@ func FindAuthorizationByToken(
authorization *influxdb.Authorization
}

// VALIS: Add tests to make sure look-up by hashed token does /not/ work
tests := []struct {
name string
fields AuthorizationFields
Expand Down Expand Up @@ -859,6 +859,13 @@ func FindAuthorizationByToken(
if diff := cmp.Diff(authorization, tt.wants.authorization, authorizationCmpOptions...); diff != "" {
t.Errorf("authorization is different -got/+want\ndiff %s", diff)
}

// Verify that lookup by the hashed token does not work.
if authorization.IsHashedTokenSet() {
a, err := s.FindAuthorizationByToken(ctx, authorization.HashedToken)
require.ErrorContains(t, err, "authorization not found")
require.Nil(t, a)
}
})
}
}
Expand All @@ -875,7 +882,6 @@ func FindAuthorizations(
token string
}

// VALIS: Do we need tests that set HashedToken, or tests with Token and HashedToken set?
type wants struct {
authorizations []*influxdb.Authorization
err error
Expand Down