mirror of
https://github.com/gravitational/teleport
synced 2024-10-20 01:03:40 +00:00
ship audit logs to auth server via SSH endpoint
This commit is contained in:
parent
7ca30fa403
commit
09734cea3e
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
|
@ -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",
|
||||
|
|
103
Godeps/_workspace/src/github.com/gravitational/form/form.go
generated
vendored
103
Godeps/_workspace/src/github.com/gravitational/form/form.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
|
|
102
Godeps/_workspace/src/github.com/gravitational/form/form_test.go
generated
vendored
102
Godeps/_workspace/src/github.com/gravitational/form/form_test.go
generated
vendored
|
@ -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))
|
||||
}
|
||||
|
|
52
Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go
generated
vendored
52
Godeps/_workspace/src/github.com/gravitational/roundtrip/client.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
54
Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go
generated
vendored
54
Godeps/_workspace/src/github.com/gravitational/roundtrip/client_test.go
generated
vendored
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
43
auth/auth.go
43
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
|
||||
|
|
49
auth/clt.go
49
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()
|
||||
}
|
||||
|
|
64
auth/srv.go
64
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}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
13
cp/cp.go
13
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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue