This commit is contained in:
kimsungmin1 2026-03-15 10:23:49 -04:00 committed by GitHub
commit dff37018b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 158 additions and 9 deletions

1
go.mod
View file

@ -80,6 +80,7 @@ require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect

2
go.sum
View file

@ -103,6 +103,8 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=

View file

@ -714,6 +714,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
repository.PlainHTTP = c.plainHTTP
repository.Client = c.authorizer
ctx = withScopeHint(ctx, repository, auth.ActionPull, auth.ActionPush)
manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions)
if err != nil {
return nil, err
@ -926,3 +928,17 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store,
return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest,
manifestData, parsedRef.String())
}
// add actions when request a registry authentication token(jwt)
// example1. when we want to pull 'testrepo/local-subchart' we can send bellow url, and 'pull' is the action
// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice
// example2. when we want to push 'testrepo/local-subchart' we can send bellow url, and 'pull%2Cpush' are the actions
// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice
// we can set the actions like bellow
// example) ctx = WithScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull)
func withScopeHint(ctx context.Context, target any, actions ...string) context.Context {
if repo, ok := target.(*remote.Repository); ok {
return auth.AppendRepositoryScope(ctx, repo.Reference, actions...)
}
return ctx
}

View file

@ -31,7 +31,7 @@ type HTTPRegistryClientTestSuite struct {
func (suite *HTTPRegistryClientTestSuite) SetupSuite() {
// init test client
setup(&suite.TestRegistry, false, false)
setup(&suite.TestRegistry, false, false, "htpasswd")
}
func (suite *HTTPRegistryClientTestSuite) TearDownSuite() {

View file

@ -29,7 +29,8 @@ type InsecureTLSRegistryClientTestSuite struct {
func (suite *InsecureTLSRegistryClientTestSuite) SetupSuite() {
// init test client
setup(&suite.TestRegistry, true, true)
setup(&suite.TestRegistry, true, true, "htpasswd")
}
func (suite *InsecureTLSRegistryClientTestSuite) TearDownSuite() {

View file

@ -0,0 +1,107 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/stretchr/testify/suite"
)
type RegistryScopeTestSuite struct {
TestRegistry
}
func (suite *RegistryScopeTestSuite) SetupSuite() {
// set registry use token auth
setup(&suite.TestRegistry, true, true, "token")
}
func (suite *RegistryScopeTestSuite) TearDownSuite() {
teardown(&suite.TestRegistry)
os.RemoveAll(suite.WorkspaceDir)
}
func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() {
var requestURL string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestURL = r.URL.String()
w.WriteHeader(http.StatusOK)
})
listener, err := net.Listen("tcp", suite.AuthServerHost)
suite.NoError(err, "no error creating server listener")
ts := httptest.NewUnstartedServer(handler)
ts.Listener = listener
ts.Start()
defer ts.Close()
// basic push, good ref
testingChartCreationTime := "1977-09-02T22:04:05Z"
chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.NoError(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.NoError(err, "no error extracting chart meta")
ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
_, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime))
suite.Error(err, "error pushing good ref because auth server doesn't give proper token")
//check the url that authentication server received
suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestURL)
}
func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() {
var requestURL string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestURL = r.URL.String()
w.WriteHeader(http.StatusOK)
})
listener, err := net.Listen("tcp", suite.AuthServerHost)
suite.NoError(err, "no error creating server listener")
ts := httptest.NewUnstartedServer(handler)
ts.Listener = listener
ts.Start()
defer ts.Close()
// Load test chart (to build ref pushed in previous test)
// Simple pull, chart only
chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz")
suite.NoError(err, "no error loading test chart")
meta, err := extractChartMeta(chartData)
suite.NoError(err, "no error extracting chart meta")
ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version)
_, err = suite.RegistryClient.Pull(ref)
suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token")
//check the url that authentication server received
suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice", requestURL)
}
func TestRegistryScopeTestSuite(t *testing.T) {
suite.Run(t, new(RegistryScopeTestSuite))
}

View file

@ -31,7 +31,8 @@ type TLSRegistryClientTestSuite struct {
func (suite *TLSRegistryClientTestSuite) SetupSuite() {
// init test client
setup(&suite.TestRegistry, true, false)
setup(&suite.TestRegistry, true, false, "htpasswd")
}
func (suite *TLSRegistryClientTestSuite) TearDownSuite() {

View file

@ -34,6 +34,7 @@ import (
"github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/registry"
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd"
_ "github.com/distribution/distribution/v3/registry/auth/token"
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/require"
@ -56,6 +57,8 @@ var (
testHtpasswdFileBasename = "authtest.htpasswd"
testUsername = "myuser"
testPassword = "mypass"
testIssuer = "testissuer"
testService = "testservice"
)
type TestRegistry struct {
@ -63,13 +66,14 @@ type TestRegistry struct {
Out io.Writer
FakeRegistryHost string
DockerRegistryHost string
AuthServerHost string
CompromisedRegistryHost string
WorkspaceDir string
RegistryClient *Client
dockerRegistry *registry.Registry
}
func setup(suite *TestRegistry, tlsEnabled, insecure bool) {
func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) {
suite.WorkspaceDir = testWorkspaceDir
err := os.RemoveAll(suite.WorkspaceDir)
require.NoError(suite.T(), err, "no error removing test workspace dir")
@ -139,11 +143,28 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) {
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}}
config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
if auth == "token" {
ln, err := net.Listen("tcp", "127.0.0.1:0")
suite.Nil(err, "no error finding free port for test auth server")
//set test auth server host
suite.AuthServerHost = ln.Addr().String()
config.Auth = configuration.Auth{
"token": configuration.Parameters{
"realm": "http://" + suite.AuthServerHost + "/auth",
"service": testService,
"issuer": testIssuer,
"rootcertbundle": tlsServerCert,
},
}
} else {
config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
}
}
// config tls