@@ -14,6 +14,7 @@ import (
1414
1515const bearerPrefix = "Bearer "
1616const responseBodyStr = "response body"
17+ const invalidImageReferenceFormat = "invalid image reference: %w"
1718
1819// ImageReference represents a parsed Docker image reference
1920type ImageReference struct {
@@ -133,14 +134,13 @@ func NewRegistryClient() *RegistryClient {
133134// Authenticate obtains a token for the given image
134135func (c * RegistryClient ) Authenticate (ref ImageReference ) error {
135136 if err := ValidateImageReference (ref ); err != nil {
136- return fmt .Errorf ("invalid image reference: %w" , err )
137+ return fmt .Errorf (invalidImageReferenceFormat , err )
137138 }
138139
139140 creds , hasCredentials := GetCredentials (ref .Registry )
141+ c .username = "anonymous"
140142 if hasCredentials {
141143 c .username = creds .Username
142- } else {
143- c .username = "anonymous"
144144 }
145145
146146 registryURL , err := buildRegistryURL (ref .Registry , "/v2/" )
@@ -153,11 +153,12 @@ func (c *RegistryClient) Authenticate(ref ImageReference) error {
153153 }
154154 defer closeWithLog (resp .Body , responseBodyStr )
155155
156- if resp .StatusCode == http .StatusOK {
156+ switch resp .StatusCode {
157+ case http .StatusOK :
157158 return nil // No auth required
158- }
159-
160- if resp . StatusCode != http . StatusUnauthorized {
159+ case http . StatusUnauthorized :
160+ // continue to token exchange below
161+ default :
161162 return fmt .Errorf ("unexpected status: %d" , resp .StatusCode )
162163 }
163164
@@ -168,56 +169,70 @@ func (c *RegistryClient) Authenticate(ref ImageReference) error {
168169
169170 realm , service , scope := parseAuthHeader (authHeader , ref .Repository )
170171
171- // Validate the realm URL to prevent SSRF
172+ if err := validateAuthRealm (realm ); err != nil {
173+ return err
174+ }
175+
176+ token , err := c .fetchToken (realm , service , scope , creds , hasCredentials )
177+ if err != nil {
178+ return err
179+ }
180+ c .token = token
181+ return nil
182+ }
183+
184+ // validateAuthRealm checks that the token realm URL is safe to contact (SSRF protection).
185+ func validateAuthRealm (realm string ) error {
172186 parsedRealm , err := url .Parse (realm )
173187 if err != nil {
174188 return fmt .Errorf ("invalid auth realm URL: %w" , err )
175189 }
176190 if parsedRealm .Scheme != "https" && parsedRealm .Scheme != "http" {
177191 return fmt .Errorf ("invalid auth realm scheme: %s" , parsedRealm .Scheme )
178192 }
179- // Validate the realm host to prevent SSRF to internal/private networks
180193 if err := validateRegistry (parsedRealm .Host ); err != nil {
181194 return fmt .Errorf ("invalid auth realm host: %w" , err )
182195 }
196+ return nil
197+ }
183198
199+ // fetchToken requests a Bearer token from the auth realm and returns it.
200+ func (c * RegistryClient ) fetchToken (realm , service , scope string , creds RegistryCredentials , hasCredentials bool ) (string , error ) {
184201 tokenURL := fmt .Sprintf ("%s?service=%s&scope=%s" , realm , url .QueryEscape (service ), url .QueryEscape (scope ))
185202
186203 req , err := http .NewRequest ("GET" , tokenURL , nil )
187204 if err != nil {
188- return err
205+ return "" , err
189206 }
190-
191207 if hasCredentials {
192208 auth := base64 .StdEncoding .EncodeToString ([]byte (creds .Username + ":" + creds .Password ))
193209 req .Header .Set ("Authorization" , "Basic " + auth )
194210 }
195211
196- resp , err = c .httpClient .Do (req )
212+ resp , err : = c .httpClient .Do (req )
197213 if err != nil {
198- return err
214+ return "" , err
199215 }
200216 defer closeWithLog (resp .Body , responseBodyStr )
201217
202218 if resp .StatusCode != http .StatusOK {
203219 body , _ := io .ReadAll (io .LimitReader (resp .Body , 4096 ))
204- return fmt .Errorf ("authentication failed: %d - %s" , resp .StatusCode , string (body ))
220+ return "" , fmt .Errorf ("authentication failed: %d - %s" , resp .StatusCode , string (body ))
205221 }
206222
207223 var tokenResp struct {
208224 Token string `json:"token"`
209225 AccessToken string `json:"access_token"`
210226 }
211227 if err := json .NewDecoder (resp .Body ).Decode (& tokenResp ); err != nil {
212- return err
228+ return "" , err
213229 }
214230
215- c . token = tokenResp .Token
216- if c . token == "" {
217- c . token = tokenResp .AccessToken
231+ token : = tokenResp .Token
232+ if token == "" {
233+ token = tokenResp .AccessToken
218234 }
219-
220- return nil
235+ return token , nil
221236}
222237
223238// GetAuthenticatedUser returns the username used for authentication
@@ -370,7 +385,7 @@ func (c *RegistryClient) doSafeRegistryRequest(registry, pathFormat string, head
370385// fetchManifestResponse fetches the raw manifest response from the registry.
371386func (c * RegistryClient ) fetchManifestResponse (ref ImageReference , reference string ) (* http.Response , error ) {
372387 if err := ValidateImageReference (ref ); err != nil {
373- return nil , fmt .Errorf ("invalid image reference: %w" , err )
388+ return nil , fmt .Errorf (invalidImageReferenceFormat , err )
374389 }
375390
376391 headers := map [string ]string {"Accept" : manifestAcceptHeader }
@@ -379,10 +394,6 @@ func (c *RegistryClient) fetchManifestResponse(ref ImageReference, reference str
379394
380395// getManifest retrieves the image manifest for the given platform
381396func (c * RegistryClient ) getManifest (ref ImageReference , platform Platform ) (* ManifestV2 , error ) {
382- if err := ValidateImageReference (ref ); err != nil {
383- return nil , fmt .Errorf ("invalid image reference: %w" , err )
384- }
385-
386397 resp , err := c .fetchManifestResponse (ref , ref .Tag )
387398 if err != nil {
388399 return nil , err
@@ -405,7 +416,7 @@ func (c *RegistryClient) getManifest(ref ImageReference, platform Platform) (*Ma
405416
406417func (c * RegistryClient ) getManifestByDigest (ref ImageReference , digest string ) (* ManifestV2 , error ) {
407418 if err := ValidateImageReference (ref ); err != nil {
408- return nil , fmt .Errorf ("invalid image reference: %w" , err )
419+ return nil , fmt .Errorf (invalidImageReferenceFormat , err )
409420 }
410421
411422 if err := validateDigest (digest ); err != nil {
@@ -436,7 +447,7 @@ func (c *RegistryClient) getManifestByDigest(ref ImageReference, digest string)
436447// DownloadBlob downloads a blob to a file
437448func (c * RegistryClient ) DownloadBlob (ref ImageReference , digest , destPath string ) error {
438449 if err := ValidateImageReference (ref ); err != nil {
439- return fmt .Errorf ("invalid image reference: %w" , err )
450+ return fmt .Errorf (invalidImageReferenceFormat , err )
440451 }
441452
442453 if err := validateDigest (digest ); err != nil {
0 commit comments