mirror of
https://github.com/genuinetools/reg.git
synced 2024-04-27 19:08:32 -04:00
add clair integration tests
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
45db00a4fa
commit
73712bf067
11
Dockerfile.clair
Normal file
11
Dockerfile.clair
Normal file
|
@ -0,0 +1,11 @@
|
|||
FROM quay.io/coreos/clair
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates
|
||||
|
||||
COPY testutils/snakeoil/cert.pem /usr/local/share/ca-certificates/clair.pem
|
||||
|
||||
# normally we'd use update-ca-certificates, but something about running it in
|
||||
# Alpine is off, and the certs don't get added. Fortunately, we only need to
|
||||
# add ca-certificates to the global store and it's all plain text.
|
||||
RUN cat /usr/local/share/ca-certificates/* >> /etc/ssl/certs/ca-certificates.crt
|
15
main_test.go
15
main_test.go
|
@ -62,11 +62,18 @@ func TestMain(m *testing.M) {
|
|||
panic(fmt.Errorf("could not connect to docker: %v", err))
|
||||
}
|
||||
|
||||
// start the clair containers.
|
||||
dbID, clairID, err := testutils.StartClair(dcli)
|
||||
if err != nil {
|
||||
testutils.RemoveContainer(dcli, dbID, clairID)
|
||||
panic(fmt.Errorf("starting clair containers failed: %v", err))
|
||||
}
|
||||
|
||||
for _, regConfig := range registryConfigs {
|
||||
// start each registry
|
||||
regID, _, err := testutils.StartRegistry(dcli, regConfig.config, regConfig.username, regConfig.password)
|
||||
if err != nil {
|
||||
testutils.RemoveContainer(dcli, regID)
|
||||
testutils.RemoveContainer(dcli, dbID, clairID, regID)
|
||||
panic(fmt.Errorf("starting registry container %s failed: %v", regConfig.config, err))
|
||||
}
|
||||
|
||||
|
@ -79,11 +86,17 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
if merr != 0 {
|
||||
testutils.RemoveContainer(dcli, dbID, clairID)
|
||||
fmt.Printf("testing config %s failed\n", regConfig.config)
|
||||
os.Exit(merr)
|
||||
}
|
||||
}
|
||||
|
||||
// remove clair containers.
|
||||
if err := testutils.RemoveContainer(dcli, dbID, clairID); err != nil {
|
||||
log.Printf("couldn't remove clair containers: %v", err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,8 @@ func (r *Registry) Headers(uri string) (map[string]string, error) {
|
|||
}
|
||||
|
||||
if len(token) < 1 {
|
||||
return nil, fmt.Errorf("got empty token for %s", uri)
|
||||
r.Logf("got empty token for %s", uri)
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
|
|
75
testutils/configs/clair.yml
Normal file
75
testutils/configs/clair.yml
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Copyright 2015 clair 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.
|
||||
|
||||
# The values specified here are the default values that Clair uses if no configuration file is specified or if the keys are not defined.
|
||||
clair:
|
||||
database:
|
||||
# Database driver
|
||||
type: pgsql
|
||||
options:
|
||||
# PostgreSQL Connection string
|
||||
# https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
source: postgresql://hacker:password@127.0.0.1:5432/clair?sslmode=disable&statement_timeout=60000
|
||||
|
||||
# Number of elements kept in the cache
|
||||
# Values unlikely to change (e.g. namespaces) are cached in order to save prevent needless roundtrips to the database.
|
||||
cachesize: 16384
|
||||
|
||||
api:
|
||||
# API server port
|
||||
addr: "0.0.0.0:6060"
|
||||
|
||||
# Health server port
|
||||
# This is an unencrypted endpoint useful for load balancers to check to healthiness of the clair server.
|
||||
healthaddr: "0.0.0.0:6061"
|
||||
|
||||
# Deadline before an API request will respond with a 503
|
||||
timeout: 900s
|
||||
|
||||
# Optional PKI configuration
|
||||
# If you want to easily generate client certificates and CAs, try the following projects:
|
||||
# https://github.com/coreos/etcd-ca
|
||||
# https://github.com/cloudflare/cfssl
|
||||
servername:
|
||||
cafile:
|
||||
keyfile:
|
||||
certfile:
|
||||
|
||||
updater:
|
||||
# Frequency the database will be updated with vulnerabilities from the default data sources
|
||||
# The value 0 disables the updater entirely.
|
||||
interval: 2h
|
||||
|
||||
notifier:
|
||||
# Number of attempts before the notification is marked as failed to be sent
|
||||
attempts: 3
|
||||
|
||||
# Duration before a failed notification is retried
|
||||
renotifyinterval: 2h
|
||||
|
||||
http:
|
||||
# Optional endpoint that will receive notifications via POST requests
|
||||
endpoint:
|
||||
|
||||
# Optional PKI configuration
|
||||
# If you want to easily generate client certificates and CAs, try the following projects:
|
||||
# https://github.com/cloudflare/cfssl
|
||||
# https://github.com/coreos/etcd-ca
|
||||
servername:
|
||||
cafile:
|
||||
keyfile:
|
||||
certfile:
|
||||
|
||||
# Optional HTTP Proxy: must be a valid URL (including the scheme).
|
||||
proxy:
|
|
@ -1,6 +1,9 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
@ -8,6 +11,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -50,6 +54,9 @@ func StartRegistry(dcli *client.Client, config, username, password string) (stri
|
|||
filepath.Join(filepath.Dir(filename), "configs", "htpasswd") + ":" + "/etc/docker/registry/htpasswd" + ":ro",
|
||||
filepath.Join(filepath.Dir(filename), "snakeoil") + ":" + "/etc/docker/registry/ssl" + ":ro",
|
||||
},
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
Name: "always",
|
||||
},
|
||||
},
|
||||
nil, "")
|
||||
if err != nil {
|
||||
|
@ -73,7 +80,7 @@ func StartRegistry(dcli *client.Client, config, username, password string) (stri
|
|||
}
|
||||
|
||||
// Prefill the images.
|
||||
images := []string{"alpine:latest", "busybox:latest", "busybox:musl", "busybox:glibc"}
|
||||
images := []string{"alpine:3.5", "alpine:latest", "busybox:latest", "busybox:musl", "busybox:glibc"}
|
||||
for _, image := range images {
|
||||
if err := prefillRegistry(image, dcli, "localhost"+port, username, password); err != nil {
|
||||
return r.ID, addr, err
|
||||
|
@ -83,13 +90,115 @@ func StartRegistry(dcli *client.Client, config, username, password string) (stri
|
|||
return r.ID, addr, nil
|
||||
}
|
||||
|
||||
func startClairDB(dcli *client.Client) (string, error) {
|
||||
image := "postgres:latest"
|
||||
|
||||
if err := pullDockerImage(dcli, image); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
c, err := dcli.ContainerCreate(
|
||||
context.Background(),
|
||||
&container.Config{
|
||||
Image: image,
|
||||
Env: []string{
|
||||
"POSTGRES_PASSWORD=password",
|
||||
"POSTGRES_DB=clair",
|
||||
"POSTGRES_USER=hacker",
|
||||
},
|
||||
},
|
||||
&container.HostConfig{
|
||||
NetworkMode: "host",
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
Name: "always",
|
||||
},
|
||||
},
|
||||
nil, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start the container
|
||||
return c.ID, dcli.ContainerStart(context.Background(), c.ID, types.ContainerStartOptions{})
|
||||
}
|
||||
|
||||
// StartClair starts a new clair container and accompanying database.
|
||||
func StartClair(dcli *client.Client) (string, string, error) {
|
||||
_, filename, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return "", "", errors.New("No caller information")
|
||||
}
|
||||
|
||||
// start the database container.
|
||||
dbID, err := startClairDB(dcli)
|
||||
if err != nil {
|
||||
return dbID, "", err
|
||||
}
|
||||
|
||||
image := "clair:dev"
|
||||
|
||||
// build the docker image
|
||||
// create the tar ball
|
||||
ctx := filepath.Dir(filepath.Dir(filename))
|
||||
tw, err := tarit(ctx)
|
||||
if err != nil {
|
||||
return dbID, "", err
|
||||
}
|
||||
|
||||
// build the image
|
||||
resp, err := dcli.ImageBuild(context.Background(), tw, types.ImageBuildOptions{
|
||||
Tags: []string{image},
|
||||
Dockerfile: "Dockerfile.clair",
|
||||
ForceRemove: true,
|
||||
Remove: true,
|
||||
SuppressOutput: false,
|
||||
PullParent: true,
|
||||
})
|
||||
if err != nil {
|
||||
return dbID, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
c, err := dcli.ContainerCreate(
|
||||
context.Background(),
|
||||
&container.Config{
|
||||
Image: image,
|
||||
},
|
||||
&container.HostConfig{
|
||||
NetworkMode: "host",
|
||||
Binds: []string{
|
||||
filepath.Join(filepath.Dir(filename), "configs", "clair.yml") + ":" + "/etc/clair/config.yaml" + ":ro",
|
||||
},
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
Name: "always",
|
||||
},
|
||||
},
|
||||
nil, "")
|
||||
if err != nil {
|
||||
return dbID, c.ID, err
|
||||
}
|
||||
|
||||
// start the container
|
||||
err = dcli.ContainerStart(context.Background(), c.ID, types.ContainerStartOptions{})
|
||||
|
||||
// wait for clair to start
|
||||
// TODO: make this not a sleep
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
return dbID, c.ID, err
|
||||
}
|
||||
|
||||
// RemoveContainer removes with force a container by it's container ID.
|
||||
func RemoveContainer(dcli *client.Client, ctrID string) error {
|
||||
return dcli.ContainerRemove(context.Background(), ctrID,
|
||||
types.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
})
|
||||
func RemoveContainer(dcli *client.Client, ctrs ...string) (err error) {
|
||||
for _, c := range ctrs {
|
||||
err = dcli.ContainerRemove(context.Background(), c,
|
||||
types.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// dockerLogin logins via the command line to a docker registry
|
||||
|
@ -239,3 +348,49 @@ func constructRegistryAuth(identity, secret string) (string, error) {
|
|||
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
func tarit(src string) (io.Reader, error) {
|
||||
s := bytes.NewBuffer(nil)
|
||||
t := bytes.NewBuffer(nil)
|
||||
buf := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(t))
|
||||
tarball := tar.NewWriter(s)
|
||||
|
||||
err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := tar.FileInfoHeader(info, info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Name = strings.TrimPrefix(path, src)
|
||||
|
||||
if err := tarball.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(tarball, file)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := s.WriteTo(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = buf.Writer.Flush()
|
||||
return t, err
|
||||
}
|
||||
|
|
18
vulns_test.go
Normal file
18
vulns_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVulns(t *testing.T) {
|
||||
out, err := run("vulns", "--clair", "http://localhost:6060", "alpine:3.5")
|
||||
if err != nil {
|
||||
t.Fatalf("output: %s, error: %v", string(out), err)
|
||||
}
|
||||
|
||||
expected := `clair.clair resp.Status=200 OK`
|
||||
if !strings.HasSuffix(strings.TrimSpace(out), expected) {
|
||||
t.Logf("expected: %s\ngot: %s", expected, out)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue