From 8a885636931019eee429fe2e37d23eb88153d4e4 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 14 Apr 2015 10:43:09 +0200 Subject: [PATCH] functional tests: Add and use test-aci-auth-server --- Godeps/Godeps.json | 4 + .../endocode/test-aci-auth-server/lib/aci.go | 128 ++++++++++ .../test-aci-auth-server/lib/commands.go | 23 ++ .../test-aci-auth-server/lib/server.go | 241 ++++++++++++++++++ tests/rkt_auth_test.go | 19 ++ 5 files changed, 415 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/aci.go create mode 100644 Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/commands.go create mode 100644 Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/server.go create mode 100644 tests/rkt_auth_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b69f8d8..c258fb0 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -106,6 +106,10 @@ "ImportPath": "github.com/cznic/zappy", "Rev": "47331054e4f96186e3ff772877c0443909368a45" }, + { + "ImportPath": "github.com/endocode/test-aci-auth-server/lib", + "Rev": "708fae061722c8da788cdacfd460c96f483fb4b6" + }, { "ImportPath": "github.com/gorilla/context", "Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da" diff --git a/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/aci.go b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/aci.go new file mode 100644 index 0000000..a6b1a56 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/aci.go @@ -0,0 +1,128 @@ +package lib + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "time" +) + +type aciToolkit struct { + acTool string + goTool string +} + +func (t *aciToolkit) prepareACI() ([]byte, error) { + dir, err := t.createTree() + if dir != "" { + defer os.RemoveAll(dir) + } + if err != nil { + return nil, fmt.Errorf("failed to build ACI tree: %v", err) + } + if err := t.buildProg(dir); err != nil { + return nil, fmt.Errorf("failed to build test program: %v", err) + } + fn, err := t.buildACI(dir) + if err != nil { + return nil, fmt.Errorf("failed to build ACI: %v", err) + } + defer os.Remove(fn) + contents, err := ioutil.ReadFile(fn) + if err != nil { + return nil, fmt.Errorf("failed to read ACI to memory: %v", err) + } + return contents, nil +} + +const ( + manifestStr = `{"acKind":"ImageManifest","acVersion":"0.5.1+git","name":"testprog","app":{"exec":["/prog"],"user":"0","group":"0"}}` + testProgSrcStr = ` +package main + +import ( + "fmt" + "time" +) + +func main() { + for i := 3; i > 0; i -= 1 { + fmt.Println(i) + time.Sleep(time.Second) + } + fmt.Println("BANG!") +} +` +) + +func (t *aciToolkit) createTree() (string, error) { + aciDir := "ACI" + rootDir := filepath.Join(aciDir, "rootfs") + manifestFile := filepath.Join(aciDir, "manifest") + srcFile := filepath.Join(rootDir, "prog.go") + if err := os.Mkdir(aciDir, 0755); err != nil { + return "", fmt.Errorf("failed to create ACI directory: %v", err) + } + if err := os.Mkdir(rootDir, 0755); err != nil { + return aciDir, fmt.Errorf("failed to create rootfs directory: %v", err) + } + if err := ioutil.WriteFile(manifestFile, []byte(manifestStr), 0644); err != nil { + return "", fmt.Errorf("failed to write manifest: %v", err) + } + if err := ioutil.WriteFile(srcFile, []byte(testProgSrcStr), 0644); err != nil { + return "", fmt.Errorf("failed to write go source: %v", err) + } + return aciDir, nil +} + +func (t *aciToolkit) buildProg(aciDir string) error { + args := []string{ + "go", + "build", + "-o", + "prog", + "./prog.go", + } + dir := filepath.Join(aciDir, "rootfs") + return runTool(t.goTool, args, dir) +} + +func (t *aciToolkit) buildACI(aciDir string) (string, error) { + timedata, err := time.Now().MarshalBinary() + if err != nil { + return "", fmt.Errorf("failed to serialize current date to bytes: %v", err) + } + if err := ioutil.WriteFile(filepath.Join(aciDir, "rootfs", "stamp"), timedata, 0644); err != nil { + return "", fmt.Errorf("failed to write a stamp: %v", err) + } + fn := "prog-build.aci" + args := []string{ + "actool", + "build", + aciDir, + fn, + } + if err := runTool(t.acTool, args, ""); err != nil { + return "", err + } + return fn, nil +} + +func runTool(tool string, args []string, dir string) error { + outBuf := new(bytes.Buffer) + errBuf := new(bytes.Buffer) + cmd := exec.Cmd{ + Path: tool, + Args: args, + Dir: dir, + Stdout: outBuf, + Stderr: errBuf, + } + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to execute `%s %s`: %v\nstdout:\n%v\n\nstderr:\n%v)", args[0], args[1], err, outBuf.String(), errBuf.String()) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/commands.go b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/commands.go new file mode 100644 index 0000000..7eedc34 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/commands.go @@ -0,0 +1,23 @@ +package lib + +import ( + "crypto/tls" + "fmt" + "net/http" +) + +func StartServer(auth Type) (*Server, error) { + return NewServer(auth, 10) +} + +func StopServer(host string) (*http.Response, error) { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: transport} + res, err := client.Post(host, "whatever", nil) + if err != nil { + return nil, fmt.Errorf("failed to send post to %q: %v", host, err) + } + return res, nil +} diff --git a/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/server.go b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/server.go new file mode 100644 index 0000000..9cfd870 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib/server.go @@ -0,0 +1,241 @@ +package lib + +import ( + "crypto/tls" + "encoding/base64" + "fmt" + "net/http" + "net/http/httptest" + "os/exec" + "path/filepath" + "strings" +) + +type Type int + +const ( + None Type = iota + Basic + Oauth +) + +type httpError struct { + code int + message string +} + +func (e *httpError) Error() string { + return fmt.Sprintf("%d: %s", e.code, e.message) +} + +type serverHandler struct { + auth Type + stop chan<- struct{} + msg chan<- string + tools *aciToolkit +} + +func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + w.WriteHeader(http.StatusOK) + h.stop <- struct{}{} + return + case "GET": + // handled later + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + switch h.auth { + case None: + // no auth to do. + case Basic: + payload, httpErr := getAuthPayload(r, "Basic") + if httpErr != nil { + w.WriteHeader(httpErr.code) + h.sendMsg(fmt.Sprintf(`No "Authorization" header: %v`, httpErr.message)) + return + } + creds, err := base64.StdEncoding.DecodeString(string(payload)) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + h.sendMsg(fmt.Sprintf(`Badly formed "Authorization" header`)) + return + } + parts := strings.Split(string(creds), ":") + if len(parts) != 2 { + w.WriteHeader(http.StatusBadRequest) + h.sendMsg(fmt.Sprintf(`Badly formed "Authorization" header (2)`)) + return + } + user := parts[0] + password := parts[1] + if user != "bar" || password != "baz" { + w.WriteHeader(http.StatusUnauthorized) + h.sendMsg(fmt.Sprintf("Bad credentials: %q", string(creds))) + return + } + case Oauth: + payload, httpErr := getAuthPayload(r, "Bearer") + if httpErr != nil { + w.WriteHeader(httpErr.code) + h.sendMsg(fmt.Sprintf(`No "Authorization" header: %v`, httpErr.message)) + return + } + if payload != "sometoken" { + w.WriteHeader(http.StatusUnauthorized) + h.sendMsg(fmt.Sprintf(`Bad token: %q`, payload)) + return + } + default: + panic("Woe is me!") + } + h.sendMsg(fmt.Sprintf("Trying to serve %q", r.URL.String())) + switch filepath.Base(r.URL.Path) { + case "prog.aci": + h.sendMsg(fmt.Sprintf(" serving")) + if data, err := h.tools.prepareACI(); err != nil { + w.WriteHeader(http.StatusInternalServerError) + h.sendMsg(fmt.Sprintf(" failed (%v)", err)) + } else { + w.Write(data) + h.sendMsg(fmt.Sprintf(" done.")) + } + default: + h.sendMsg(fmt.Sprintf(" not found.")) + w.WriteHeader(http.StatusNotFound) + } +} + +func (h *serverHandler) sendMsg(msg string) { + select { + case h.msg <- msg: + default: + } +} + +func getAuthPayload(r *http.Request, authType string) (string, *httpError) { + auth := r.Header.Get("Authorization") + if auth == "" { + err := &httpError{ + code: http.StatusUnauthorized, + message: "No auth", + } + return "", err + } + parts := strings.Split(auth, " ") + if len(parts) != 2 { + err := &httpError{ + code: http.StatusBadRequest, + message: "Malformed auth", + } + return "", err + } + if parts[0] != authType { + err := &httpError{ + code: http.StatusUnauthorized, + message: "Wrong auth", + } + return "", err + } + return parts[1], nil +} + +type Server struct { + Stop <-chan struct{} + Msg <-chan string + Conf string + URL string + handler *serverHandler + http *httptest.Server +} + +func (s *Server) Close() { + s.http.Close() + close(s.handler.msg) + close(s.handler.stop) +} + +func NewServer(auth Type, msgCapacity int) (*Server, error) { + acTool, err := getTool("actool") + if err != nil { + return nil, err + } + goTool, err := getTool("go") + if err != nil { + return nil, err + } + return NewServerWithPaths(auth, msgCapacity, acTool, goTool) +} + +func getTool(tool string) (string, error) { + toolPath, err := exec.LookPath(tool) + if err != nil { + return "", fmt.Errorf("failed to find %s in $PATH: $v", tool, err) + } + absToolPath, err := filepath.Abs(toolPath) + if err != nil { + return "", fmt.Errorf("failed to get absolute path of %s: %v", tool, err) + } + return absToolPath, nil +} + +func NewServerWithPaths(auth Type, msgCapacity int, acTool, goTool string) (*Server, error) { + if !filepath.IsAbs(acTool) { + return nil, fmt.Errorf("path to actool has to be absolute (%s is not)", acTool) + } + if !filepath.IsAbs(goTool) { + return nil, fmt.Errorf("path to go has to be absolute (%s is not)", goTool) + } + stop := make(chan struct{}) + msg := make(chan string, msgCapacity) + server := &Server{ + Stop: stop, + Msg: msg, + handler: &serverHandler{ + auth: auth, + stop: stop, + msg: msg, + tools: &aciToolkit{ + acTool: acTool, + goTool: goTool, + }, + }, + } + server.http = httptest.NewUnstartedServer(server.handler) + server.http.TLS = &tls.Config{InsecureSkipVerify: true} + server.http.StartTLS() + server.URL = server.http.URL + host := server.http.Listener.Addr().String() + switch auth { + case None: + // nothing to do + case Basic: + creds := `"user": "bar", + "password": "baz"` + server.Conf = sprintCreds(host, "basic", creds) + case Oauth: + creds := `"token": "sometoken"` + server.Conf = sprintCreds(host, "oauth", creds) + default: + panic("Woe is me!") + } + return server, nil +} + +func sprintCreds(host, auth, creds string) string { + return fmt.Sprintf(` +{ + "rktKind": "auth", + "rktVersion": "v1", + "domains": ["%s"], + "type": "%s", + "credentials": + { + %s + } +} + +`, host, auth, creds) +} diff --git a/tests/rkt_auth_test.go b/tests/rkt_auth_test.go new file mode 100644 index 0000000..18018f2 --- /dev/null +++ b/tests/rkt_auth_test.go @@ -0,0 +1,19 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package main + +import ( + _ "github.com/coreos/rkt/Godeps/_workspace/src/github.com/endocode/test-aci-auth-server/lib" +)