Skip to content

Commit

Permalink
Add containerd support (#31)
Browse files Browse the repository at this point in the history
* Added containerd support

Context:

Since kubernetes 1.25 docker is being removed as the default container runtime in favor of containerd.

Due this is desired to have a mechanism to parse containerd auth files following the spec defined here:

https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration

On this PR is being implemented the necesary code to parse the containerd toml files and login on the registries defined on them.

Change-Id: Ic38ad5ba981cbea32aa2dd2aecd312409f5516a2
Co-authored-by: Javier Avila <[email protected]>
Co-developed-by: Javier Avila <[email protected]>

* fixup! Added containerd support

* fixup! Added containerd support

---------

Co-authored-by: Javier Avila <[email protected]>
  • Loading branch information
miguelbernadi and javilaadevinta authored Nov 9, 2023
1 parent 28dfe3e commit ad52603
Show file tree
Hide file tree
Showing 7 changed files with 514 additions and 7 deletions.
12 changes: 11 additions & 1 deletion charts/noe/templates/webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ spec:
hostPath:
path: {{ $path }}
type: File
{{ end }}
{{ range $i, $path := .Values.containerdConfigPathCandidates }}
- name: containerd-config-{{ $i }}
hostPath:
path: {{ $path }}
{{ end }}
containers:
- name: webhook
Expand All @@ -63,4 +68,9 @@ spec:
- name: docker-config-{{ $i }}
mountPath: {{ $path }}
readOnly: true
{{ end }}
{{ end }}
{{ range $i, $path := .Values.containerdConfigPathCandidates }}
- name: containerd-config-{{ $i }}
mountPath: {{ $path }}
readOnly: true
{{ end }}
6 changes: 5 additions & 1 deletion charts/noe/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ proxies: []
# - quay.io=quay-proxy.company.corp
matchNodeLabels: []
# - accelerator.node.kubernetes.io/inference
# - accelerator.node.kubernetes.io/gpu
# - accelerator.node.kubernetes.io/gpu

containerdConfigPathCandidates:
- /etc/containerd

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ require (
github.com/abbot/go-http-auth v0.4.0
github.com/go-logr/logr v1.2.4
github.com/guseggert/pkggodev-client v0.0.0-20211029144512-2df8afe3ebe4
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.15.1
github.com/prometheus/client_model v0.4.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5
github.com/stretchr/testify v1.8.4
golang.org/x/mod v0.10.0
gomodules.xyz/jsonpatch/v2 v2.4.0
Expand Down
329 changes: 329 additions & 0 deletions go.sum

Large diffs are not rendered by default.

86 changes: 85 additions & 1 deletion pkg/registry/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,28 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/adevinta/noe/pkg/log"
"github.com/pelletier/go-toml"
"github.com/spf13/afero"
)

type ContainerdHostConfig struct {
Capabilities []string `toml:"capabilities"`
Header ContainerdHeader `toml:"header"`
}

type ContainerdHeader struct {
Authorization string `toml:"authorization"`
}

type ContainerdConfig struct {
Server string `toml:"server"`
Hosts map[string]ContainerdHostConfig `toml:"host"`
}

type DockerAuths map[string]DockerAuth

type DockerAuth struct {
Expand All @@ -32,13 +49,20 @@ type AuthenticationToken struct {
Token string
}

type ContainerdServerHeader struct {
Server string
Header string
}

type Authenticator interface {
Authenticate(ctx context.Context, imagePullSecret, registry, image, tag string) chan AuthenticationToken
}

var _ Authenticator = RegistryAuthenticator{}

type RegistryAuthenticator struct{}
type RegistryAuthenticator struct {
fs afero.Fs
}

func (r RegistryAuthenticator) parseDockerConfig(reader io.ReadCloser) (DockerConfig, error) {
defer reader.Close()
Expand Down Expand Up @@ -94,6 +118,56 @@ func (r RegistryAuthenticator) readDockerConfig() DockerConfig {
return DockerConfig{}
}

func (r RegistryAuthenticator) getHeaderOnContainerdFiles(repository, directory string) (ContainerdServerHeader, error) {
var matchedServerHeader ContainerdServerHeader

err := afero.Walk(r.fs, directory, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}

if info.IsDir() {
return nil
}

fileExtension := filepath.Ext(path)
if fileExtension != ".toml" {
return nil
}

configData, err := afero.ReadFile(r.fs, path)
if err != nil {
return nil
}

config := ContainerdConfig{}
err = toml.Unmarshal(configData, &config)
if err != nil {
return nil
}

if match, _ := regexp.MatchString(repository, config.Server); match {
log.DefaultLogger.Printf("Get containerd auth for %s", config.Server)
for _, hostConfig := range config.Hosts {
header := strings.TrimPrefix(hostConfig.Header.Authorization, "Basic ")
matchedServerHeader = ContainerdServerHeader{
Server: config.Server,
Header: header,
}
return nil
}
}

return nil
})

if err != nil {
return ContainerdServerHeader{}, err
}

return matchedServerHeader, nil
}

func (r RegistryAuthenticator) getAuthCandidates(ctx context.Context, cfg DockerConfig, registry, image string) chan string {
candidates := make(chan string)
go func() {
Expand Down Expand Up @@ -140,6 +214,16 @@ func (r RegistryAuthenticator) getAuthCandidates(ctx context.Context, cfg Docker
}
}
}
containerdAuth, _ := r.getHeaderOnContainerdFiles(registry, "/etc/containerd")
if containerdAuth.Header != "" {
log.DefaultLogger.WithContext(ctx).WithField("registry", containerdAuth.Server).WithField("image", fmt.Sprintf("%s/%s", registry, image)).Printf("Image matches registry config. Trying it")
select {
case candidates <- containerdAuth.Header:
case <-ctx.Done():
return
}
}

}()
return candidates
}
Expand Down
77 changes: 77 additions & 0 deletions pkg/registry/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package registry

import (
"context"
"testing"

"github.com/pelletier/go-toml"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)

func TestAuthenticateWithimagePullSecret(t *testing.T) {
imagePullSecret := `{"auths":{"registry.example.com":{"username":"user","password":"pass","auth":"YXV0aDp1c2VyOnBhc3M="}}}`
registry := "registry.example.com"
image := "myimage"
tag := "latest"

authenticator := RegistryAuthenticator{fs: afero.NewMemMapFs()} // Create an instance of the RegistryAuthenticator

candidates := authenticator.Authenticate(context.Background(), imagePullSecret, registry, image, tag)

receivedToken, ok := <-candidates
assert.True(t, ok, "AuthenticationToken not received")

expectedToken := AuthenticationToken{
Kind: "Basic",
Token: "YXV0aDp1c2VyOnBhc3M=",
}

assert.Equal(t, expectedToken, receivedToken)

}

func TestRegistryAuthenticator_GetHeaderOnContainerdFiles(t *testing.T) {
fs := afero.NewMemMapFs()

// Create test directory and files in the in-memory file system
err := fs.MkdirAll("/etc/containerd", 0755)
assert.NoError(t, err)

config := ContainerdConfig{
Server: "registry.example.com",
Hosts: map[string]ContainerdHostConfig{
"example-host": {
Capabilities: []string{"cap1", "cap2"},
Header: ContainerdHeader{
Authorization: "Basic dXNlcjpwYXNz",
},
},
},
}

configData, err := toml.Marshal(config)
assert.NoError(t, err)

err = afero.WriteFile(fs, "/etc/containerd/config.toml", configData, 0644)
assert.NoError(t, err)

authenticator := RegistryAuthenticator{fs: fs} // Create an instance of the RegistryAuthenticator

imagePullSecret := ""
registry := "registry.example.com"
image := "myimage"
tag := "latest"

candidates := authenticator.Authenticate(context.Background(), imagePullSecret, registry, image, tag)

receivedToken, ok := <-candidates
assert.True(t, ok, "AuthenticationToken not received")

expectedToken := AuthenticationToken{
Kind: "Basic",
Token: "dXNlcjpwYXNz",
}

assert.Equal(t, expectedToken, receivedToken)
}
9 changes: 5 additions & 4 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/adevinta/noe/pkg/httputils"
"github.com/adevinta/noe/pkg/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
)

type Registry interface {
Expand All @@ -26,7 +27,7 @@ var DefaultRegistry = NewPlainRegistry()
func NewPlainRegistry(builders ...func(*PlainRegistry)) PlainRegistry {
r := PlainRegistry{
Scheme: "https",
Authenticator: RegistryAuthenticator{},
Authenticator: RegistryAuthenticator{fs: afero.NewMemMapFs()},
Proxies: []RegistryProxy{},
}
for _, builder := range builders {
Expand Down Expand Up @@ -276,12 +277,12 @@ func (r PlainRegistry) ListArchs(ctx context.Context, imagePullSecret, image str
// Ensure that the pointed image is available
req, err := newGetManifestRequest(ctx, r.Scheme, registry, image, manifest.Digest, auth)
if err != nil {
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manigest for arch %s of %s/%s:%v. Skipping\n", manifest.Platform.Architecture, registry, image, err)
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manifest for arch %s of %s/%s:%v. Skipping\n", manifest.Platform.Architecture, registry, image, err)
continue
}
resp, err := client.Do(req)
if err != nil {
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manigest for arch %s of %s/%s: %v. Skipping\n", manifest.Platform.Architecture, registry, image, err)
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manifest for arch %s of %s/%s: %v. Skipping\n", manifest.Platform.Architecture, registry, image, err)
continue
}
resp.Body.Close()
Expand All @@ -294,7 +295,7 @@ func (r PlainRegistry) ListArchs(ctx context.Context, imagePullSecret, image str
}
platforms = append(platforms, manifest.Platform)
} else {
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manigest for arch %s of %s/%s: statusCode: %d. Skipping\n", manifest.Platform.Architecture, registry, image, resp.StatusCode)
log.DefaultLogger.WithContext(ctx).Printf("failed to get pointed manifest for arch %s of %s/%s: statusCode: %d. Skipping\n", manifest.Platform.Architecture, registry, image, resp.StatusCode)
}
}
default:
Expand Down

0 comments on commit ad52603

Please sign in to comment.