package integration
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/docker/distribution/manifest/schema1"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"github.com/openshift/origin/pkg/cmd/dockerregistry"
cmdutil "github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/cmd/util/tokencmd"
imageapi "github.com/openshift/origin/pkg/image/api"
testutil "github.com/openshift/origin/test/util"
testserver "github.com/openshift/origin/test/util/server"
)
// gzippedEmptyTar is a gzip-compressed version of an empty tar file
// (1024 NULL bytes)
var gzippedEmptyTar = []byte{
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
}
func runRegistry() error {
config := `version: 0.1
log:
level: debug
http:
addr: 127.0.0.1:5000
storage:
inmemory: {}
auth:
openshift:
middleware:
registry:
- name: openshift
repository:
- name: openshift
options:
acceptschema2: false
pullthrough: true
enforcequota: false
projectcachettl: 1m
blobrepositorycachettl: 10m
storage:
- name: openshift
`
os.Setenv("DOCKER_REGISTRY_URL", "127.0.0.1:5000")
go dockerregistry.Execute(strings.NewReader(config))
if err := cmdutil.WaitForSuccessfulDial(false, "tcp", "127.0.0.1:5000", 100*time.Millisecond, 1*time.Second, 35); err != nil {
return err
}
return nil
}
func testPullThroughGetManifest(stream *imageapi.ImageStreamImport, user, token, urlPart string) error {
url := fmt.Sprintf("http://127.0.0.1:5000/v2/%s/%s/manifests/%s", stream.Namespace, stream.Name, urlPart)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
req.SetBasicAuth(user, token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error retrieving manifest from registry: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
var retrievedManifest schema1.Manifest
if err := json.Unmarshal(body, &retrievedManifest); err != nil {
return fmt.Errorf("error unmarshaling retrieved manifest")
}
return nil
}
func testPullThroughStatBlob(stream *imageapi.ImageStreamImport, user, token, digest string) error {
url := fmt.Sprintf("http://127.0.0.1:5000/v2/%s/%s/blobs/%s", stream.Namespace, stream.Name, digest)
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
req.SetBasicAuth(user, token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("error retrieving manifest from registry: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
if resp.Header.Get("Docker-Content-Digest") != digest {
return fmt.Errorf("unexpected blob digest: %s (expected %s)", resp.Header.Get("Docker-Content-Digest"), digest)
}
return nil
}
func TestPullThroughInsecure(t *testing.T) {
testutil.RequireEtcd(t)
defer testutil.DumpEtcdOnFailure(t)
_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
if err != nil {
t.Fatalf("error starting master: %v", err)
}
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("error getting cluster admin client: %v", err)
}
clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
if err != nil {
t.Fatalf("error getting cluster admin client config: %v", err)
}
user := "admin"
adminClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, testutil.Namespace(), user)
if err != nil {
t.Fatalf("error creating project: %v", err)
}
token, err := tokencmd.RequestToken(clusterAdminClientConfig, nil, user, "password")
if err != nil {
t.Fatalf("error requesting token: %v", err)
}
os.Setenv("OPENSHIFT_CA_DATA", string(clusterAdminClientConfig.CAData))
os.Setenv("OPENSHIFT_CERT_DATA", string(clusterAdminClientConfig.CertData))
os.Setenv("OPENSHIFT_KEY_DATA", string(clusterAdminClientConfig.KeyData))
os.Setenv("OPENSHIFT_MASTER", clusterAdminClientConfig.Host)
// start regular HTTP server
reponame := "testrepo"
repotag := "testtag"
isname := "test/" + reponame
countStat := 0
descriptors := map[string]int64{
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4": 3000,
"sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa": 200,
"sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4": 10,
}
imageSize := int64(0)
for _, size := range descriptors {
imageSize += size
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("External registry got %s %s", r.Method, r.URL.Path)
w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
switch r.URL.Path {
case "/v2/":
w.Write([]byte(`{}`))
case "/v2/" + isname + "/tags/list":
w.Write([]byte("{\"name\": \"" + isname + "\", \"tags\": [\"latest\", \"" + repotag + "\"]}"))
case "/v2/" + isname + "/manifests/latest", "/v2/" + isname + "/manifests/" + repotag, "/v2/" + isname + "/manifests/" + etcdDigest:
if r.Method == "HEAD" {
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(etcdManifest)))
w.Header().Set("Docker-Content-Digest", etcdDigest)
w.WriteHeader(http.StatusOK)
} else {
w.Write([]byte(etcdManifest))
}
default:
if strings.HasPrefix(r.URL.Path, "/v2/"+isname+"/blobs/") {
for dgst, size := range descriptors {
if r.URL.Path != "/v2/"+isname+"/blobs/"+dgst {
continue
}
if r.Method == "HEAD" {
w.Header().Set("Content-Length", fmt.Sprintf("%d", size))
w.Header().Set("Docker-Content-Digest", dgst)
w.WriteHeader(http.StatusOK)
countStat++
return
}
w.Write(gzippedEmptyTar)
return
}
}
t.Fatalf("unexpected request %s: %#v", r.URL.Path, r)
}
}))
srvurl, _ := url.Parse(server.URL)
stream := imageapi.ImageStreamImport{
ObjectMeta: kapi.ObjectMeta{
Namespace: testutil.Namespace(),
Name: "myimagestream",
Annotations: map[string]string{
imageapi.InsecureRepositoryAnnotation: "true",
},
},
Spec: imageapi.ImageStreamImportSpec{
Import: true,
Images: []imageapi.ImageImportSpec{
{
From: kapi.ObjectReference{
Kind: "DockerImage",
Name: srvurl.Host + "/" + isname + ":" + repotag,
},
ImportPolicy: imageapi.TagImportPolicy{Insecure: true},
},
},
},
}
isi, err := adminClient.ImageStreams(testutil.Namespace()).Import(&stream)
if err != nil {
t.Fatal(err)
}
if len(isi.Status.Images) != 1 {
t.Fatalf("imported unexpected number of images (%d != 1)", len(isi.Status.Images))
}
for i, image := range isi.Status.Images {
if image.Status.Status != unversioned.StatusSuccess {
t.Fatalf("unexpected status %d: %#v", i, image.Status)
}
if image.Image == nil {
t.Fatalf("unexpected empty image %d", i)
}
// the image name is always the sha256, and size is calculated
if image.Image.Name != etcdDigest {
t.Fatalf("unexpected image %d: %#v (expect %q)", i, image.Image.Name, etcdDigest)
}
}
istream, err := adminClient.ImageStreams(stream.Namespace).Get(stream.Name)
if err != nil {
t.Fatal(err)
}
if istream.Annotations == nil {
istream.Annotations = make(map[string]string)
}
istream.Annotations[imageapi.InsecureRepositoryAnnotation] = "true"
_, err = adminClient.ImageStreams(istream.Namespace).Update(istream)
if err != nil {
t.Fatal(err)
}
t.Logf("Run registry...")
if err := runRegistry(); err != nil {
t.Fatal(err)
}
t.Logf("Run testPullThroughGetManifest with tag...")
if err := testPullThroughGetManifest(&stream, user, token, repotag); err != nil {
t.Fatal(err)
}
t.Logf("Run testPullThroughGetManifest with digest...")
if err := testPullThroughGetManifest(&stream, user, token, etcdDigest); err != nil {
t.Fatal(err)
}
t.Logf("Run testPullThroughStatBlob (%s == true)...", imageapi.InsecureRepositoryAnnotation)
for digest := range descriptors {
if err := testPullThroughStatBlob(&stream, user, token, digest); err != nil {
t.Fatal(err)
}
}
istream, err = adminClient.ImageStreams(stream.Namespace).Get(stream.Name)
if err != nil {
t.Fatal(err)
}
istream.Annotations[imageapi.InsecureRepositoryAnnotation] = "false"
_, err = adminClient.ImageStreams(istream.Namespace).Update(istream)
if err != nil {
t.Fatal(err)
}
t.Logf("Run testPullThroughStatBlob (%s == false)...", imageapi.InsecureRepositoryAnnotation)
for digest := range descriptors {
if err := testPullThroughStatBlob(&stream, user, token, digest); err == nil {
t.Fatal("unexpexted access to insecure blobs")
}
}
}