diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 63aca678658..79c68b23a41 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -44,7 +44,7 @@ }, { "ImportPath": "github.com/gravitational/form", - "Rev": "733f5ef69c2641b29fd765c52c560550607d58bb" + "Rev": "c517afe011873196a66137df5ab729187250a4f0" }, { "ImportPath": "github.com/gravitational/memlog", @@ -52,7 +52,7 @@ }, { "ImportPath": "github.com/gravitational/roundtrip", - "Rev": "300fc85b1d938507e5b5b2c021ded3e3e84da302" + "Rev": "e7bee8354256fe4ae0d847e4c5d946d3e86f22af" }, { "ImportPath": "github.com/gravitational/session", diff --git a/Godeps/_workspace/src/github.com/gravitational/form/form.go b/Godeps/_workspace/src/github.com/gravitational/form/form.go index 3b534614a94..362895f04d5 100644 --- a/Godeps/_workspace/src/github.com/gravitational/form/form.go +++ b/Godeps/_workspace/src/github.com/gravitational/form/form.go @@ -3,6 +3,8 @@ package form import ( "fmt" + "mime" + "mime/multipart" "net/http" "time" @@ -32,9 +34,19 @@ type Param func(r *http.Request) error // // handle error here // } func Parse(r *http.Request, params ...Param) error { - if err := r.ParseForm(); err != nil { + mtype, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { return err } + if mtype == "multipart/form-data" { + if err := r.ParseMultipartForm(maxMemoryBytes); err != nil { + return err + } + } else { + if err := r.ParseForm(); err != nil { + return err + } + } for _, p := range params { if err := p(r); err != nil { return err @@ -51,7 +63,7 @@ func Duration(name string, out *time.Duration, predicates ...Predicate) Param { return err } } - v := r.PostForm.Get(name) + v := r.Form.Get(name) if v == "" { return nil } @@ -72,7 +84,7 @@ func String(name string, out *string, predicates ...Predicate) Param { return err } } - *out = r.PostForm.Get(name) + *out = r.Form.Get(name) return nil } } @@ -80,7 +92,7 @@ func String(name string, out *string, predicates ...Predicate) Param { // Int extracts the integer argument in decimal format e.g. "10" func Int(name string, out *int, predicates ...Predicate) Param { return func(r *http.Request) error { - v := r.PostForm.Get(name) + v := r.Form.Get(name) for _, p := range predicates { if err := p.Pass(name, r); err != nil { return err @@ -98,6 +110,58 @@ func Int(name string, out *int, predicates ...Predicate) Param { } } +// StringSlice extracts the string slice of arguments by name +func StringSlice(name string, out *[]string, predicates ...Predicate) Param { + return func(r *http.Request) error { + for _, p := range predicates { + if err := p.Pass(name, r); err != nil { + return err + } + } + *out = make([]string, len(r.Form[name])) + copy(*out, r.Form[name]) + return nil + } +} + +// FileSlice reads the files uploaded with name parameter and initialized +// the slice of files. The files should be closed by the callee after +// usage, by executing f.Close() on each of them +// files slice will be nil if there's an error +func FileSlice(name string, files *Files, predicates ...Predicate) Param { + return func(r *http.Request) error { + err := r.ParseMultipartForm(maxMemoryBytes) + if err != nil { + return err + } + if r.MultipartForm == nil && r.MultipartForm.File == nil { + return fmt.Errorf("missing form") + } + for _, p := range predicates { + if err := p.Pass(name, r); err != nil { + return err + } + } + + fhs := r.MultipartForm.File[name] + if len(fhs) == 0 { + *files = []multipart.File{} + return nil + } + + *files = make([]multipart.File, len(fhs)) + for i, fh := range fhs { + f, err := fh.Open() + if err != nil { + files.Close() + return err + } + (*files)[i] = f + } + return nil + } +} + // Predicate provides an extensible way to check various conditions on a variable // e.g. setting minimums and maximums, or parsing some regular expressions type Predicate interface { @@ -115,7 +179,7 @@ func (p PredicateFunc) Pass(param string, r *http.Request) error { // it returns MissingParameterError when parameter is not present func Required() Predicate { return PredicateFunc(func(param string, r *http.Request) error { - if r.PostForm.Get(param) == "" { + if r.Form.Get(param) == "" { return &MissingParameterError{Param: param} } return nil @@ -142,3 +206,32 @@ type BadParameterError struct { func (p *BadParameterError) Error() string { return fmt.Sprintf("bad parameter '%v', error: %v", p.Param, p.Message) } + +const maxMemoryBytes = 64 * 1024 + +// Files is a slice of multipart.File that provides additional +// convenient method to close all files as a single operation +type Files []multipart.File + +func (fs *Files) Close() error { + e := &FilesCloseError{} + for _, f := range *fs { + if f != nil { + if err := f.Close(); err != nil { + e.Errors = append(e.Errors, err) + } + } + } + if len(e.Errors) != 0 { + return e + } + return nil +} + +type FilesCloseError struct { + Errors []error +} + +func (p *FilesCloseError) Error() string { + return fmt.Sprintf("failed to close files, error: %v", p.Errors) +} diff --git a/Godeps/_workspace/src/github.com/gravitational/form/form_test.go b/Godeps/_workspace/src/github.com/gravitational/form/form_test.go index 4dda205d97b..8dd578cb71f 100644 --- a/Godeps/_workspace/src/github.com/gravitational/form/form_test.go +++ b/Godeps/_workspace/src/github.com/gravitational/form/form_test.go @@ -1,6 +1,11 @@ package form import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" "net/http" "net/http/httptest" "net/url" @@ -82,6 +87,103 @@ func (s *FormSuite) TestDurationInvalidFormat(c *C) { c.Assert(err, FitsTypeOf, &BadParameterError{}) } +func (s *FormSuite) TestMultipartFormOK(c *C) { + var err error + var str string + var i int + var d time.Duration + srv := serveHandler(func(w http.ResponseWriter, r *http.Request) { + err = Parse(r, + String("svar", &str), + Int("ivar", &i), + Duration("dvar", &d), + ) + }) + defer srv.Close() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + writer.WriteField("svar", "hello") + writer.WriteField("ivar", "77") + writer.WriteField("dvar", "100s") + boundary := writer.Boundary() + c.Assert(writer.Close(), IsNil) + + r, err := http.NewRequest("POST", srv.URL, body) + c.Assert(err, IsNil) + r.Header.Set("Content-Type", fmt.Sprintf(`multipart/form-data;boundary="%v"`, boundary)) + + _, err = http.DefaultClient.Do(r) + c.Assert(err, IsNil) + + c.Assert(str, Equals, "hello") + c.Assert(i, Equals, 77) + c.Assert(d, Equals, 100*time.Second) +} + +func (s *FormSuite) TestStringSliceOK(c *C) { + var err error + var slice []string + var empty []string + srv := serveHandler(func(w http.ResponseWriter, r *http.Request) { + err = Parse(r, + StringSlice("slice", &slice), + StringSlice("empty", &empty), + ) + }) + defer srv.Close() + + http.PostForm(srv.URL, url.Values{ + "slice": []string{"hello1", "hello2"}, + }) + + c.Assert(err, IsNil) + c.Assert(slice, DeepEquals, []string{"hello1", "hello2"}) + c.Assert(empty, DeepEquals, []string{}) +} + +func (s *FormSuite) TestFileSliceOK(c *C) { + var err error + var files Files + var values []string + srv := serveHandler(func(w http.ResponseWriter, r *http.Request) { + err = Parse(r, + FileSlice("file", &files), + ) + c.Assert(err, IsNil) + values = make([]string, len(files)) + for i, f := range files { + out, err := ioutil.ReadAll(f) + c.Assert(err, IsNil) + values[i] = string(out) + } + }) + defer srv.Close() + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // upload multiple files with the same name + for _, data := range []string{"file 1", "file 2"} { + w, err := writer.CreateFormFile("file", "file.json") + c.Assert(err, IsNil) + _, err = io.WriteString(w, data) + c.Assert(err, IsNil) + } + boundary := writer.Boundary() + c.Assert(writer.Close(), IsNil) + + req, err := http.NewRequest("POST", srv.URL, body) + req.Header.Set("Content-Type", + fmt.Sprintf(`multipart/form-data;boundary="%v"`, boundary)) + c.Assert(err, IsNil) + _, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + + c.Assert(values, DeepEquals, []string{"file 1", "file 2"}) +} + func serveHandler(f http.HandlerFunc) *httptest.Server { return httptest.NewServer(http.HandlerFunc(f)) } diff --git a/Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go b/Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go index b48c97a6fdf..8b1f8108fef 100644 --- a/Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go +++ b/Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go @@ -24,6 +24,7 @@ import ( "bytes" "fmt" "io" + "mime/multipart" "net/http" "net/url" @@ -85,11 +86,47 @@ func (c *Client) Endpoint(params ...string) string { // // c.PostForm(c.Endpoint("users"), url.Values{"name": []string{"John"}}) // -func (c *Client) PostForm(endpoint string, vals url.Values) (*Response, error) { +func (c *Client) PostForm(endpoint string, vals url.Values, files ...File) (*Response, error) { return c.RoundTrip(func() (*http.Response, error) { - return c.client.Post( - endpoint, "application/x-www-form-urlencoded", - strings.NewReader(vals.Encode())) + if len(files) == 0 { + return c.client.Post( + endpoint, "application/x-www-form-urlencoded", + strings.NewReader(vals.Encode())) + } + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // write simple fields + for name, vals := range vals { + for _, val := range vals { + if err := writer.WriteField(name, val); err != nil { + return nil, err + } + } + } + + // add files + for _, f := range files { + w, err := writer.CreateFormFile(f.Name, f.Filename) + if err != nil { + return nil, err + } + _, err = io.Copy(w, f.Reader) + if err != nil { + return nil, err + } + } + boundary := writer.Boundary() + if err := writer.Close(); err != nil { + return nil, err + } + req, err := http.NewRequest("POST", endpoint, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", + fmt.Sprintf(`multipart/form-data;boundary="%v"`, boundary)) + return c.client.Do(req) }) } @@ -166,3 +203,10 @@ func (r *Response) Reader() io.Reader { func (r *Response) Bytes() []byte { return r.body.Bytes() } + +// File is a file-like object that can be posted to the files +type File struct { + Name string + Filename string + Reader io.Reader +} diff --git a/Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go b/Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go index 169b74245d2..2dd2238ff71 100644 --- a/Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go +++ b/Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go @@ -2,9 +2,11 @@ package roundtrip import ( "io" + "io/ioutil" "net/http" "net/http/httptest" "net/url" + "strings" "testing" "time" @@ -99,6 +101,58 @@ func (s *ClientSuite) TestCustomClient(c *C) { c.Assert(err, NotNil) } +func (s *ClientSuite) TestPostMultipartForm(c *C) { + var u *url.URL + var params url.Values + var method string + var data []string + srv := serveHandler(func(w http.ResponseWriter, r *http.Request) { + u = r.URL + c.Assert(r.ParseMultipartForm(64<<20), IsNil) + params = r.Form + method = r.Method + + c.Assert(r.MultipartForm, NotNil) + c.Assert(len(r.MultipartForm.File["a"]), Not(Equals), 0) + + fhs := r.MultipartForm.File["a"] + for _, fh := range fhs { + f, err := fh.Open() + c.Assert(err, IsNil) + val, err := ioutil.ReadAll(f) + c.Assert(err, IsNil) + data = append(data, string(val)) + } + + io.WriteString(w, "hello back") + + }) + defer srv.Close() + + clt := newC(srv.URL, "v1") + values := url.Values{"a": []string{"b"}} + out, err := clt.PostForm( + clt.Endpoint("a", "b"), + values, + File{ + Name: "a", + Filename: "a.json", + Reader: strings.NewReader("file 1")}, + File{ + Name: "a", + Filename: "a.json", + Reader: strings.NewReader("file 2")}, + ) + + c.Assert(err, IsNil) + c.Assert(string(out.Bytes()), Equals, "hello back") + c.Assert(u.String(), DeepEquals, "/v1/a/b") + + c.Assert(method, Equals, "POST") + c.Assert(params, DeepEquals, values) + c.Assert(data, DeepEquals, []string{"file 1", "file 2"}) +} + func newC(addr, version string) *testClient { c, err := NewClient(addr, version) if err != nil { diff --git a/auth/api.go b/auth/api.go index e8c18fa79c1..12f8c933e57 100644 --- a/auth/api.go +++ b/auth/api.go @@ -4,6 +4,7 @@ import ( "net" "net/http" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/oxy/trace" "github.com/gravitational/teleport/utils" @@ -15,7 +16,8 @@ func StartHTTPServer(a string, srv *AuthServer) error { return err } t, err := trace.New( - NewAPIServer(srv), log.GetLogger().Writer(log.SeverityInfo)) + NewAPIServer(srv, memlog.New()), + log.GetLogger().Writer(log.SeverityInfo)) if err != nil { return err } diff --git a/auth/auth.go b/auth/auth.go index 1b9adbfa49e..e9769740430 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -22,10 +22,12 @@ import ( type Authority interface { GenerateKeyPair(passphrase string) (privKey []byte, pubKey []byte, err error) - // GenerateHostCert generates host certificate, it takes pkey as a signing private key (host certificate authority) + // GenerateHostCert generates host certificate, it takes pkey as a signing + // private key (host certificate authority) GenerateHostCert(pkey, key []byte, id, hostname string, ttl time.Duration) ([]byte, error) - // GenerateHostCert generates user certificate, it takes pkey as a signing private key (user certificate authority) + // GenerateHostCert generates user certificate, it takes pkey as a signing + // private key (user certificate authority) GenerateUserCert(pkey, key []byte, id, username string, ttl time.Duration) ([]byte, error) } @@ -43,7 +45,8 @@ func NewAuthServer(b backend.Backend, a Authority, scrt *secret.Service) *AuthSe } } -// AuthServer implements key signing, generation and ACL functionality used by teleport +// AuthServer implements key signing, generation and ACL functionality +// used by teleport type AuthServer struct { b backend.Backend a Authority @@ -59,9 +62,12 @@ func (s *AuthServer) GetServers() ([]backend.Server, error) { } // UpsertUserKey takes user's public key, generates certificate for it -// and adds it to the authorized keys database. It returns certificate signed by user CA in case of success, -// error otherwise. The certificate will be valid for the duration of the ttl passed in. -func (s *AuthServer) UpsertUserKey(user string, key backend.AuthorizedKey, ttl time.Duration) ([]byte, error) { +// and adds it to the authorized keys database. It returns certificate signed +// by user CA in case of success, error otherwise. The certificate will be +// valid for the duration of the ttl passed in. +func (s *AuthServer) UpsertUserKey( + user string, key backend.AuthorizedKey, ttl time.Duration) ([]byte, error) { + cert, err := s.GenerateUserCert(key.Value, key.ID, user, ttl) if err != nil { return nil, err @@ -90,7 +96,7 @@ func (s *AuthServer) DeleteUserKey(user, key string) error { return s.b.DeleteUserKey(user, key) } -// GenerateKeyPair generates private and public key pair of OpenSSH style certificates +// GenerateKeyPair generates private and public key pair of OpenSSH certs func (s *AuthServer) GenerateKeyPair(pass string) ([]byte, []byte, error) { return s.a.GenerateKeyPair(pass) } @@ -123,8 +129,11 @@ func (s *AuthServer) GetUserCAPub() ([]byte, error) { return s.b.GetUserCAPub() } -// GenerateHostCert generates host certificate, it takes pkey as a signing private key (host certificate authority) -func (s *AuthServer) GenerateHostCert(key []byte, id, hostname string, ttl time.Duration) ([]byte, error) { +// GenerateHostCert generates host certificate, it takes pkey as a signing +// private key (host certificate authority) +func (s *AuthServer) GenerateHostCert( + key []byte, id, hostname string, ttl time.Duration) ([]byte, error) { + hk, err := s.b.GetHostCA() if err != nil { return nil, err @@ -132,8 +141,11 @@ func (s *AuthServer) GenerateHostCert(key []byte, id, hostname string, ttl time. return s.a.GenerateHostCert(hk.Priv, key, id, hostname, ttl) } -// GenerateHostCert generates user certificate, it takes pkey as a signing private key (user certificate authority) -func (s *AuthServer) GenerateUserCert(key []byte, id, username string, ttl time.Duration) ([]byte, error) { +// GenerateHostCert generates user certificate, it takes pkey as a signing +// private key (user certificate authority) +func (s *AuthServer) GenerateUserCert( + key []byte, id, username string, ttl time.Duration) ([]byte, error) { + hk, err := s.b.GetUserCA() if err != nil { return nil, err @@ -287,18 +299,21 @@ func (s *AuthServer) DeleteWebTun(prefix string) error { return s.b.DeleteWebTun(prefix) } -// make sure password satisfies our requirements (relaxed), mostly to avoid putting garbage in +// make sure password satisfies our requirements (relaxed), +// mostly to avoid putting garbage in func verifyPassword(password []byte) error { if len(password) < MinPasswordLength { return &BadParameterError{ Param: "password", - Msg: fmt.Sprintf("password is too short, min length is %v", MinPasswordLength), + Msg: fmt.Sprintf( + "password is too short, min length is %v", MinPasswordLength), } } if len(password) > MaxPasswordLength { return &BadParameterError{ Param: "password", - Msg: fmt.Sprintf("password is too long, max length is %v", MaxPasswordLength), + Msg: fmt.Sprintf( + "password is too long, max length is %v", MaxPasswordLength), } } return nil diff --git a/auth/clt.go b/auth/clt.go index d9e79ba6c9c..fb0f0f15c1c 100644 --- a/auth/clt.go +++ b/auth/clt.go @@ -1,6 +1,7 @@ package auth import ( + "bytes" "encoding/json" "fmt" "net" @@ -51,8 +52,8 @@ func (c *Client) convertResponse(re *roundtrip.Response, err error) (*roundtrip. return re, nil } -func (c *Client) PostForm(endpoint string, vals url.Values) (*roundtrip.Response, error) { - return c.convertResponse(c.Client.PostForm(endpoint, vals)) +func (c *Client) PostForm(endpoint string, vals url.Values, files ...roundtrip.File) (*roundtrip.Response, error) { + return c.convertResponse(c.Client.PostForm(endpoint, vals, files...)) } func (c *Client) Get(u string, params url.Values) (*roundtrip.Response, error) { @@ -78,6 +79,31 @@ func (c *Client) GenerateToken(fqdn string, ttl time.Duration) (string, error) { return re.Token, nil } +func (c *Client) SubmitEvents(events [][]byte) error { + files := make([]roundtrip.File, len(events)) + for i, e := range events { + files[i] = roundtrip.File{ + Name: "event", + Filename: "event.json", + Reader: bytes.NewReader(e), + } + } + _, err := c.PostForm(c.Endpoint("events"), url.Values{}, files...) + return err +} + +func (c *Client) GetEvents() ([]interface{}, error) { + out, err := c.Get(c.Endpoint("events"), url.Values{}) + if err != nil { + return nil, err + } + var re *eventsResponse + if err := json.Unmarshal(out.Bytes(), &re); err != nil { + return nil, err + } + return re.Events, nil +} + func (c *Client) UpsertServer(s backend.Server, ttl time.Duration) error { _, err := c.PostForm(c.Endpoint("servers"), url.Values{ "id": []string{string(s.ID)}, @@ -326,3 +352,22 @@ func (c *Client) ResetUserCA() error { _, err := c.PostForm(c.Endpoint("ca", "user", "keys"), url.Values{}) return err } + +func (c *Client) GetLogWriter() *LogWriter { + return &LogWriter{clt: c} +} + +type LogWriter struct { + clt *Client +} + +func (w *LogWriter) Write(data []byte) (int, error) { + if err := w.clt.SubmitEvents([][]byte{data}); err != nil { + return 0, err + } + return len(data), nil +} + +func (w *LogWriter) LastEvents() ([]interface{}, error) { + return w.clt.GetEvents() +} diff --git a/auth/srv.go b/auth/srv.go index 2213ca4549c..d03e6a1c797 100644 --- a/auth/srv.go +++ b/auth/srv.go @@ -3,10 +3,13 @@ package auth import ( "encoding/json" "fmt" + "io/ioutil" "net/http" "time" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/form" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/session" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/julienschmidt/httprouter" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log" @@ -21,12 +24,14 @@ type Config struct { // APISrv implements http API server for authority type APIServer struct { httprouter.Router - s *AuthServer + s *AuthServer + elog memlog.Logger } -func NewAPIServer(s *AuthServer) *APIServer { +func NewAPIServer(s *AuthServer, elog memlog.Logger) *APIServer { srv := &APIServer{ - s: s, + s: s, + elog: elog, } srv.Router = *httprouter.New() @@ -75,6 +80,10 @@ func NewAPIServer(s *AuthServer) *APIServer { // Tokens srv.POST("/v1/tokens", srv.generateToken) + // Events + srv.POST("/v1/events", srv.submitEvents) + srv.GET("/v1/events", srv.getEvents) + return srv } @@ -435,6 +444,51 @@ func (s *APIServer) generateToken(w http.ResponseWriter, r *http.Request, _ http reply(w, http.StatusOK, tokenResponse{Token: string(token)}) } +func (s *APIServer) submitEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + var events form.Files + + err := form.Parse(r, + form.FileSlice("event", &events)) + if err != nil { + reply(w, http.StatusBadRequest, err.Error()) + } + + if len(events) == 0 { + reply(w, http.StatusBadRequest, + fmt.Errorf("at least one event is required")) + } + + defer func() { + // don't let get the error get lost + if err := events.Close(); err != nil { + log.Errorf("failed to close files: %v", err) + } + }() + + // submit events + for _, e := range events { + data, err := ioutil.ReadAll(e) + if err != nil { + log.Errorf("failed to read event: %v", err) + reply(w, http.StatusBadRequest, fmt.Errorf("failed to read event")) + return + } + if _, err := s.elog.Write(data); err != nil { + log.Errorf("failed to write event: %v", err) + reply(w, http.StatusInternalServerError, + fmt.Errorf("failed to write event")) + return + } + } + + reply(w, http.StatusOK, message("events submitted")) +} + +func (s *APIServer) getEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + roundtrip.ReplyJSON( + w, http.StatusOK, &eventsResponse{Events: s.elog.LastEvents()}) +} + func replyErr(w http.ResponseWriter, e error) { switch err := e.(type) { case *backend.NotFoundError: @@ -505,6 +559,10 @@ type tokenResponse struct { Token string `json:"token"` } +type eventsResponse struct { + Events []interface{} `json:"events"` +} + func message(msg string) map[string]interface{} { return map[string]interface{}{"message": msg} } diff --git a/auth/srv_test.go b/auth/srv_test.go index 9cbbbb05137..51f03d9ca20 100644 --- a/auth/srv_test.go +++ b/auth/srv_test.go @@ -10,6 +10,7 @@ import ( "github.com/gravitational/teleport/backend" "github.com/gravitational/teleport/backend/membk" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/lemma/secret" . "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1" ) @@ -37,7 +38,7 @@ func (s *APISuite) SetUpSuite(c *C) { func (s *APISuite) SetUpTest(c *C) { s.bk = membk.New() s.a = NewAuthServer(s.bk, openssh.New(), s.scrt) - s.srv = httptest.NewServer(NewAPIServer(s.a)) + s.srv = httptest.NewServer(NewAPIServer(s.a, memlog.New())) clt, err := NewClient(s.srv.URL) c.Assert(err, IsNil) s.clt = clt @@ -232,6 +233,22 @@ func (s *APISuite) TestServers(c *C) { c.Assert(servers, DeepEquals, expected) } +func (s *APISuite) TestEvents(c *C) { + err := s.clt.SubmitEvents( + [][]byte{ + []byte(`{"e": "event 1"}`), + []byte(`{"e": "event 2"}`)}) + c.Assert(err, IsNil) + + out, err := s.clt.GetEvents() + c.Assert(err, IsNil) + expected := []interface{}{ + map[string]interface{}{"e": "event 2"}, + map[string]interface{}{"e": "event 1"}, + } + c.Assert(out, DeepEquals, expected) +} + func (s *APISuite) TestTokens(c *C) { out, err := s.clt.GenerateToken("a.example.com", 0) c.Assert(err, IsNil) diff --git a/auth/tun_test.go b/auth/tun_test.go index d48a1dc7f62..f0992c1c8a3 100644 --- a/auth/tun_test.go +++ b/auth/tun_test.go @@ -14,6 +14,7 @@ import ( "github.com/gravitational/teleport/sshutils" "github.com/gravitational/teleport/utils" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/lemma/secret" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log" "github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh" @@ -48,7 +49,7 @@ func (s *TunSuite) TearDownTest(c *C) { func (s *TunSuite) SetUpTest(c *C) { s.bk = membk.New() s.a = NewAuthServer(s.bk, openssh.New(), s.scrt) - s.srv = httptest.NewServer(NewAPIServer(s.a)) + s.srv = httptest.NewServer(NewAPIServer(s.a, memlog.New())) // set up host private key and certificate c.Assert(s.a.ResetHostCA(""), IsNil) @@ -81,7 +82,7 @@ func (s *TunSuite) TestUnixServerClient(c *C) { l, err := net.Listen("unix", socketPath) c.Assert(err, IsNil) - h := NewAPIServer(s.a) + h := NewAPIServer(s.a, memlog.New()) srv := &httptest.Server{ Listener: l, Config: &http.Server{ diff --git a/cp/bindata_assetfs.go b/cp/bindata_assetfs.go index 76879c15c69..dff14acb167 100644 --- a/cp/bindata_assetfs.go +++ b/cp/bindata_assetfs.go @@ -597,7 +597,7 @@ func assets_static_js_grv_servers_js() (*asset, error) { return nil, err } - info := bindata_file_info{name: "assets/static/js/grv/servers.js", size: 3751, mode: os.FileMode(436), modTime: time.Unix(1431289694, 0)} + info := bindata_file_info{name: "assets/static/js/grv/servers.js", size: 3751, mode: os.FileMode(436), modTime: time.Unix(1431295739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -617,7 +617,7 @@ func assets_static_js_grv_webtuns_js() (*asset, error) { return nil, err } - info := bindata_file_info{name: "assets/static/js/grv/webtuns.js", size: 6230, mode: os.FileMode(436), modTime: time.Unix(1431290735, 0)} + info := bindata_file_info{name: "assets/static/js/grv/webtuns.js", size: 6230, mode: os.FileMode(436), modTime: time.Unix(1431295739, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/cp/cp.go b/cp/cp.go index f20e5ce0863..73e3310045f 100644 --- a/cp/cp.go +++ b/cp/cp.go @@ -11,7 +11,6 @@ import ( "github.com/gravitational/teleport/utils" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/form" - "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/julienschmidt/httprouter" "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log" @@ -20,14 +19,12 @@ import ( // cpHandler implements methods for control panel type cpHandler struct { httprouter.Router - events memlog.Logger host string authServers []utils.NetAddr } -func newCPHandler(host string, auth []utils.NetAddr, events memlog.Logger) *cpHandler { +func newCPHandler(host string, auth []utils.NetAddr) *cpHandler { h := &cpHandler{ - events: events, authServers: auth, host: host, } @@ -141,7 +138,13 @@ func (s *cpHandler) upsertWebTun(w http.ResponseWriter, r *http.Request, _ httpr } func (s *cpHandler) getEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) { - roundtrip.ReplyJSON(w, http.StatusOK, s.events.LastEvents()) + events, err := c.clt.GetEvents() + if err != nil { + log.Errorf("failed to retrieve events: %v") + replyErr(w, http.StatusInternalServerError, err) + return + } + roundtrip.ReplyJSON(w, http.StatusOK, events) } func (s *cpHandler) keysIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) { diff --git a/cp/srv.go b/cp/srv.go index 28699d00231..2ca1d9ff99a 100644 --- a/cp/srv.go +++ b/cp/srv.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" - "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" "github.com/gravitational/teleport/utils" ) @@ -16,7 +15,6 @@ type CPServer struct { type Config struct { AuthSrv []utils.NetAddr - LogSrv memlog.Logger Host string } @@ -24,13 +22,10 @@ func NewServer(cfg Config) (*CPServer, error) { if len(cfg.AuthSrv) == 0 { return nil, fmt.Errorf("need at least one auth server") } - if cfg.LogSrv == nil { - return nil, fmt.Errorf("need an event logger service") - } if cfg.Host == "" { return nil, fmt.Errorf("need an base host") } - cp := newCPHandler(cfg.Host, cfg.AuthSrv, cfg.LogSrv) + cp := newCPHandler(cfg.Host, cfg.AuthSrv) proxy := newProxyHandler(cp, cfg.AuthSrv, cfg.Host) return &CPServer{ cfg: cfg, diff --git a/service/service.go b/service/service.go index a9d4a4c2ead..bfa19f00e47 100644 --- a/service/service.go +++ b/service/service.go @@ -20,7 +20,6 @@ import ( type TeleportService struct { Supervisor - log memlog.Logger } func NewTeleport(cfg Config) (*TeleportService, error) { @@ -34,7 +33,6 @@ func NewTeleport(cfg Config) (*TeleportService, error) { t := &TeleportService{} t.Supervisor = *New() - t.log = memlog.New() if cfg.Auth.Enabled { if err := initAuth(t, cfg); err != nil { @@ -77,7 +75,7 @@ func initAuth(t *TeleportService, cfg Config) error { // register HTTP API endpoint t.RegisterFunc(func() error { - apisrv := auth.NewAPIServer(asrv) + apisrv := auth.NewAPIServer(asrv, memlog.New()) t, err := trace.New(apisrv, log.GetLogger().Writer(log.SeverityInfo)) if err != nil { log.Fatalf("failed to start: %v", err) @@ -115,7 +113,6 @@ func initCP(t *TeleportService, cfg Config) error { } csrv, err := cp.NewServer(cp.Config{ AuthSrv: cfg.AuthServers, - LogSrv: t.log, Host: cfg.CP.Domain, }) if err != nil { @@ -150,17 +147,17 @@ func initSSH(t *TeleportService, cfg Config) error { func initSSHEndpoint(t *TeleportService, cfg Config) error { signer, err := auth.ReadKeys(cfg.FQDN, cfg.DataDir) - elog := &FanOutEventLogger{ - Loggers: []lunk.EventLogger{ - lunk.NewTextEventLogger(log.GetLogger().Writer(log.SeverityInfo)), - lunk.NewJSONEventLogger(t.log), - }} - client, err := auth.NewTunClient( cfg.AuthServers[0], cfg.FQDN, []ssh.AuthMethod{ssh.PublicKeys(signer)}) + elog := &FanOutEventLogger{ + Loggers: []lunk.EventLogger{ + lunk.NewTextEventLogger(log.GetLogger().Writer(log.SeverityInfo)), + lunk.NewJSONEventLogger(client.GetLogWriter()), + }} + s, err := srv.New(cfg.SSH.Addr, []ssh.Signer{signer}, client, diff --git a/tctl/command/cmd_test.go b/tctl/command/cmd_test.go index 7ff3287a30b..e77714decb6 100644 --- a/tctl/command/cmd_test.go +++ b/tctl/command/cmd_test.go @@ -9,12 +9,14 @@ import ( "strings" "testing" - "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/lemma/secret" "github.com/gravitational/teleport/auth" "github.com/gravitational/teleport/auth/openssh" "github.com/gravitational/teleport/backend/membk" "github.com/gravitational/teleport/utils" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/memlog" + "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/lemma/secret" + . "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1" ) @@ -46,7 +48,7 @@ func (s *CmdSuite) SetUpSuite(c *C) { func (s *CmdSuite) SetUpTest(c *C) { s.bk = membk.New() s.asrv = auth.NewAuthServer(s.bk, openssh.New(), s.scrt) - s.srv = httptest.NewServer(auth.NewAPIServer(s.asrv)) + s.srv = httptest.NewServer(auth.NewAPIServer(s.asrv, memlog.New())) u, err := url.Parse(s.srv.URL) c.Assert(err, IsNil)