mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-16 15:10:12 +01:00
feat(registry): add support for custom CA certificates and TLS validation
- Introduced `--registry-ca` and `--registry-ca-validate` flags for configuring TLS verification with private registries. - Implemented in-memory token caching with expiration handling. - Updated documentation to reflect new CLI options and usage examples. - Added tests for token cache concurrency and expiry behavior.
This commit is contained in:
parent
76f9cea516
commit
e1f67fc3d0
18 changed files with 738 additions and 17 deletions
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/pkg/registry/helpers"
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
|
|
@ -75,12 +77,20 @@ func GetChallengeRequest(URL url.URL) (*http.Request, error) {
|
|||
// GetBearerHeader tries to fetch a bearer token from the registry based on the challenge instructions
|
||||
func GetBearerHeader(challenge string, imageRef ref.Named, registryAuth string) (string, error) {
|
||||
client := http.Client{}
|
||||
authURL, err := GetAuthURL(challenge, imageRef)
|
||||
|
||||
authURL, err := GetAuthURL(challenge, imageRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Build cache key from the auth realm, service and scope
|
||||
cacheKey := authURL.String()
|
||||
|
||||
// Check cache first
|
||||
if token := getCachedToken(cacheKey); token != "" {
|
||||
return fmt.Sprintf("Bearer %s", token), nil
|
||||
}
|
||||
|
||||
var r *http.Request
|
||||
if r, err = http.NewRequest("GET", authURL.String(), nil); err != nil {
|
||||
return "", err
|
||||
|
|
@ -88,8 +98,6 @@ func GetBearerHeader(challenge string, imageRef ref.Named, registryAuth string)
|
|||
|
||||
if registryAuth != "" {
|
||||
logrus.Debug("Credentials found.")
|
||||
// CREDENTIAL: Uncomment to log registry credentials
|
||||
// logrus.Tracef("Credentials: %v", registryAuth)
|
||||
r.Header.Add("Authorization", fmt.Sprintf("Basic %s", registryAuth))
|
||||
} else {
|
||||
logrus.Debug("No credentials found.")
|
||||
|
|
@ -99,6 +107,7 @@ func GetBearerHeader(challenge string, imageRef ref.Named, registryAuth string)
|
|||
if authResponse, err = client.Do(r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer authResponse.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(authResponse.Body)
|
||||
tokenResponse := &types.TokenResponse{}
|
||||
|
|
@ -108,9 +117,54 @@ func GetBearerHeader(challenge string, imageRef ref.Named, registryAuth string)
|
|||
return "", err
|
||||
}
|
||||
|
||||
// Cache token if ExpiresIn provided
|
||||
if tokenResponse.Token != "" {
|
||||
storeToken(cacheKey, tokenResponse.Token, tokenResponse.ExpiresIn)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Bearer %s", tokenResponse.Token), nil
|
||||
}
|
||||
|
||||
// token cache implementation
|
||||
type cachedToken struct {
|
||||
token string
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
tokenCache = map[string]cachedToken{}
|
||||
tokenCacheMu = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// now is a package-level function returning current time. It is a variable so tests
|
||||
// can override it for deterministic behavior.
|
||||
var now = time.Now
|
||||
|
||||
// getCachedToken returns token string if present and not expired, otherwise empty
|
||||
func getCachedToken(key string) string {
|
||||
tokenCacheMu.Lock()
|
||||
defer tokenCacheMu.Unlock()
|
||||
if ct, ok := tokenCache[key]; ok {
|
||||
if ct.expiresAt.IsZero() || now().Before(ct.expiresAt) {
|
||||
return ct.token
|
||||
}
|
||||
// expired
|
||||
delete(tokenCache, key)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// storeToken stores token with optional ttl (seconds). ttl<=0 means no expiry.
|
||||
func storeToken(key, token string, ttl int) {
|
||||
tokenCacheMu.Lock()
|
||||
defer tokenCacheMu.Unlock()
|
||||
ct := cachedToken{token: token}
|
||||
if ttl > 0 {
|
||||
ct.expiresAt = now().Add(time.Duration(ttl) * time.Second)
|
||||
}
|
||||
tokenCache[key] = ct
|
||||
}
|
||||
|
||||
// GetAuthURL from the instructions in the challenge
|
||||
func GetAuthURL(challenge string, imageRef ref.Named) (*url.URL, error) {
|
||||
loweredChallenge := strings.ToLower(challenge)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue