mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 17:53:28 +00:00
push fixes and tests
This commit is contained in:
parent
db8117b676
commit
df59710382
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
|
@ -65,11 +65,11 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/gravitational/roundtrip",
|
||||
"Rev": "348c0b57968dcd9fef02239abf9b7688881d65e6"
|
||||
"Rev": "6f5b466bba024cee2c0a549bf8844d98f07fd5a3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gravitational/trace",
|
||||
"Rev": "bdd69590106bbc949e797d0ed545489ec6f36eca"
|
||||
"Rev": "af77d5facfcfa8cdccdae73865728c8c0ac3dbf6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jonboulle/clockwork",
|
||||
|
|
|
@ -606,7 +606,7 @@ func (c *Client) CreateUserWithToken(token, password, hotpToken string) (*Sessio
|
|||
if err := json.Unmarshal(out.Bytes(), &sess); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
type chunkRW struct {
|
||||
|
|
|
@ -198,6 +198,12 @@ func (s *AuthServer) CreateUserWithToken(token, password, hotpToken string) (*Se
|
|||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
err = s.UpsertWebSession(tokenData.User, sess, WebSessionTTL)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sess.WS.Priv = nil
|
||||
return sess, nil
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ 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 web
|
||||
|
||||
import (
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SessionCookie stores information about active user and session
|
||||
|
@ -29,6 +31,7 @@ type SessionCookie struct {
|
|||
}
|
||||
|
||||
func EncodeCookie(user, sid string) (string, error) {
|
||||
log.Infof("Encod: %v %v", user, sid)
|
||||
bytes, err := json.Marshal(SessionCookie{User: user, SID: sid})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -45,6 +48,7 @@ func DecodeCookie(b string) (*SessionCookie, error) {
|
|||
if err := json.Unmarshal(bytes, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("DEncod: %v %v", c.User, c.SID)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,8 @@ func NewMultiSiteHandler(cfg MultiSiteConfig) (http.Handler, error) {
|
|||
// SSH proxy web login
|
||||
h.POST("/sshlogin", h.loginSSHProxy)
|
||||
|
||||
h.GET("/webapi/sites", h.needsAuth(h.getSites))
|
||||
|
||||
// Forward all requests to site handler
|
||||
sh := h.needsAuth(h.siteHandler)
|
||||
h.GET("/webapi/sites/:site/*path", sh)
|
||||
|
@ -248,6 +250,42 @@ func (m *MultiSiteHandler) createNewUser(w http.ResponseWriter, r *http.Request,
|
|||
}, nil
|
||||
}
|
||||
|
||||
type getSitesResponse struct {
|
||||
Sites []site `json:"sites"`
|
||||
}
|
||||
|
||||
type site struct {
|
||||
Name string `json:"name"`
|
||||
LastConnected time.Time `json:"last_connected"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func convertSites(rs []reversetunnel.RemoteSite) []site {
|
||||
out := make([]site, len(rs))
|
||||
for i := range rs {
|
||||
out[i] = site{
|
||||
Name: rs[i].GetName(),
|
||||
LastConnected: rs[i].GetLastConnected(),
|
||||
Status: rs[i].GetStatus(),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// getSites returns a list of sites
|
||||
//
|
||||
// GET /v1/webapi/sites
|
||||
//
|
||||
// Sucessful response:
|
||||
//
|
||||
// {"sites": {"name": "localhost", "last_connected": "RFC3339 time", "status": "active"}}
|
||||
//
|
||||
func (h *MultiSiteHandler) getSites(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) (interface{}, error) {
|
||||
return getSitesResponse{
|
||||
Sites: convertSites(h.cfg.Tun.GetSites()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func message(msg string) interface{} {
|
||||
return map[string]interface{}{"message": msg}
|
||||
}
|
||||
|
@ -480,23 +518,32 @@ type contextHandler func(w http.ResponseWriter, r *http.Request, p httprouter.Pa
|
|||
|
||||
func (h *MultiSiteHandler) needsAuth(fn contextHandler) httprouter.Handle {
|
||||
return httplib.MakeHandler(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
logger := log.WithFields(log.Fields{
|
||||
"request": fmt.Sprintf("%v %v", r.Method, r.URL.String()),
|
||||
})
|
||||
logger.Infof("incoming request")
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
logger.Warningf("missing cookie: %v", err)
|
||||
return nil, trace.Wrap(teleport.AccessDenied("missing cookie"))
|
||||
}
|
||||
d, err := DecodeCookie(cookie.Value)
|
||||
if err != nil {
|
||||
logger.Warningf("failed to decode cookie: %v", err)
|
||||
return nil, trace.Wrap(teleport.AccessDenied("failed to decode cookie"))
|
||||
}
|
||||
ctx, err := h.auth.ValidateSession(d.User, d.SID)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
creds, err := roundtrip.ParseAuthHeaders(r)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
logger.Warningf("no auth headers %v", err)
|
||||
return nil, trace.Wrap(teleport.AccessDenied("need auth"))
|
||||
}
|
||||
ctx, err := h.auth.ValidateSession(d.User, d.SID)
|
||||
if err != nil {
|
||||
logger.Warningf("invalid session: %v", err)
|
||||
return nil, trace.Wrap(teleport.AccessDenied("need auth"))
|
||||
}
|
||||
if creds.Password != d.SID {
|
||||
logger.Warningf("bad auth token")
|
||||
return nil, trace.Wrap(teleport.AccessDenied("missing auth token"))
|
||||
}
|
||||
return fn(w, r, p, ctx)
|
||||
|
@ -532,24 +579,6 @@ func New(addr utils.NetAddr, cfg MultiSiteConfig) (*Server, error) {
|
|||
return srv, nil
|
||||
}
|
||||
|
||||
type site struct {
|
||||
Name string `json:"name"`
|
||||
LastConnected time.Time `json:"last_connected"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func sitesResponse(rs []reversetunnel.RemoteSite) []site {
|
||||
out := make([]site, len(rs))
|
||||
for i := range rs {
|
||||
out[i] = site{
|
||||
Name: rs[i].GetName(),
|
||||
LastConnected: rs[i].GetLastConnected(),
|
||||
Status: rs[i].GetStatus(),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func CreateSignupLink(hostPort string, token string) string {
|
||||
return "http://" + hostPort + "/web/newuser/" + token
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package web
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os/user"
|
||||
|
@ -34,6 +35,7 @@ import (
|
|||
"github.com/gravitational/teleport/lib/backend/encryptedbk"
|
||||
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
|
||||
"github.com/gravitational/teleport/lib/events/boltlog"
|
||||
"github.com/gravitational/teleport/lib/httplib"
|
||||
"github.com/gravitational/teleport/lib/limiter"
|
||||
"github.com/gravitational/teleport/lib/recorder/boltrec"
|
||||
"github.com/gravitational/teleport/lib/reversetunnel"
|
||||
|
@ -43,6 +45,7 @@ import (
|
|||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/gokyle/hotp"
|
||||
"github.com/gravitational/roundtrip"
|
||||
"golang.org/x/crypto/ssh"
|
||||
. "gopkg.in/check.v1"
|
||||
|
@ -198,12 +201,20 @@ func (s *WebSuite) SetUpTest(c *C) {
|
|||
s.webServer = httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func (s *WebSuite) client() *roundtrip.Client {
|
||||
clt, err := roundtrip.NewClient("http://"+s.webServer.Listener.Addr().String(), "v1")
|
||||
func (s *WebSuite) url() *url.URL {
|
||||
u, err := url.Parse("http://" + s.webServer.Listener.Addr().String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return clt
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *WebSuite) client(opts ...roundtrip.ClientParam) *testClient {
|
||||
clt, err := roundtrip.NewClient(s.url().String(), "v1", opts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &testClient{clt}
|
||||
}
|
||||
|
||||
func (s *WebSuite) TearDownTest(c *C) {
|
||||
|
@ -224,4 +235,122 @@ func (s *WebSuite) TestNewUser(c *C) {
|
|||
c.Assert(json.Unmarshal(re.Bytes(), &out), IsNil)
|
||||
c.Assert(out.User, Equals, "bob")
|
||||
c.Assert(out.InviteToken, Equals, token)
|
||||
|
||||
_, _, hotpValues, err := s.roleAuth.GetSignupTokenData(token)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
tempPass := "abc123"
|
||||
|
||||
re, err = clt.PostJSON(clt.Endpoint("webapi", "users"), createNewUserReq{
|
||||
InviteToken: token,
|
||||
Pass: tempPass,
|
||||
SecondFactorToken: hotpValues[0],
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var sess *createSessionResponse
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &sess), IsNil)
|
||||
cookies := re.Cookies()
|
||||
c.Assert(len(cookies), Equals, 1)
|
||||
|
||||
// now make sure we are logged in by calling authenticated method
|
||||
// we need to supply both session cookie and bearer token for
|
||||
// request to succeed
|
||||
jar, err := cookiejar.New(nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
clt = s.client(roundtrip.BearerAuth(sess.Token), roundtrip.CookieJar(jar))
|
||||
jar.SetCookies(s.url(), re.Cookies())
|
||||
|
||||
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var sites *getSitesResponse
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &sites), IsNil)
|
||||
|
||||
// in absense of session cookie or bearer auth the same request fill fail
|
||||
|
||||
// no session cookie:
|
||||
clt = s.client(roundtrip.BearerAuth(sess.Token))
|
||||
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
||||
|
||||
// no bearer token:
|
||||
clt = s.client(roundtrip.CookieJar(jar))
|
||||
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
||||
}
|
||||
|
||||
type authPack struct {
|
||||
user string
|
||||
pass string
|
||||
otp *hotp.HOTP
|
||||
session *createSessionResponse
|
||||
clt *testClient
|
||||
}
|
||||
|
||||
// authPack returns new authenticated package consisting
|
||||
// of created valid user, hotp token, created web session and
|
||||
// authenticated client
|
||||
func (s *WebSuite) authPack(c *C) *authPack {
|
||||
user := "bob"
|
||||
pass := "abc123"
|
||||
|
||||
hotpURL, _, err := s.roleAuth.UpsertPassword(user, []byte(pass))
|
||||
c.Assert(err, IsNil)
|
||||
otp, _, err := hotp.FromURL(hotpURL)
|
||||
c.Assert(err, IsNil)
|
||||
otp.Increment()
|
||||
|
||||
clt := s.client()
|
||||
|
||||
re, err := clt.PostJSON(clt.Endpoint("webapi", "sessions"), createSessionReq{
|
||||
User: user,
|
||||
Pass: pass,
|
||||
SecondFactorToken: otp.OTP(),
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var sess *createSessionResponse
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &sess), IsNil)
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
clt = s.client(roundtrip.BearerAuth(sess.Token), roundtrip.CookieJar(jar))
|
||||
jar.SetCookies(s.url(), re.Cookies())
|
||||
|
||||
return &authPack{
|
||||
user: user,
|
||||
pass: pass,
|
||||
session: sess,
|
||||
clt: clt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSuite) TestWebSessionsCRUD(c *C) {
|
||||
pack := s.authPack(c)
|
||||
|
||||
// make sure we can use client to make authenticated requests
|
||||
re, err := pack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var sites *getSitesResponse
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &sites), IsNil)
|
||||
}
|
||||
|
||||
type testClient struct {
|
||||
*roundtrip.Client
|
||||
}
|
||||
|
||||
func (t *testClient) PostJSON(
|
||||
endpoint string, val interface{}) (*roundtrip.Response, error) {
|
||||
return httplib.ConvertResponse(t.Client.PostJSON(endpoint, val))
|
||||
}
|
||||
|
||||
func (t *testClient) Get(
|
||||
endpoint string, val url.Values) (*roundtrip.Response, error) {
|
||||
return httplib.ConvertResponse(t.Client.Get(endpoint, val))
|
||||
}
|
||||
|
|
32
vendor/github.com/gravitational/roundtrip/client.go
generated
vendored
32
vendor/github.com/gravitational/roundtrip/client.go
generated
vendored
|
@ -48,6 +48,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// ClientParam specifies functional argument for client
|
||||
type ClientParam func(c *Client) error
|
||||
|
||||
// HTTPClient is a functional parameter that sets the internal
|
||||
|
@ -67,7 +68,7 @@ func BasicAuth(username, password string) ClientParam {
|
|||
}
|
||||
}
|
||||
|
||||
// BearerAuth sets username and token for HTTP client
|
||||
// BearerAuth sets token for HTTP client
|
||||
func BearerAuth(token string) ClientParam {
|
||||
return func(c *Client) error {
|
||||
c.auth = &bearerAuth{token: token}
|
||||
|
@ -75,6 +76,14 @@ func BearerAuth(token string) ClientParam {
|
|||
}
|
||||
}
|
||||
|
||||
// CookieJar sets http cookie jar for this client
|
||||
func CookieJar(jar http.CookieJar) ClientParam {
|
||||
return func(c *Client) error {
|
||||
c.jar = jar
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a wrapper holding HTTP client. It hold target server address and a version prefix,
|
||||
// and provides common features for building HTTP client wrappers.
|
||||
type Client struct {
|
||||
|
@ -84,9 +93,10 @@ type Client struct {
|
|||
v string
|
||||
// client is a private http.Client instance
|
||||
client *http.Client
|
||||
|
||||
// auth tells client to use HTTP auth on every request
|
||||
auth fmt.Stringer
|
||||
// jar is a set of cookies passed with requests
|
||||
jar http.CookieJar
|
||||
}
|
||||
|
||||
// NewClient returns a new instance of roundtrip.Client, or nil and error
|
||||
|
@ -100,13 +110,16 @@ func NewClient(addr, v string, params ...ClientParam) (*Client, error) {
|
|||
c := &Client{
|
||||
addr: addr,
|
||||
v: v,
|
||||
client: http.DefaultClient,
|
||||
client: &http.Client{},
|
||||
}
|
||||
for _, p := range params {
|
||||
if err := p(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if c.jar != nil {
|
||||
c.client.Jar = c.jar
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -288,7 +301,12 @@ func (c *Client) RoundTrip(fn RoundTripFn) (*Response, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{code: re.StatusCode, headers: re.Header, body: buf}, nil
|
||||
return &Response{
|
||||
code: re.StatusCode,
|
||||
headers: re.Header,
|
||||
body: buf,
|
||||
cookies: re.Cookies(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthHeader sets client's authorization headers if client
|
||||
|
@ -310,6 +328,12 @@ type Response struct {
|
|||
code int
|
||||
headers http.Header
|
||||
body *bytes.Buffer
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
// Cookies returns a list of cookies set by server
|
||||
func (r *Response) Cookies() []*http.Cookie {
|
||||
return r.cookies
|
||||
}
|
||||
|
||||
// Code returns HTTP response status code
|
||||
|
|
1
vendor/github.com/gravitational/trace/log.go
generated
vendored
1
vendor/github.com/gravitational/trace/log.go
generated
vendored
|
@ -48,7 +48,6 @@ func (tf *TextFormatter) Format(e *log.Entry) ([]byte, error) {
|
|||
if frameNo := findFrame(); frameNo != -1 {
|
||||
t := newTrace(runtime.Caller(frameNo - 1))
|
||||
e.Data[FileField] = t.String()
|
||||
e.Data[FunctionField] = t.Func
|
||||
}
|
||||
return (&tf.TextFormatter).Format(e)
|
||||
}
|
||||
|
|
12
vendor/github.com/gravitational/trace/trace.go
generated
vendored
12
vendor/github.com/gravitational/trace/trace.go
generated
vendored
|
@ -23,13 +23,19 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var debug bool
|
||||
var debug int32
|
||||
|
||||
// EnableDebug turns on debugging mode, that causes Fatalf to panic
|
||||
func EnableDebug() {
|
||||
debug = true
|
||||
atomic.StoreInt32(&debug, 1)
|
||||
}
|
||||
|
||||
// IsDebug returns true if debug mode is on, false otherwize
|
||||
func IsDebug() bool {
|
||||
return atomic.LoadInt32(&debug) == 1
|
||||
}
|
||||
|
||||
// Wrap takes the original error and wraps it into the Trace struct
|
||||
|
@ -63,7 +69,7 @@ func Errorf(format string, args ...interface{}) error {
|
|||
// Fatalf - If debug is false Fatalf calls Errorf. If debug is
|
||||
// true Fatalf calls panic
|
||||
func Fatalf(format string, args ...interface{}) error {
|
||||
if debug {
|
||||
if IsDebug() {
|
||||
panic(fmt.Sprintf(format, args))
|
||||
} else {
|
||||
return Errorf(format, args)
|
||||
|
|
Loading…
Reference in a new issue