Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Add e2e tests for registry authentication
Browse files Browse the repository at this point in the history
- Adds setup-private-registry.sh script to setup a local docker registry
with self-signed cert and basic auth.
- Adds test util to delete image from ignite and the runtime content
store.
- Adds e2e tests for image import with configurations set via flag and
ignite configuration.
- Enable docker registry setup only in semaphore CI for e2e tests.
- Update go version in semaphore to 1.16.3 to use os.WriteFile().
  • Loading branch information
darkowlzz committed May 24, 2021
1 parent 45ee919 commit 12e0919
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ blocks:
task:
env_vars:
- name: GIMME_GO_VERSION
value: "1.14.2"
value: "1.16.3"
jobs:
- name: Tests
commands:
Expand All @@ -32,4 +32,5 @@ blocks:
- make ignite ignite-spawn ignited bin/amd64/Dockerfile GO_MAKE_TARGET=local
- make test
- make root-test
- bash e2e/util/setup-private-registry.sh # This is required for registry auth e2e tests.
- make e2e-nobuild # this depends on Semaphore CI's support for nested virtualization
201 changes: 201 additions & 0 deletions e2e/registry_auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package e2e

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

"gotest.tools/assert"

"github.com/weaveworks/ignite/e2e/util"
"github.com/weaveworks/ignite/pkg/runtime"
)

const (
testOSImage = "localhost:5000/weaveworks/ignite-ubuntu:test"
testKernelImage = "localhost:5000/weaveworks/ignite-kernel:test"
)

// client config with auth info for the registry setup in
// e2e/util/setup-private-registry.sh.
// NOTE: Update the auth token if the credentials in setup-private-registry.sh
// is updated.
const clientConfigContent = `
{
"auths": {
"localhost:5000": {
"auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk"
}
}
}
`

func TestPullFromAuthRegistry(t *testing.T) {
assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")

// Create a client config directory to use in test.
ccDir, err := ioutil.TempDir("", "ignite-test")
assert.NilError(t, err)
defer os.RemoveAll(ccDir)

templateConfig := `---
apiVersion: ignite.weave.works/v1alpha4
kind: Configuration
metadata:
name: test-config
spec:
clientConfigDir: %s
`
igniteConfigContent := fmt.Sprintf(templateConfig, ccDir)

cases := []struct {
name string
runtime runtime.Name
configWithAuthPath string
clientConfigFlag string
igniteConfig string
wantErr bool
}{
{
name: "no auth info - containerd",
runtime: runtime.RuntimeContainerd,
wantErr: true,
},
{
name: "no auth info - docker",
runtime: runtime.RuntimeDocker,
wantErr: true,
},
{
name: "client config flag - containerd",
runtime: runtime.RuntimeContainerd,
configWithAuthPath: ccDir,
clientConfigFlag: ccDir,
},
{
name: "client config flag - docker",
runtime: runtime.RuntimeDocker,
configWithAuthPath: ccDir,
clientConfigFlag: ccDir,
},
{
name: "client config in ignite config - containerd",
runtime: runtime.RuntimeContainerd,
configWithAuthPath: ccDir,
igniteConfig: igniteConfigContent,
},
{
name: "client config in ignite config - docker",
runtime: runtime.RuntimeDocker,
configWithAuthPath: ccDir,
igniteConfig: igniteConfigContent,
},
// Following sets the client config dir to a location without a valid
// client config file, although the client config dir in the ignite
// config is correct, the import fails due to bad configuration by the
// flag override.
{
name: "flag override client config - containerd",
runtime: runtime.RuntimeContainerd,
configWithAuthPath: ccDir,
clientConfigFlag: "/tmp",
igniteConfig: igniteConfigContent,
wantErr: true,
},
{
name: "flag override client config - docker",
runtime: runtime.RuntimeDocker,
configWithAuthPath: ccDir,
clientConfigFlag: "/tmp",
igniteConfig: igniteConfigContent,
wantErr: true,
},
// Following set the client config dir via flag without any actual
// client config. Import fails due to missing auth info in the given
// client config dir.
{
name: "invalid client config - containerd",
runtime: runtime.RuntimeContainerd,
configWithAuthPath: "",
clientConfigFlag: ccDir,
wantErr: true,
},
{
name: "invalid client config - docker",
runtime: runtime.RuntimeDocker,
configWithAuthPath: "",
clientConfigFlag: ccDir,
wantErr: true,
},
}

for _, rt := range cases {
rt := rt
t.Run(rt.name, func(t *testing.T) {
igniteCmd := util.NewCommand(t, igniteBin)

// Remove images from ignite store and runtime store. Remove
// individually because an error in deleting one image cancels the
// whole command.
// TODO: Improve image rm to not fail completely when there are
// multiple images and some are not found.
util.RmiCompletely(testOSImage, igniteCmd, rt.runtime)
util.RmiCompletely(testKernelImage, igniteCmd, rt.runtime)

// Write client config if given.
if len(rt.configWithAuthPath) > 0 {
// Ensure the directory exists and create a config file in the
// directory.
assert.NilError(t, os.MkdirAll(rt.configWithAuthPath, 0755))
configPath := filepath.Join(rt.configWithAuthPath, "config.json")
assert.NilError(t, os.WriteFile(configPath, []byte(clientConfigContent), 0600))
defer os.Remove(configPath)
}

// Write ignite config if provided.
var igniteConfigPath string
if len(rt.igniteConfig) > 0 {
igniteFile, err := ioutil.TempFile("", "ignite-config-file-test")
if err != nil {
t.Fatalf("failed to create a file: %v", err)
}
igniteConfigPath = igniteFile.Name()

_, err = igniteFile.WriteString(rt.igniteConfig)
assert.NilError(t, err)
assert.NilError(t, igniteFile.Close())

defer os.Remove(igniteFile.Name())
}

// Construct the ignite image import command.
imageImportCmdArgs := []string{"--runtime", rt.runtime.String()}
if len(rt.clientConfigFlag) > 0 {
imageImportCmdArgs = append(imageImportCmdArgs, "--client-config-dir", rt.clientConfigFlag)
}
if len(igniteConfigPath) > 0 {
imageImportCmdArgs = append(imageImportCmdArgs, "--ignite-config", igniteConfigPath)
}

// Run image import.
_, importErr := igniteCmd.New().
With("image", "import", testOSImage).
With(imageImportCmdArgs...).
Cmd.CombinedOutput()
if (importErr != nil) != rt.wantErr {
t.Error("expected OS image import to fail")
}

// Run kernel import.
_, importErr = igniteCmd.New().
With("image", "import", testKernelImage).
With(imageImportCmdArgs...).
Cmd.CombinedOutput()
if (importErr != nil) != rt.wantErr {
t.Error("expected kernel image import to fail")
}
})
}
}
40 changes: 40 additions & 0 deletions e2e/util/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package util

import (
"os/exec"

"github.com/weaveworks/ignite/pkg/runtime"
)

// RmiDocker removes an image from docker content store.
func RmiDocker(img string) {
_, _ = exec.Command(
"docker",
"rmi", img,
).CombinedOutput()
}

// RmiContainerd removes an image from containerd content store.
func RmiContainerd(img string) {
_, _ = exec.Command(
"ctr", "-n", "firecracker",
"image", "rm", img,
).CombinedOutput()
}

// rmiCompletely removes a given image completely, from ignite image store and
// runtime image store.
func RmiCompletely(img string, cmd *Command, rt runtime.Name) {
// Remote from ignite content store.
_, _ = cmd.New().
With("image", "rm", img).
Cmd.CombinedOutput()

// Remove from runtime content store.
switch rt {
case runtime.RuntimeContainerd:
RmiContainerd(img)
case runtime.RuntimeDocker:
RmiDocker(img)
}
}
57 changes: 57 additions & 0 deletions e2e/util/setup-private-registry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash

set -e

# This script runs a local private docker registry with self-signed certificate
# and basic auth.

REGISTRY_SECRET_PATH=/tmp/ignite-test-registry
PRIVATE_KEY=${REGISTRY_SECRET_PATH}/certs/domain.key
CERT=${REGISTRY_SECRET_PATH}/certs/domain.crt
HTPASSWD=${REGISTRY_SECRET_PATH}/auth/htpasswd
USERNAME=testuser
PASSWORD=testpassword
REGISTRY_ADDRESS=https://localhost:5000
OS_IMG=weaveworks/ignite-ubuntu:latest
LOCAL_OS_IMG=localhost:5000/weaveworks/ignite-ubuntu:test
KERNEL_IMG=weaveworks/ignite-kernel:5.4.108
LOCAL_KERNEL_IMG=localhost:5000/weaveworks/ignite-kernel:test

# Clear any existing registry secret and create new directories.
rm -rf ${REGISTRY_SECRET_PATH}
mkdir -p ${REGISTRY_SECRET_PATH}/{certs,auth}

# Generate key and cert.
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
-subj "/C=US/ST=Foo/L=Bar/O=Weave" \
-keyout ${PRIVATE_KEY} -out ${CERT}
chmod 400 ${PRIVATE_KEY}

# Create htpasswd file.
docker run --rm \
--entrypoint htpasswd \
httpd:2 -Bbn ${USERNAME} ${PASSWORD} > ${HTPASSWD}

# Run the registry.
docker run -d --rm \
--name registry \
-v ${REGISTRY_SECRET_PATH}/auth:/auth \
-v ${REGISTRY_SECRET_PATH}/certs:/certs \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-p 5000:5000 \
registry:2

# Login, push test images to download in tests and logout.
docker login -u ${USERNAME} -p ${PASSWORD} ${REGISTRY_ADDRESS}
docker pull ${OS_IMG}
docker pull ${KERNEL_IMG}
docker tag ${OS_IMG} ${LOCAL_OS_IMG}
docker tag ${KERNEL_IMG} ${LOCAL_KERNEL_IMG}
docker push ${LOCAL_OS_IMG}
docker push ${LOCAL_KERNEL_IMG}
docker rmi ${LOCAL_OS_IMG} ${LOCAL_KERNEL_IMG}
docker logout ${REGISTRY_ADDRESS}
13 changes: 13 additions & 0 deletions pkg/runtime/containerd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package containerd

import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -159,6 +161,17 @@ func newRemoteResolver(refHostname string, configPath string) (remotes.Resolver,
docker.WithAuthorizer(authz),
}

// TODO: Make this opt-in via a flag option.
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{
Transport: tr,
}
regOpts = append(regOpts, docker.WithClient(client))

// TODO: Add option to skip verifying HTTPS cert.
resolverOpts := docker.ResolverOptions{
Hosts: docker.ConfigureDefaultRegistries(regOpts...),
Expand Down

0 comments on commit 12e0919

Please sign in to comment.