-
Notifications
You must be signed in to change notification settings - Fork 3.7k
feat: add optional token hashing #25982
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
Merged
+2,726
−716
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
342023b
feat: add optional token hashing
gwossum f45372d
chore: rollback changes to v1/authorization
gwossum 226b492
chore: fix static check issues, take advantage of go1.23 feature
gwossum 32ff23b
chore: replace deprecated call
gwossum 2cb148b
fix: prevent duplicate tokens from being created
gwossum 66d58d5
fix: allow token lookups by all hashing algorithms used in store
gwossum c40e0cf
chore: variety of minor fixes
gwossum 997e38f
chore: improve comments, error handling, logging, and edge cases
gwossum d530327
chore: fix spelling and grammar mistakes, remove dead code
gwossum eaa140d
chore: address comments from PR
gwossum 0b61c32
chore: add tests for misuses of NewAuthorizationHasher
gwossum c06e7bb
chore: turn some user-facing string literals into constants
gwossum 5494e24
chore: add context to test cases
gwossum d473f24
chore: update outdated comment
gwossum ad62021
fix: remove timing attack when comparing raw tokens
gwossum a6b9cb9
chore: code improvements
gwossum 18ec84a
chore: fix flaky test
gwossum 339ee6d
chore: address PR comments
gwossum 5988c96
fix: use constant time comparison when checking for unhashed tokens
gwossum 5e88aff
feat: add logging if hashing disabled but hashed tokens found
gwossum 19a4057
chore: abstract checking if tokens are clear or set for readability
gwossum d1b3e1b
chore: updates based on PR comments
gwossum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| package authorization | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/go-crypt/crypt" | ||
| "github.com/go-crypt/crypt/algorithm" | ||
| influxdb2_algo "github.com/influxdata/influxdb/v2/pkg/crypt/algorithm/influxdb2" | ||
| ) | ||
|
|
||
| var ( | ||
| ErrNoDecoders = errors.New("no authorization decoders specified") | ||
| ) | ||
|
|
||
| type AuthorizationHasher struct { | ||
| // hasher encodes tokens into hashed PHC-encoded tokens. | ||
| hasher algorithm.Hash | ||
|
|
||
| // decoder decodes hashed PHC-encoded tokens into crypt.Digest objects. | ||
| decoder *crypt.Decoder | ||
|
|
||
| // allHashers is the list of all hashers which could be used for hashed index lookup. | ||
| allHashers []algorithm.Hash | ||
| } | ||
|
|
||
| const ( | ||
| DefaultHashVariant = influxdb2_algo.VariantSHA256 | ||
| DefaultHashVariantName = influxdb2_algo.VariantIdentifierSHA256 | ||
|
|
||
| // HashVariantNameUnknown is the placeholder name used for unknown or unsupported hash variants. | ||
| HashVariantNameUnknown = "N/A" | ||
| ) | ||
|
|
||
| type authorizationHasherOptions struct { | ||
| hasherVariant influxdb2_algo.Variant | ||
| decoderVariants []influxdb2_algo.Variant | ||
| } | ||
|
|
||
| type AuthorizationHasherOption func(o *authorizationHasherOptions) | ||
|
|
||
| func WithHasherVariant(variant influxdb2_algo.Variant) AuthorizationHasherOption { | ||
| return func(o *authorizationHasherOptions) { | ||
| o.hasherVariant = variant | ||
| } | ||
| } | ||
|
|
||
| func WithDecoderVariants(variants []influxdb2_algo.Variant) AuthorizationHasherOption { | ||
| return func(o *authorizationHasherOptions) { | ||
| o.decoderVariants = variants | ||
| } | ||
| } | ||
|
|
||
| // NewAuthorizationHasher creates an AuthorizationHasher for influxdb2 algorithm hashed tokens. | ||
| // variantName specifies which token hashing variant to use, with blank indicating to use the default | ||
| // hashing variant. By default, all variants of the influxdb2 hashing scheme are supported for | ||
| // maximal compatibility. | ||
| func NewAuthorizationHasher(opts ...AuthorizationHasherOption) (*AuthorizationHasher, error) { | ||
| options := authorizationHasherOptions{ | ||
| hasherVariant: DefaultHashVariant, | ||
| decoderVariants: influxdb2_algo.AllVariants, | ||
| } | ||
|
|
||
| for _, o := range opts { | ||
| o(&options) | ||
| } | ||
|
|
||
| if len(options.decoderVariants) == 0 { | ||
| return nil, fmt.Errorf("error in NewAuthorizationHasher: %w", ErrNoDecoders) | ||
| } | ||
|
|
||
| // Create the hasher used for hashing new tokens before storage. | ||
| hasher, err := influxdb2_algo.New(influxdb2_algo.WithVariant(options.hasherVariant)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("creating hasher %s for AuthorizationHasher: %w", options.hasherVariant.Prefix(), err) | ||
| } | ||
|
|
||
| // Create decoder and register all requested decoder variants. | ||
| decoder := crypt.NewDecoder() | ||
| for _, variant := range options.decoderVariants { | ||
| if err := variant.RegisterDecoder(decoder); err != nil { | ||
| return nil, fmt.Errorf("registering variant %s with decoder: %w", variant.Prefix(), err) | ||
| } | ||
| } | ||
|
|
||
| // Create all variant hashers needed for requested decoder variants. This is required for operations where | ||
| // all potential variations of a raw token must be hashed, such as looking up a hash in the hashed token index. | ||
| var allHashers []algorithm.Hash | ||
| for _, variant := range options.decoderVariants { | ||
| h, err := influxdb2_algo.New(influxdb2_algo.WithVariant(variant)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("creating hasher %s for authorization service index lookups: %w", variant.Prefix(), err) | ||
| } | ||
| allHashers = append(allHashers, h) | ||
| } | ||
|
|
||
| return &AuthorizationHasher{ | ||
| hasher: hasher, | ||
| decoder: decoder, | ||
| allHashers: allHashers, | ||
| }, nil | ||
| } | ||
|
|
||
| // Hash generates a PHC-encoded hash of token using the selected hash algorithm variant. | ||
| func (h *AuthorizationHasher) Hash(token string) (string, error) { | ||
| digest, err := h.hasher.Hash(token) | ||
| if err != nil { | ||
| return "", fmt.Errorf("hashing raw token failed: %w", err) | ||
| } | ||
| return digest.Encode(), nil | ||
| } | ||
|
|
||
| // AllHashes generates a list of PHC-encoded hashes of token for all deterministic (i.e. non-salted) supported hashes. | ||
| func (h *AuthorizationHasher) AllHashes(token string) ([]string, error) { | ||
| hashes := make([]string, len(h.allHashers)) | ||
| for idx, hasher := range h.allHashers { | ||
gwossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| digest, err := hasher.Hash(token) | ||
| if err != nil { | ||
| variantName := HashVariantNameUnknown | ||
| if influxdb_hasher, ok := hasher.(*influxdb2_algo.Hasher); ok { | ||
| variantName = influxdb_hasher.Variant().Prefix() | ||
| } | ||
| return nil, fmt.Errorf("hashing raw token failed (variant=%s): %w", variantName, err) | ||
gwossum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| hashes[idx] = digest.Encode() | ||
| } | ||
| return hashes, nil | ||
| } | ||
|
|
||
| // AllHashesCount returns the number of hash variants available through AllHashes. | ||
| func (h *AuthorizationHasher) AllHashesCount() int { | ||
| return len(h.allHashers) | ||
| } | ||
|
|
||
| // Decode decodes a PHC-encoded hash into a Digest object that can be matched. | ||
| func (h *AuthorizationHasher) Decode(phc string) (algorithm.Digest, error) { | ||
| return h.decoder.Decode(phc) | ||
| } | ||
|
|
||
| // Match determines if a raw token matches a PHC-encoded token. | ||
| func (h *AuthorizationHasher) Match(phc string, token string) (bool, error) { | ||
| digest, err := h.Decode(phc) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| return digest.MatchAdvanced(token) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package authorization_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/go-crypt/crypt/algorithm" | ||
| "github.com/influxdata/influxdb/v2/authorization" | ||
| influxdb2_algo "github.com/influxdata/influxdb/v2/pkg/crypt/algorithm/influxdb2" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func Test_NewAuthorizationHasher_EmptyDecoderVariants(t *testing.T) { | ||
| hasher, err := authorization.NewAuthorizationHasher( | ||
| authorization.WithDecoderVariants([]influxdb2_algo.Variant{}), | ||
| ) | ||
|
|
||
| require.ErrorIs(t, err, authorization.ErrNoDecoders) | ||
| require.Nil(t, hasher) | ||
| } | ||
|
|
||
| func TestNewAuthorizationHasher_WithInvalidDecoderVariant(t *testing.T) { | ||
| // Test that using an invalid decoder variant returns an error | ||
| hasher, err := authorization.NewAuthorizationHasher( | ||
| authorization.WithDecoderVariants([]influxdb2_algo.Variant{ | ||
| influxdb2_algo.Variant(-1), // Invalid variant | ||
| }), | ||
| ) | ||
|
|
||
| // Should return an error and nil hasher | ||
| require.ErrorIs(t, err, algorithm.ErrParameterInvalid) | ||
| require.Contains(t, err.Error(), "registering variant") | ||
| require.Nil(t, hasher) | ||
| } | ||
|
|
||
| func TestNewAuthorizationHasher_WithHasherVariantInvalid(t *testing.T) { | ||
| // Test that using VariantNone returns an error | ||
| hasher, err := authorization.NewAuthorizationHasher( | ||
| authorization.WithHasherVariant(influxdb2_algo.Variant(-1)), | ||
| ) | ||
|
|
||
| // Should return an error and nil hasher | ||
| require.ErrorIs(t, err, algorithm.ErrParameterInvalid) | ||
| require.ErrorContains(t, err, "creating hasher") | ||
| require.Nil(t, hasher) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.