talos/internal/pkg/containers/image/resolver_test.go
Andrey Smirnov e1779ac77c feat: implement registry mirror & config for image pull
When images are pulled by Talos or via CRI plugin, configuration
for each registry is applied. Mirrors allow to redirect pull request to
either local registry or cached registry. Auth & TLS enable
authentication and TLS authentication for non-public registries.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2020-02-14 00:28:59 +03:00

226 lines
7.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package image_test
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/internal/pkg/containers/image"
"github.com/talos-systems/talos/pkg/config/machine"
)
type mockConfig struct {
mirrors map[string]machine.RegistryMirrorConfig
config map[string]machine.RegistryConfig
}
func (c *mockConfig) Mirrors() map[string]machine.RegistryMirrorConfig {
return c.mirrors
}
func (c *mockConfig) Config() map[string]machine.RegistryConfig {
return c.config
}
func (c *mockConfig) ExtraFiles() ([]machine.File, error) {
return nil, fmt.Errorf("not implemented")
}
type ResolverSuite struct {
suite.Suite
}
func (suite *ResolverSuite) TestRegistryEndpoints() {
// defaults
endpoints, err := image.RegistryEndpoints(&mockConfig{}, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://registry-1.docker.io"}, endpoints)
endpoints, err = image.RegistryEndpoints(&mockConfig{}, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://quay.io"}, endpoints)
// overrides without catch-all
cfg := &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000", "https://some.host"},
},
},
}
endpoints, err = image.RegistryEndpoints(cfg, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints)
endpoints, err = image.RegistryEndpoints(cfg, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://quay.io"}, endpoints)
// overrides with catch-all
cfg = &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000", "https://some.host"},
},
"*": {
Endpoints: []string{"http://127.0.0.1:5001"},
},
},
}
endpoints, err = image.RegistryEndpoints(cfg, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints)
endpoints, err = image.RegistryEndpoints(cfg, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5001"}, endpoints)
}
func (suite *ResolverSuite) TestPrepareAuth() {
user, pass, err := image.PrepareAuth(nil, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
}, "docker.io", "not.docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("root", user)
suite.Assert().Equal("secret", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
IdentityToken: "xyz",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("xyz", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Auth: "dXNlcjE6c2VjcmV0MQ==",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("user1", user)
suite.Assert().Equal("secret1", pass)
_, _, err = image.PrepareAuth(&machine.RegistryAuthConfig{}, "docker.io", "docker.io")
suite.Assert().EqualError(err, "invalid auth config for \"docker.io\"")
}
func (suite *ResolverSuite) TestRegistryHosts() {
registryHosts, err := image.RegistryHosts(&mockConfig{})("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 1)
suite.Assert().Equal("https", registryHosts[0].Scheme)
suite.Assert().Equal("registry-1.docker.io", registryHosts[0].Host)
suite.Assert().Equal("/v2", registryHosts[0].Path)
suite.Assert().Nil(registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
cfg := &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000/docker.io", "https://some.host"},
},
},
}
registryHosts, err = image.RegistryHosts(cfg)("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 2)
suite.Assert().Equal("http", registryHosts[0].Scheme)
suite.Assert().Equal("127.0.0.1:5000", registryHosts[0].Host)
suite.Assert().Equal("/docker.io", registryHosts[0].Path)
suite.Assert().Nil(registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
suite.Assert().Equal("https", registryHosts[1].Scheme)
suite.Assert().Equal("some.host", registryHosts[1].Host)
suite.Assert().Equal("/v2", registryHosts[1].Path)
suite.Assert().Nil(registryHosts[1].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
cfg = &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"https://some.host:123"},
},
},
config: map[string]machine.RegistryConfig{
"some.host:123": {
TLS: &machine.RegistryTLSConfig{
CA: []byte(caCertMock),
// ClientIdentity: &x509.PEMEncodedCertificateAndKey{},
},
Auth: &machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
},
},
},
}
registryHosts, err = image.RegistryHosts(cfg)("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 1)
suite.Assert().Equal("https", registryHosts[0].Scheme)
suite.Assert().Equal("some.host:123", registryHosts[0].Host)
suite.Assert().Equal("/v2", registryHosts[0].Path)
tlsClientConfig := registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig //nolint: errcheck
suite.Require().NotNil(tlsClientConfig)
suite.Require().NotNil(tlsClientConfig.RootCAs)
suite.Require().Empty(tlsClientConfig.Certificates)
suite.Require().NotNil(registryHosts[0].Authorizer)
req, err := http.NewRequest("GET", "htts://some.host:123/v2", nil)
suite.Require().NoError(err)
resp := &http.Response{}
resp.Request = req
resp.Header = http.Header{}
resp.Header.Add(http.CanonicalHeaderKey("WWW-Authenticate"), "Basic realm=\"Access to the staging site\", charset=\"UTF-8\"")
suite.Require().NoError(registryHosts[0].Authorizer.AddResponses(context.Background(), []*http.Response{resp}))
suite.Require().NoError(registryHosts[0].Authorizer.Authorize(context.Background(), req))
suite.Assert().Equal("Basic cm9vdDpzZWNyZXQ=", req.Header.Get("Authorization"))
}
func TestResolverSuite(t *testing.T) {
suite.Run(t, new(ResolverSuite))
}
const caCertMock = `-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----
`