Skip to content
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

Add proxy support for oci/auth login #808

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions oci/auth/aws/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
Expand Down Expand Up @@ -51,17 +53,32 @@ func ParseRegistry(registry string) (accountId, awsEcrRegion string, ok bool) {
// Client is a AWS ECR client which can log into the registry and return
// authorization information.
type Client struct {
config *aws.Config
mu sync.Mutex
config *aws.Config
mu sync.Mutex
proxyURL *url.URL
}

// Option is a functional option for configuring the client.
type Option func(*Client)

// WithProxyURL sets the proxy URL for the client.
func WithProxyURL(proxyURL *url.URL) Option {
return func(c *Client) {
c.proxyURL = proxyURL
}
}

// NewClient creates a new empty ECR client.
// NOTE: In order to avoid breaking the auth API with aws-sdk-go-v2's default
// config, return an empty Client. Client.getLoginAuth() loads the default
// config if Client.config is nil. This also enables tests to configure the
// Client with stub before calling the login method using Client.WithConfig().
func NewClient() *Client {
return &Client{}
func NewClient(opts ...Option) *Client {
client := &Client{}
for _, opt := range opts {
opt(client)
}
return client
}

// WithConfig allows setting the client config if it's uninitialized.
Expand All @@ -87,8 +104,16 @@ func (c *Client) getLoginAuth(ctx context.Context, awsEcrRegion string) (authn.A
if c.config != nil {
cfg = c.config.Copy()
} else {
var confOpts []func(*config.LoadOptions) error
confOpts = append(confOpts, config.WithRegion(awsEcrRegion))
if c.proxyURL != nil {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = http.ProxyURL(c.proxyURL)
confOpts = append(confOpts, config.WithHTTPClient(&http.Client{Transport: transport}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made me wonder if the explicitly set proxy should be respected when the aws.Config is configured externally. It looks like we can modify the given aws.Config to add proxy to it. But the same can't be done in case of Azure. It'll work for GCP easily as there's no external client in that case.
I believe the ability to provide external config was added just to make testing easier. I don't think anyone uses it for anything else.
I think for consistency across all the clients, it's better to not try to add proxy if an external client config is provided. So, it's good as it is.

}

var err error
cfg, err = config.LoadDefaultConfig(ctx, config.WithRegion(awsEcrRegion))
cfg, err = config.LoadDefaultConfig(ctx, confOpts...)
if err != nil {
c.mu.Unlock()
return authConfig, time.Time{}, fmt.Errorf("failed to load default configuration: %w", err)
Expand Down
32 changes: 28 additions & 4 deletions oci/auth/azure/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package azure
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"

Expand All @@ -44,11 +46,26 @@ const defaultCacheExpirationInSeconds = 600
type Client struct {
credential azcore.TokenCredential
scheme string
proxyURL *url.URL
}

// Option is a functional option for configuring the client.
type Option func(*Client)

// WithProxyURL sets the proxy URL for the client.
func WithProxyURL(proxyURL *url.URL) Option {
return func(c *Client) {
c.proxyURL = proxyURL
}
}

// NewClient creates a new ACR client with default configurations.
func NewClient() *Client {
return &Client{scheme: "https"}
func NewClient(opts ...Option) *Client {
client := &Client{scheme: "https"}
for _, opt := range opts {
opt(client)
}
return client
}

// WithTokenCredential sets the token credential used by the ACR client.
Expand All @@ -73,7 +90,14 @@ func (c *Client) getLoginAuth(ctx context.Context, registryURL string) (authn.Au
// NOTE: NewDefaultAzureCredential() performs a lot of environment lookup
// for creating default token credential. Load it only when it's needed.
if c.credential == nil {
cred, err := azidentity.NewDefaultAzureCredential(nil)
opts := &azidentity.DefaultAzureCredentialOptions{}
if c.proxyURL != nil {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = http.ProxyURL(c.proxyURL)
opts.Transport = &http.Client{Transport: transport}
}

cred, err := azidentity.NewDefaultAzureCredential(opts)
if err != nil {
return authConfig, time.Time{}, err
}
Expand All @@ -90,7 +114,7 @@ func (c *Client) getLoginAuth(ctx context.Context, registryURL string) (authn.Au
}

// Obtain ACR access token using exchanger.
ex := newExchanger(registryURL)
ex := newExchanger(registryURL, c.proxyURL)
accessToken, err := ex.ExchangeACRAccessToken(string(armToken.Token))
if err != nil {
return authConfig, time.Time{}, fmt.Errorf("error exchanging token: %w", err)
Expand Down
14 changes: 12 additions & 2 deletions oci/auth/azure/exchanger.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ type acrError struct {

type exchanger struct {
endpoint string
proxyURL *url.URL
}

// newExchanger returns an Azure Exchanger for Azure Container Registry with
// a given endpoint, for example https://azurecr.io.
func newExchanger(endpoint string) *exchanger {
func newExchanger(endpoint string, proxyURL *url.URL) *exchanger {
return &exchanger{
endpoint: endpoint,
proxyURL: proxyURL,
}
}

Expand All @@ -87,12 +89,20 @@ func (e *exchanger) ExchangeACRAccessToken(armToken string) (string, error) {
}
exchangeURL.Path = path.Join(exchangeURL.Path, "oauth2/exchange")

httpClient := &http.Client{}

if e.proxyURL != nil {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = http.ProxyURL(e.proxyURL)
httpClient.Transport = transport
}

parameters := url.Values{}
parameters.Add("grant_type", "access_token")
parameters.Add("service", exchangeURL.Hostname())
parameters.Add("access_token", armToken)

resp, err := http.PostForm(exchangeURL.String(), parameters)
resp, err := httpClient.PostForm(exchangeURL.String(), parameters)
if err != nil {
return "", fmt.Errorf("failed to send token exchange request: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion oci/auth/azure/exchanger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestExchanger_ExchangeACRAccessToken(t *testing.T) {
srv.Close()
})

ex := newExchanger(srv.URL)
ex := newExchanger(srv.URL, nil /*proxyURL*/)
token, err := ex.ExchangeACRAccessToken("some-access-token")
g.Expect(err != nil).To(Equal(tt.wantErr))
if tt.statusCode == http.StatusOK {
Expand Down
29 changes: 26 additions & 3 deletions oci/auth/gcp/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -50,11 +51,26 @@ func ValidHost(host string) bool {
// authorization information.
type Client struct {
tokenURL string
proxyURL *url.URL
}

// Option is a functional option for configuring the client.
type Option func(*Client)

// WithProxyURL sets the proxy URL for the client.
func WithProxyURL(proxyURL *url.URL) Option {
return func(c *Client) {
c.proxyURL = proxyURL
}
}

// NewClient creates a new GCR client with default configurations.
func NewClient() *Client {
return &Client{tokenURL: GCP_TOKEN_URL}
func NewClient(opts ...Option) *Client {
client := &Client{tokenURL: GCP_TOKEN_URL}
for _, opt := range opts {
opt(client)
}
return client
}

// WithTokenURL sets the token URL used by the GCR client.
Expand All @@ -77,7 +93,14 @@ func (c *Client) getLoginAuth(ctx context.Context) (authn.AuthConfig, time.Time,

request.Header.Add("Metadata-Flavor", "Google")

client := &http.Client{}
var transport http.RoundTripper
if c.proxyURL != nil {
t := http.DefaultTransport.(*http.Transport).Clone()
t.Proxy = http.ProxyURL(c.proxyURL)
transport = t
}

client := &http.Client{Transport: transport}
response, err := client.Do(request)
if err != nil {
return authConfig, time.Time{}, err
Expand Down
37 changes: 33 additions & 4 deletions oci/auth/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,42 @@ type Manager struct {
acr *azure.Client
}

// Option is a functional option for configuring the manager.
type Option func(*options)

type options struct {
proxyURL *url.URL
}

// WithProxyURL sets the proxy URL for the manager.
func WithProxyURL(proxyURL *url.URL) Option {
return func(o *options) {
o.proxyURL = proxyURL
}
}

// NewManager initializes a Manager with default registry clients
// configurations.
func NewManager() *Manager {
func NewManager(opts ...Option) *Manager {
var o options
for _, opt := range opts {
opt(&o)
}

var awsOpts []aws.Option
var gcpOpts []gcp.Option
var azureOpts []azure.Option

if o.proxyURL != nil {
awsOpts = append(awsOpts, aws.WithProxyURL(o.proxyURL))
gcpOpts = append(gcpOpts, gcp.WithProxyURL(o.proxyURL))
azureOpts = append(azureOpts, azure.WithProxyURL(o.proxyURL))
}

return &Manager{
ecr: aws.NewClient(),
gcr: gcp.NewClient(),
acr: azure.NewClient(),
ecr: aws.NewClient(awsOpts...),
gcr: gcp.NewClient(gcpOpts...),
acr: azure.NewClient(azureOpts...),
}
}

Expand Down
Loading