-
Notifications
You must be signed in to change notification settings - Fork 96
refactor: resolve Docker credentials on CLI side instead of copying config #590
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,16 @@ import ( | |
| "github.com/sirupsen/logrus" | ||
| ) | ||
|
|
||
| // Credentials holds authentication credentials for registry operations. | ||
| type Credentials struct { | ||
| // Username for basic authentication. | ||
| Username string | ||
| // Password for basic authentication. | ||
| Password string | ||
| // BearerToken for token-based authentication (e.g., Hugging Face). | ||
| BearerToken string | ||
| } | ||
|
|
||
| // Client provides model distribution functionality | ||
| type Client struct { | ||
| store *store.LocalStore | ||
|
|
@@ -227,32 +237,32 @@ func (c *Client) resolveID(id string) string { | |
| } | ||
|
|
||
| // PullModel pulls a model from a registry and returns the local file path | ||
| func (c *Client) PullModel(ctx context.Context, reference string, progressWriter io.Writer, bearerToken ...string) error { | ||
| func (c *Client) PullModel(ctx context.Context, reference string, progressWriter io.Writer, creds *Credentials) error { | ||
| // Store original reference before normalization (needed for case-sensitive HuggingFace API) | ||
| originalReference := reference | ||
| // Normalize the model reference | ||
| reference = c.normalizeModelName(reference) | ||
| c.log.Infoln("Starting model pull:", utils.SanitizeForLog(reference)) | ||
|
|
||
| // Handle bearer token for registry authentication | ||
| var token string | ||
| if len(bearerToken) > 0 && bearerToken[0] != "" { | ||
| token = bearerToken[0] | ||
| } | ||
|
|
||
| // HuggingFace references always use native pull (download raw files from HF Hub) | ||
| if isHuggingFaceReference(originalReference) { | ||
| c.log.Infoln("Using native HuggingFace pull for:", utils.SanitizeForLog(reference)) | ||
|
Comment on lines
+240
to
249
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Authentication precedence differs between PullModel and PushModel and may be surprising In PullModel, basic auth currently takes precedence over BearerToken, while PushModel does the opposite. This inconsistent precedence means callers who set both fields get different auth behavior between pull and push. Consider standardizing the precedence across both methods or rejecting calls where multiple auth fields are set to avoid confusion and unintended auth schemes. Suggested implementation: // PullModel pulls a model from a registry and returns the local file path
func (c *Client) PullModel(ctx context.Context, reference string, progressWriter io.Writer, creds *Credentials) error {
// Ensure callers don't mix auth mechanisms; this matches PushModel behavior and avoids
// surprising precedence differences when multiple credential fields are set.
if creds != nil {
hasBearer := creds.BearerToken != ""
// NOTE: adjust these field names if your Credentials type uses different ones
hasBasic := creds.Username != "" || creds.Password != ""
if hasBearer && hasBasic {
return fmt.Errorf("multiple authentication methods configured: use either bearer token or basic auth, not both")
}
}
// Store original reference before normalization (needed for case-sensitive HuggingFace API)
originalReference := reference
// Normalize the model reference
reference = c.normalizeModelName(reference)
c.log.Infoln("Starting model pull:", utils.SanitizeForLog(reference))
|
||
| // Pass original reference to preserve case-sensitivity for HuggingFace API | ||
| var token string | ||
| if creds != nil && creds.BearerToken != "" { | ||
| token = creds.BearerToken | ||
| } | ||
| return c.pullNativeHuggingFace(ctx, originalReference, progressWriter, token) | ||
| } | ||
|
|
||
| // For non-HF references, use OCI registry | ||
| registryClient := c.registry | ||
| if token != "" { | ||
| // Create a temporary registry client with bearer token authentication | ||
| auth := authn.NewBearer(token) | ||
| registryClient = registry.FromClient(c.registry, registry.WithAuth(auth)) | ||
| if creds != nil { | ||
| if creds.Username != "" && creds.Password != "" { | ||
| registryClient = registry.FromClient(c.registry, registry.WithAuthConfig(creds.Username, creds.Password)) | ||
| } else if creds.BearerToken != "" { | ||
| registryClient = registry.FromClient(c.registry, registry.WithAuth(authn.NewBearer(creds.BearerToken))) | ||
| } | ||
| } | ||
|
|
||
| // Fetch the remote model to get the manifest | ||
|
|
@@ -538,9 +548,18 @@ func (c *Client) Tag(source string, target string) error { | |
| } | ||
|
|
||
| // PushModel pushes a tagged model from the content store to the registry. | ||
| func (c *Client) PushModel(ctx context.Context, tag string, progressWriter io.Writer) (err error) { | ||
| func (c *Client) PushModel(ctx context.Context, tag string, progressWriter io.Writer, creds *Credentials) (err error) { | ||
| registryClient := c.registry | ||
| if creds != nil { | ||
| if creds.BearerToken != "" { | ||
| registryClient = registry.FromClient(c.registry, registry.WithAuth(authn.NewBearer(creds.BearerToken))) | ||
| } else if creds.Username != "" && creds.Password != "" { | ||
| registryClient = registry.FromClient(c.registry, registry.WithAuthConfig(creds.Username, creds.Password)) | ||
| } | ||
| } | ||
|
|
||
| // Parse the tag | ||
| target, err := c.registry.NewTarget(tag) | ||
| target, err := registryClient.NewTarget(tag) | ||
| if err != nil { | ||
| return fmt.Errorf("new tag: %w", err) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 issue (security): Using PushScope for all token exchanges may be too restrictive for pull-only operations
resolveCredentials is used for both pull and push, but exchangeForToken always requests remote.PushScope. For pull-only callers this can both fail unnecessarily and request overly broad (write) tokens. Consider making the scope a parameter (pull vs push) or deriving it from the caller so we only request the minimum required scope.