Skip to content

Commit

Permalink
oci: Remove cache from auth and int tests code
Browse files Browse the repository at this point in the history
OCI auth token caching will be done in the OCI client. Remove the cache
from auth package and its use in the integration tests.

Since the cache related changes introduced the auth token expiry time to
be returned from the auth provider logins, a new function
LoginWithExpiry() is introduced which returns the expiry time. The
original Login() remains as it is for backwards compatibility. A higher
level client can use LoginWithExpiry() to obtain the TTL of the auth
token to use with the cache.

Signed-off-by: Sunny <[email protected]>
  • Loading branch information
darkowlzz committed Nov 7, 2024
1 parent eb427c1 commit 02d44f7
Show file tree
Hide file tree
Showing 8 changed files with 16 additions and 658 deletions.
44 changes: 0 additions & 44 deletions oci/auth/login/cache.go

This file was deleted.

94 changes: 13 additions & 81 deletions oci/auth/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (
"fmt"
"net/url"
"strings"
"time"

"github.com/go-logr/logr"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"

"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/oci"
"github.com/fluxcd/pkg/oci/auth/aws"
"github.com/fluxcd/pkg/oci/auth/azure"
Expand Down Expand Up @@ -70,8 +70,6 @@ type ProviderOptions struct {
// AzureAutoLogin enables automatic attempt to get credentials for images in
// ACR.
AzureAutoLogin bool
// Cache is a cache for storing auth configurations.
Cache cache.Expirable[cache.StoreObject[authn.Authenticator]]
}

// Manager is a login manager for various registry providers.
Expand Down Expand Up @@ -141,65 +139,24 @@ func (m *Manager) WithACRClient(c *azure.Client) *Manager {
// Login performs authentication against a registry and returns the Authenticator.
// For generic registry provider, it is no-op.
func (m *Manager) Login(ctx context.Context, url string, ref name.Reference, opts ProviderOptions) (authn.Authenticator, error) {
provider := ImageRegistryProvider(url, ref)
var (
key string
err error
)
if opts.Cache != nil {
key, err = m.keyFromURL(url, provider)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "failed to get cache key")
} else {
auth, exists, err := getObjectFromCache(opts.Cache, key)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "failed to get auth object from cache")
}
if exists {
return auth, nil
}
}
}
auth, _, err := m.LoginWithExpiry(ctx, url, ref, opts)
return auth, err
}

// LoginWithExpiry performs authentication against a registry and returns the
// Authenticator along with the auth expiry time.
// For generic registry provider, it is no-op.
func (m *Manager) LoginWithExpiry(ctx context.Context, url string, ref name.Reference, opts ProviderOptions) (authn.Authenticator, time.Time, error) {
provider := ImageRegistryProvider(url, ref)
switch provider {
case oci.ProviderAWS:
auth, expiresAt, err := m.ecr.LoginWithExpiry(ctx, opts.AwsAutoLogin, url)
if err != nil {
return nil, err
}
if opts.Cache != nil {
err := cacheObject(opts.Cache, auth, key, expiresAt)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "failed to cache auth object")
}
}
return auth, nil
return m.ecr.LoginWithExpiry(ctx, opts.AwsAutoLogin, url)
case oci.ProviderGCP:
auth, expiresAt, err := m.gcr.LoginWithExpiry(ctx, opts.GcpAutoLogin, url, ref)
if err != nil {
return nil, err
}
if opts.Cache != nil {
err := cacheObject(opts.Cache, auth, key, expiresAt)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "failed to cache auth object")
}
}
return auth, nil
return m.gcr.LoginWithExpiry(ctx, opts.GcpAutoLogin, url, ref)
case oci.ProviderAzure:
auth, expiresAt, err := m.acr.LoginWithExpiry(ctx, opts.AzureAutoLogin, url, ref)
if err != nil {
return nil, err
}
if opts.Cache != nil {
err := cacheObject(opts.Cache, auth, key, expiresAt)
if err != nil {
logr.FromContextOrDiscard(ctx).Error(err, "failed to cache auth object")
}
}
return auth, nil
return m.acr.LoginWithExpiry(ctx, opts.AzureAutoLogin, url, ref)
}
return nil, nil
return nil, time.Time{}, nil
}

// OIDCLogin attempts to get an Authenticator for the provided URL endpoint.
Expand Down Expand Up @@ -236,28 +193,3 @@ func (m *Manager) OIDCLogin(ctx context.Context, registryURL string, opts Provid
}
return nil, nil
}

// keyFromURL returns a key for the cache based on the URL and provider.
// Use this when you don't want to cache the full URL,
// but instead want to cache based on the provider secific way of identifying
// the authentication principal, i.e. the Domain for AWS and Azure, Project for GCP.
func (m *Manager) keyFromURL(ref string, provider oci.Provider) (string, error) {
if !strings.Contains(ref, "://") {
ref = fmt.Sprintf("//%s", ref)
}
u, err := url.Parse(ref)
if err != nil {
return "", err
}
switch provider {
case oci.ProviderAWS, oci.ProviderAzure:
return u.Host, nil
case oci.ProviderGCP:
paths := strings.Split(u.Path, "/")
if len(paths) > 1 {
return fmt.Sprintf("%s/%s", u.Host, paths[1]), nil
}
return u.Host, nil
}
return "", nil
}
147 changes: 0 additions & 147 deletions oci/auth/login/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,16 @@ package login

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
. "github.com/onsi/gomega"

"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/oci"
"github.com/fluxcd/pkg/oci/auth/aws"
"github.com/fluxcd/pkg/oci/auth/azure"
Expand Down Expand Up @@ -165,146 +161,3 @@ func TestLogin(t *testing.T) {
})
}
}

func TestLogin_WithCache(t *testing.T) {
timestamp := time.Now().Add(10 * time.Second).Unix()
tests := []struct {
name string
responseBody string
statusCode int
providerOpts ProviderOptions
beforeFunc func(serverURL string, mgr *Manager, image *string)
wantErr bool
}{
{
name: "ecr",
responseBody: fmt.Sprintf(`{"authorizationData": [{"authorizationToken": "c29tZS1rZXk6c29tZS1zZWNyZXQ=","expiresAt": %d}]}`, timestamp),
providerOpts: ProviderOptions{AwsAutoLogin: true},
beforeFunc: func(serverURL string, mgr *Manager, image *string) {
// Create ECR client and configure the manager.
ecrClient := aws.NewClient()
cfg := awssdk.NewConfig()
cfg.EndpointResolverWithOptions = awssdk.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (awssdk.Endpoint, error) {
return awssdk.Endpoint{URL: serverURL}, nil
})
cfg.Credentials = credentials.NewStaticCredentialsProvider("x", "y", "z")
ecrClient.WithConfig(cfg)

mgr.WithECRClient(ecrClient)

*image = "012345678901.dkr.ecr.us-east-1.amazonaws.com/foo:v1"
},
},
{
name: "gcr",
responseBody: `{"access_token": "some-token","expires_in": 10, "token_type": "foo"}`,
providerOpts: ProviderOptions{GcpAutoLogin: true},
beforeFunc: func(serverURL string, mgr *Manager, image *string) {
// Create GCR client and configure the manager.
gcrClient := gcp.NewClient().WithTokenURL(serverURL)
mgr.WithGCRClient(gcrClient)

*image = "gcr.io/foo/bar:v1"
},
},
{
name: "acr",
responseBody: `{"refresh_token": "bbbbb"}`,
providerOpts: ProviderOptions{AzureAutoLogin: true},
beforeFunc: func(serverURL string, mgr *Manager, image *string) {
acrClient := azure.NewClient().WithTokenCredential(&azure.FakeTokenCredential{Token: "foo"}).WithScheme("http")
mgr.WithACRClient(acrClient)

*image = "foo.azurecr.io/bar:v1"
},
// NOTE: This fails because the azure exchanger uses the image host
// to exchange token which can't be modified here without
// interfering image name based categorization of the login
// provider, that's actually being tested here. This is tested in
// detail in the azure package.
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

// Create test server.
handler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(tt.responseBody))
}
srv := httptest.NewServer(http.HandlerFunc(handler))
t.Cleanup(func() {
srv.Close()
})

mgr := NewManager()
var image string

if tt.beforeFunc != nil {
tt.beforeFunc(srv.URL, mgr, &image)
}

ref, err := name.ParseReference(image)
g.Expect(err).ToNot(HaveOccurred())

cache, err := cache.New(5, cache.StoreObjectKeyFunc,
cache.WithCleanupInterval[cache.StoreObject[authn.Authenticator]](1*time.Second))
g.Expect(err).ToNot(HaveOccurred())

tt.providerOpts.Cache = cache

_, err = mgr.Login(context.TODO(), image, ref, tt.providerOpts)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
} else {
key, err := mgr.keyFromURL(image, ImageRegistryProvider(image, ref))
g.Expect(err).ToNot(HaveOccurred())
auth, exists, err := getObjectFromCache(cache, key)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(exists).To(BeTrue())
g.Expect(auth).ToNot(BeNil())
obj, _, err := cache.GetByKey(key)
g.Expect(err).ToNot(HaveOccurred())
expiration, err := cache.GetExpiration(obj)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(expiration).ToNot(BeZero())
g.Expect(expiration).To(BeTemporally("~", time.Unix(timestamp, 0), 1*time.Second))
}
})
}
}

func Test_keyFromURL(t *testing.T) {
tests := []struct {
name string
image string
want string
}{
{"gcr", "gcr.io/foo/bar:v1", "gcr.io/foo"},
{"ecr", "012345678901.dkr.ecr.us-east-1.amazonaws.com/foo:v1", "012345678901.dkr.ecr.us-east-1.amazonaws.com"},
{"ecr-root", "012345678901.dkr.ecr.us-east-1.amazonaws.com", "012345678901.dkr.ecr.us-east-1.amazonaws.com"},
{"ecr-root with slash", "012345678901.dkr.ecr.us-east-1.amazonaws.com/", "012345678901.dkr.ecr.us-east-1.amazonaws.com"},
{"gcr", "gcr.io/foo/bar:v1", "gcr.io/foo"},
{"gcr-root", "gcr.io", "gcr.io"},
{"acr", "foo.azurecr.io/bar:v1", "foo.azurecr.io"},
{"acr-root", "foo.azurecr.io", "foo.azurecr.io"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

// Trim suffix to allow parsing it as reference without modifying
// the given image address.
ref, err := name.ParseReference(strings.TrimSuffix(tt.image, "/"))
g.Expect(err).ToNot(HaveOccurred())
key, err := NewManager().keyFromURL(tt.image, ImageRegistryProvider(tt.image, ref))
g.Expect(err).ToNot(HaveOccurred())
g.Expect(key).To(Equal(tt.want))
})
}
}
Loading

0 comments on commit 02d44f7

Please sign in to comment.