mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 05:23:04 +00:00
9eaa8eb026
* dns token fix whitespace for docs and comments fix test cases fix test cases remove tabs in help text Add changelog Peering dns test Peering dns test Partial implementation of Peered DNS test Swap to new topology lib expose dns port for integration tests on client remove partial test implementation remove extra port exposure remove changelog from the ent pr Add dns token to set-agent-token switch Add enterprise golden file Use builtin/dns template in tests Update ent dns policy Update ent dns template test remove local gen certs fix templated policy specs * add changelog * go mod tidy
339 lines
10 KiB
Go
339 lines
10 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package token
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"crypto/subtle"
|
|
)
|
|
|
|
type TokenSource bool
|
|
|
|
const (
|
|
TokenSourceConfig TokenSource = false
|
|
TokenSourceAPI TokenSource = true
|
|
)
|
|
|
|
type TokenKind int
|
|
|
|
const (
|
|
TokenKindAgent TokenKind = iota
|
|
TokenKindAgentRecovery
|
|
TokenKindUser
|
|
TokenKindReplication
|
|
TokenKindConfigFileRegistration
|
|
TokenKindDNS
|
|
)
|
|
|
|
type watcher struct {
|
|
kind TokenKind
|
|
ch chan<- struct{}
|
|
}
|
|
|
|
// Notifier holds the channel used to notify a watcher
|
|
// of token updates as well as some internal tracking
|
|
// information to allow for deregistering the notifier.
|
|
type Notifier struct {
|
|
id int
|
|
Ch <-chan struct{}
|
|
}
|
|
|
|
// Store is used to hold the special ACL tokens used by Consul agents. It is
|
|
// designed to update the tokens on the fly, so the token store itself should be
|
|
// plumbed around and used to get tokens at runtime, don't save the resulting
|
|
// tokens.
|
|
type Store struct {
|
|
// l synchronizes access to the token store.
|
|
l sync.RWMutex
|
|
|
|
// userToken is passed along for requests when the user didn't supply a
|
|
// token, and may be left blank to use the anonymous token. This will
|
|
// also be used for agent operations if the agent token isn't set.
|
|
userToken string
|
|
|
|
// userTokenSource indicates where this token originated from.
|
|
userTokenSource TokenSource
|
|
|
|
// agentToken is used for internal agent operations like self-registering
|
|
// with the catalog and anti-entropy, but should never be used for
|
|
// user-initiated operations.
|
|
agentToken string
|
|
|
|
// agentTokenSource indicates where this token originated from.
|
|
agentTokenSource TokenSource
|
|
|
|
// agentRecoveryToken is a special token that's only used locally for
|
|
// access to the /v1/agent utility operations if the servers aren't
|
|
// available.
|
|
agentRecoveryToken string
|
|
|
|
// agentRecoveryTokenSource indicates where this token originated from.
|
|
agentRecoveryTokenSource TokenSource
|
|
|
|
// replicationToken is a special token that's used by servers to
|
|
// replicate data from the primary datacenter.
|
|
replicationToken string
|
|
|
|
// replicationTokenSource indicates where this token originated from.
|
|
replicationTokenSource TokenSource
|
|
|
|
// configFileRegistrationToken is used to register services and checks
|
|
// that are defined in configuration files.
|
|
configFileRegistrationToken string
|
|
|
|
// configFileRegistrationTokenSource indicates where this token originated from.
|
|
configFileRegistrationTokenSource TokenSource
|
|
|
|
// dnsToken is a special token that is used as the implicit token for DNS requests
|
|
// as well as for DNS-specific RPC requests.
|
|
dnsToken string
|
|
|
|
// dnsTokenSource indicates where the dnsToken originated from.
|
|
dnsTokenSource TokenSource
|
|
|
|
watchers map[int]watcher
|
|
watcherIndex int
|
|
|
|
persistence *fileStore
|
|
// persistenceLock is used to synchronize access to the persisted token store
|
|
// within the data directory. This will prevent loading while writing as well as
|
|
// multiple concurrent writes.
|
|
persistenceLock sync.RWMutex
|
|
|
|
// enterpriseTokens contains tokens only used in consul-enterprise
|
|
enterpriseTokens
|
|
}
|
|
|
|
// Notify will set up a watch for when tokens of the desired kind is changed
|
|
func (t *Store) Notify(kind TokenKind) Notifier {
|
|
// buffering ensures that notifications aren't missed if the watcher
|
|
// isn't already in a select and that our notifications don't
|
|
// block returning from the Update* methods.
|
|
ch := make(chan struct{}, 1)
|
|
|
|
w := watcher{
|
|
kind: kind,
|
|
ch: ch,
|
|
}
|
|
|
|
t.l.Lock()
|
|
defer t.l.Unlock()
|
|
if t.watchers == nil {
|
|
t.watchers = make(map[int]watcher)
|
|
}
|
|
// we specifically want to avoid the zero-value to prevent accidental stop-notification requests
|
|
t.watcherIndex += 1
|
|
t.watchers[t.watcherIndex] = w
|
|
|
|
return Notifier{id: t.watcherIndex, Ch: ch}
|
|
}
|
|
|
|
// StopNotify stops the token store from sending notifications to the specified notifiers chan
|
|
func (t *Store) StopNotify(n Notifier) {
|
|
t.l.Lock()
|
|
defer t.l.Unlock()
|
|
delete(t.watchers, n.id)
|
|
}
|
|
|
|
// anyKindAllowed returns true if any of the kinds in the `check` list are
|
|
// set to be allowed in the `allowed` map.
|
|
//
|
|
// Note: this is mostly just a convenience to simplify the code in
|
|
// sendNotificationLocked and prevent more nested looping with breaks/continues
|
|
// and other state tracking.
|
|
func anyKindAllowed(allowed TokenKind, check []TokenKind) bool {
|
|
for _, kind := range check {
|
|
if allowed == kind {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// sendNotificationLocked will iterate through all watchers and notify them that a
|
|
// token they are watching has been updated.
|
|
//
|
|
// NOTE: this function explicitly does not attempt to send the kind or new token value
|
|
// along through the channel. With that approach watchers could potentially miss updates
|
|
// if the buffered chan fills up. Instead with this approach we just notify that any
|
|
// token they care about has been udpated and its up to the caller to retrieve the
|
|
// new value (after receiving from the chan). With this approach its entirely possible
|
|
// for the watcher to be notified twice before actually retrieving the token after the first
|
|
// read from the chan. This is better behavior than missing events. It can cause some
|
|
// churn temporarily but in common cases its not expected that these tokens would be updated
|
|
// frequently enough to cause this to happen.
|
|
func (t *Store) sendNotificationLocked(kinds ...TokenKind) {
|
|
for _, watcher := range t.watchers {
|
|
if !anyKindAllowed(watcher.kind, kinds) {
|
|
// ignore this watcher as it doesn't want events for these kinds of token
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case watcher.ch <- struct{}{}:
|
|
default:
|
|
// its already pending a notification
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateUserToken replaces the current user token in the store.
|
|
// Returns true if it was changed.
|
|
func (t *Store) UpdateUserToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.userToken, &t.userTokenSource, TokenKindUser)
|
|
}
|
|
|
|
// UpdateAgentToken replaces the current agent token in the store.
|
|
// Returns true if it was changed.
|
|
func (t *Store) UpdateAgentToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.agentToken, &t.agentTokenSource, TokenKindAgent)
|
|
}
|
|
|
|
// UpdateAgentRecoveryToken replaces the current agent recovery token in the store.
|
|
// Returns true if it was changed.
|
|
func (t *Store) UpdateAgentRecoveryToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.agentRecoveryToken,
|
|
&t.agentRecoveryTokenSource, TokenKindAgentRecovery)
|
|
}
|
|
|
|
// UpdateReplicationToken replaces the current replication token in the store.
|
|
// Returns true if it was changed.
|
|
func (t *Store) UpdateReplicationToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.replicationToken,
|
|
&t.replicationTokenSource, TokenKindReplication)
|
|
}
|
|
|
|
// UpdateConfigFileRegistrationToken replaces the current config file registration token
|
|
// in the store. Returns true if it was changed.
|
|
func (t *Store) UpdateConfigFileRegistrationToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.configFileRegistrationToken,
|
|
&t.configFileRegistrationTokenSource, TokenKindConfigFileRegistration)
|
|
}
|
|
|
|
// UpdateDNSToken replaces the current DNS token in the store.
|
|
// Returns true if it was changed.
|
|
func (t *Store) UpdateDNSToken(token string, source TokenSource) bool {
|
|
return t.updateToken(token, source, &t.dnsToken, &t.dnsTokenSource, TokenKindDNS)
|
|
}
|
|
|
|
func (t *Store) updateToken(token string, source TokenSource, dstToken *string, dstSource *TokenSource, kind TokenKind) bool {
|
|
t.l.Lock()
|
|
changed := *dstToken != token || *dstSource != source
|
|
*dstToken = token
|
|
*dstSource = source
|
|
if changed {
|
|
t.sendNotificationLocked(kind)
|
|
}
|
|
t.l.Unlock()
|
|
return changed
|
|
}
|
|
|
|
// UserToken returns the best token to use for user operations.
|
|
func (t *Store) UserToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.userToken
|
|
}
|
|
|
|
// AgentToken returns the best token to use for internal agent operations.
|
|
func (t *Store) AgentToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
if tok := t.enterpriseAgentToken(); tok != "" {
|
|
return tok
|
|
}
|
|
|
|
if t.agentToken != "" {
|
|
return t.agentToken
|
|
}
|
|
return t.userToken
|
|
}
|
|
|
|
func (t *Store) AgentRecoveryToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.agentRecoveryToken
|
|
}
|
|
|
|
// ReplicationToken returns the replication token.
|
|
func (t *Store) ReplicationToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.replicationToken
|
|
}
|
|
|
|
func (t *Store) ConfigFileRegistrationToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.configFileRegistrationToken
|
|
}
|
|
|
|
func (t *Store) DNSToken() string {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.dnsToken
|
|
}
|
|
|
|
// UserToken returns the best token to use for user operations.
|
|
func (t *Store) UserTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.userToken, t.userTokenSource
|
|
}
|
|
|
|
// AgentToken returns the best token to use for internal agent operations.
|
|
func (t *Store) AgentTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.agentToken, t.agentTokenSource
|
|
}
|
|
|
|
func (t *Store) AgentRecoveryTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.agentRecoveryToken, t.agentRecoveryTokenSource
|
|
}
|
|
|
|
// ReplicationTokenAndSource returns the replication token and its source.
|
|
func (t *Store) ReplicationTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.replicationToken, t.replicationTokenSource
|
|
}
|
|
|
|
func (t *Store) ConfigFileRegistrationTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.configFileRegistrationToken, t.configFileRegistrationTokenSource
|
|
}
|
|
|
|
// DNSTokenAndSource returns the best token to use for DNS-specific RPC requests and DNS requests
|
|
func (t *Store) DNSTokenAndSource() (string, TokenSource) {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return t.dnsToken, t.dnsTokenSource
|
|
}
|
|
|
|
// IsAgentRecoveryToken checks to see if a given token is the agent recovery token.
|
|
// This will never match an empty token for safety.
|
|
func (t *Store) IsAgentRecoveryToken(token string) bool {
|
|
t.l.RLock()
|
|
defer t.l.RUnlock()
|
|
|
|
return (token != "") && (subtle.ConstantTimeCompare([]byte(token), []byte(t.agentRecoveryToken)) == 1)
|
|
}
|