mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Refactored events
This commit is contained in:
@@ -110,6 +110,8 @@ run-simple: install
|
||||
-authBackend=bolt\
|
||||
-authBackendConfig='{"path": "/tmp/teleport.auth.db"}'\
|
||||
-authDomain=gravitational.io\
|
||||
-authEventBackend=bolt\
|
||||
-authEventBackendConfig='{"path": "/tmp/teleport.event.db"}'\
|
||||
-log=console\
|
||||
-logSeverity=INFO\
|
||||
-dataDir=/var/lib/teleport\
|
||||
@@ -118,7 +120,7 @@ run-simple: install
|
||||
-authServer=tcp://auth.gravitational.io:33000\
|
||||
-cp\
|
||||
-cpAssetsDir=$(GOPATH)/src/github.com/gravitational/teleport\
|
||||
-cpDomain=gravitational.io\
|
||||
-cpDomain=gravitational.io
|
||||
|
||||
run-ssh2: install
|
||||
tctl token generate --output=/tmp/token -fqdn=node1.gravitational.io
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var EventsPage = React.createClass({
|
||||
showEvent: function(e){
|
||||
if(e.schema != "teleport.shell") {
|
||||
return;
|
||||
}
|
||||
this.refs.event.show(atob(e.properties.log));
|
||||
},
|
||||
getInitialState: function(){
|
||||
return {entries: []};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.reload();
|
||||
setInterval(this.reload, this.props.pollInterval);
|
||||
},
|
||||
reload: function(){
|
||||
$.ajax({
|
||||
url: this.props.url,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
this.setState({entries: data});
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(this.props.url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div id="wrapper">
|
||||
<LeftNavBar current="events"/>
|
||||
<div id="page-wrapper" className="gray-bg">
|
||||
<TopNavBar/>
|
||||
<PageHeader title="Cluster Events" url="/events"/>
|
||||
<div className="wrapper wrapper-content animated fadeInRight">
|
||||
<Box>
|
||||
<EventsBox entries={this.state.entries} onShowEvent={this.showEvent}/>
|
||||
</Box>
|
||||
</div>
|
||||
<PageFooter/>
|
||||
</div>
|
||||
<EventForm ref="event"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventsBox = React.createClass({
|
||||
render: function() {
|
||||
if (this.props.entries.length == 0) {
|
||||
return (
|
||||
<div className="text-center m-t-lg">
|
||||
<h1>Logged Events</h1>
|
||||
<small>There are no events logged. Log in via SSH to watch the events.</small><br/><br/>
|
||||
</div>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<EventsTable {...this.props}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventsTable = React.createClass({
|
||||
render: function() {
|
||||
var show = this.props.onShowEvent
|
||||
var keyNodes = this.props.entries.map(function (event, index) {
|
||||
return (
|
||||
<EventRow event={event} key={index} onShowEvent={show}></EventRow>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Event</th>
|
||||
<th>User</th>
|
||||
<th>Client IP</th>
|
||||
<th>Remote IP</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{keyNodes}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventRow = React.createClass({
|
||||
showEvent: function(e) {
|
||||
e.preventDefault();
|
||||
this.props.onShowEvent(this.props.event);
|
||||
},
|
||||
describe: function(event) {
|
||||
switch (event.schema) {
|
||||
case "teleport.auth.attempt":
|
||||
if (event.properties.success == "true") {
|
||||
return {icon: "fa fa-user text-navy", text: "successfull auth"};
|
||||
}
|
||||
return {icon: "fa fa-user text-warning", text: "unsucessfull auth: " + event.properties.error};
|
||||
case "teleport.shell":
|
||||
return {icon: "fa fa-tty text-navy", text: "view session log"};
|
||||
case "teleport.exec":
|
||||
return {icon: "fa fa-tty text-navy", text: event.properties.command + " " + atob(event.properties.log).substring(0, 100)};
|
||||
}
|
||||
return {icon: "fa fa-question text-navy", text: "unrecognized event: "+event.schema};
|
||||
},
|
||||
render: function() {
|
||||
var e = this.props.event;
|
||||
var d = this.describe(e);
|
||||
return (
|
||||
<tr className="key">
|
||||
<td><a href="#"><i className={d.icon}></i></a></td>
|
||||
<td>{e.time}</td>
|
||||
<td>{e.schema}</td>
|
||||
<td>{e.properties.user}</td>
|
||||
<td>{e.properties.localaddr}</td>
|
||||
<td>{e.properties.remoteaddr}</td>
|
||||
<td><a href="#" onClick={this.showEvent}>{d.text}</a></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var EventForm = React.createClass({
|
||||
show: function(log) {
|
||||
this.term = new Terminal({
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
useStyle: true,
|
||||
screenKeys: true,
|
||||
cursorBlink: false
|
||||
});
|
||||
this.term.open(React.findDOMNode(this.refs.term));
|
||||
this.term.write(log);
|
||||
this.refs.modal.open();
|
||||
},
|
||||
close: function() {
|
||||
this.term.destroy();
|
||||
this.refs.modal.close();
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<BootstrapModal
|
||||
dialogClass="modal-lg"
|
||||
icon="fa-list"
|
||||
ref="modal"
|
||||
cancel="Close"
|
||||
onCancel={this.close}
|
||||
title="SSH Session Log">
|
||||
<div ref="term" style={{width: '580px', height: '400px'}} className="text-center m-t-lg"></div>
|
||||
</BootstrapModal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
React.render(
|
||||
<EventsPage url="/api/events" pollInterval={2000}/>,
|
||||
document.body
|
||||
);
|
||||
@@ -0,0 +1,183 @@
|
||||
'use strict';
|
||||
|
||||
var EventsPage = React.createClass({
|
||||
showEvent: function(e){
|
||||
if(e.schema != "teleport.shell") {
|
||||
return;
|
||||
}
|
||||
this.refs.event.show(atob(e.properties.log));
|
||||
},
|
||||
getInitialState: function(){
|
||||
return {entries: []};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.reload();
|
||||
setInterval(this.reload, this.props.pollInterval);
|
||||
},
|
||||
reload: function(){
|
||||
var q = [
|
||||
{name: 'start', value: toRFC3339(new Date())},
|
||||
{name: 'limit', value: '20'},
|
||||
{name: 'order', value: '-1'},
|
||||
];
|
||||
$.ajax({
|
||||
url: this.props.url+ "?"+ $.param(q),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
this.setState({entries: data});
|
||||
}.bind(this),
|
||||
error: function(xhr, status, err) {
|
||||
console.error(this.props.url, status, err.toString());
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div id="wrapper">
|
||||
<LeftNavBar current="events"/>
|
||||
<div id="page-wrapper" className="gray-bg">
|
||||
<TopNavBar/>
|
||||
<PageHeader title="Cluster Events" url="/events"/>
|
||||
<div className="wrapper wrapper-content animated fadeInRight">
|
||||
<Box>
|
||||
<EventsBox entries={this.state.entries} onShowEvent={this.showEvent}/>
|
||||
</Box>
|
||||
</div>
|
||||
<PageFooter/>
|
||||
</div>
|
||||
<EventForm ref="event"/>
|
||||
</div>);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventsBox = React.createClass({
|
||||
render: function() {
|
||||
if (this.props.entries.length == 0) {
|
||||
return (
|
||||
<div className="text-center m-t-lg">
|
||||
<h1>Logged Events</h1>
|
||||
<small>There are no events logged. Log in via SSH to watch the events.</small><br/><br/>
|
||||
</div>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<EventsTable {...this.props}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventsTable = React.createClass({
|
||||
render: function() {
|
||||
var show = this.props.onShowEvent
|
||||
var keyNodes = this.props.entries.map(function (event, index) {
|
||||
return (
|
||||
<EventRow event={event} key={index} onShowEvent={show}></EventRow>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Event</th>
|
||||
<th>User</th>
|
||||
<th>Client IP</th>
|
||||
<th>Remote IP</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{keyNodes}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var EventRow = React.createClass({
|
||||
showEvent: function(e) {
|
||||
e.preventDefault();
|
||||
this.props.onShowEvent(this.props.event);
|
||||
},
|
||||
describe: function(event) {
|
||||
switch (event.schema) {
|
||||
case "teleport.auth.attempt":
|
||||
if (event.properties.success == "true") {
|
||||
return {icon: "fa fa-user text-navy", text: "successfull auth"};
|
||||
}
|
||||
return {icon: "fa fa-user text-warning", text: "unsucessfull auth: " + event.properties.error};
|
||||
case "teleport.shell":
|
||||
return {icon: "fa fa-tty text-navy", text: "view session log"};
|
||||
case "teleport.exec":
|
||||
return {icon: "fa fa-tty text-navy", text: event.properties.command + " " + atob(event.properties.log).substring(0, 100)};
|
||||
}
|
||||
return {icon: "fa fa-question text-navy", text: "unrecognized event: "+event.schema};
|
||||
},
|
||||
render: function() {
|
||||
var e = this.props.event;
|
||||
var d = this.describe(e);
|
||||
return (
|
||||
<tr className="key">
|
||||
<td><a href="#"><i className={d.icon}></i></a></td>
|
||||
<td>{e.time}</td>
|
||||
<td>{e.schema}</td>
|
||||
<td>{e.properties.user}</td>
|
||||
<td>{e.properties.localaddr}</td>
|
||||
<td>{e.properties.remoteaddr}</td>
|
||||
<td><a href="#" onClick={this.showEvent}>{d.text}</a></td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var EventForm = React.createClass({
|
||||
show: function(log) {
|
||||
this.term = new Terminal({
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
useStyle: true,
|
||||
screenKeys: true,
|
||||
cursorBlink: false
|
||||
});
|
||||
this.term.open(React.findDOMNode(this.refs.term));
|
||||
var self = this;
|
||||
var i = 0;
|
||||
var write = function(){
|
||||
if(i >= log.length) {
|
||||
return
|
||||
}
|
||||
self.term.write(log[i]);
|
||||
i++;
|
||||
setTimeout(write, 40);
|
||||
}
|
||||
this.refs.modal.open();
|
||||
write();
|
||||
},
|
||||
close: function() {
|
||||
this.term.destroy();
|
||||
this.refs.modal.close();
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<BootstrapModal dialogClass="modal-lg"
|
||||
icon="fa-list"
|
||||
ref="modal"
|
||||
cancel="Close"
|
||||
onCancel={this.close}
|
||||
title="SSH Session Log">
|
||||
<div ref="term" style={{width: '580px', height: '400px'}} className="text-center m-t-lg"></div>
|
||||
</BootstrapModal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
React.render(
|
||||
<EventsPage url="/api/events" pollInterval={2000}/>,
|
||||
document.body
|
||||
);
|
||||
@@ -35,3 +35,19 @@ function formatBytes(bytes,decimals) {
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
|
||||
function toRFC3339(dt) {
|
||||
return (dt.getUTCFullYear() + "-" + zeroPad(dt.getUTCMonth() + 1, 2) + "-" + zeroPad(dt.getUTCDate(),2) + "T" +
|
||||
zeroPad(dt.getUTCHours(),2) + ":" + zeroPad(dt.getUTCMinutes(),2) + ":" + zeroPad(dt.getUTCSeconds(),2) + "." + dt.getUTCMilliseconds() + "Z");
|
||||
}
|
||||
|
||||
function zeroPad(num, numZeros) {
|
||||
var n = Math.abs(num);
|
||||
var zeros = Math.max(0, numZeros - Math.floor(n).toString().length );
|
||||
var zeroString = Math.pow(10,zeros).toString().substr(1);
|
||||
if( num < 0 ) {
|
||||
zeroString = '-' + zeroString;
|
||||
}
|
||||
return zeroString+n;
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
|
||||
{{ define "script" }}
|
||||
<script type="text/javascript" src="/static/js/term.js"></script>
|
||||
<script type="text/jsx" src="/static/js/grv/events.js"></script>
|
||||
<script type="text/jsx" src="/static/js/grv/events.jsx"></script>
|
||||
{{ end }}
|
||||
|
||||
+6
-5
@@ -4,20 +4,21 @@ 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/events"
|
||||
"github.com/gravitational/teleport/session"
|
||||
"github.com/gravitational/teleport/utils"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/oxy/trace"
|
||||
)
|
||||
|
||||
func StartHTTPServer(a string, srv *AuthServer, se session.SessionServer) error {
|
||||
func StartHTTPServer(a string, srv *AuthServer, elog events.Log, se session.SessionServer) error {
|
||||
addr, err := utils.ParseAddr(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := trace.New(
|
||||
NewAPIServer(srv, memlog.New(), se),
|
||||
NewAPIServer(srv, elog, se),
|
||||
log.GetLogger().Writer(log.SeverityInfo))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+24
-35
@@ -10,9 +10,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/events"
|
||||
"github.com/gravitational/teleport/session"
|
||||
"github.com/gravitational/teleport/utils"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
|
||||
)
|
||||
|
||||
@@ -211,24 +213,32 @@ func (c *Client) GenerateToken(fqdn string, ttl time.Duration) (string, error) {
|
||||
return re.Token, nil
|
||||
}
|
||||
|
||||
// Submit events submits structured audit events in JSON serialized
|
||||
// format to the auth server.
|
||||
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),
|
||||
}
|
||||
func (c *Client) Log(id lunk.EventID, e lunk.Event) {
|
||||
en := lunk.NewEntry(id, e)
|
||||
en.Time = time.Now()
|
||||
c.LogEntry(en)
|
||||
}
|
||||
|
||||
func (c *Client) LogEntry(en lunk.Entry) error {
|
||||
bt, err := json.Marshal(en)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := c.PostForm(c.Endpoint("events"), url.Values{}, files...)
|
||||
file := roundtrip.File{
|
||||
Name: "event",
|
||||
Filename: "event.json",
|
||||
Reader: bytes.NewReader(bt),
|
||||
}
|
||||
_, err = c.PostForm(c.Endpoint("events"), url.Values{}, file)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetEvents returns last 20 audit events recorded by the auth server
|
||||
func (c *Client) GetEvents() ([]interface{}, error) {
|
||||
out, err := c.Get(c.Endpoint("events"), url.Values{})
|
||||
func (c *Client) GetEvents(filter events.Filter) ([]lunk.Entry, error) {
|
||||
vals, err := events.FilterToURL(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := c.Get(c.Endpoint("events"), vals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -554,24 +564,3 @@ func (c *Client) ResetUserCA() error {
|
||||
_, err := c.PostForm(c.Endpoint("ca", "user", "keys"), url.Values{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLogWriter returns a io.Writer - compatible object
|
||||
// that can be used by lunk.EventLogger to ship audit logs to the auth server
|
||||
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()
|
||||
}
|
||||
|
||||
+29
-9
@@ -7,14 +7,16 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/events"
|
||||
"github.com/gravitational/teleport/session"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"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"
|
||||
websession "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"
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/session"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -26,11 +28,11 @@ type Config struct {
|
||||
type APIServer struct {
|
||||
httprouter.Router
|
||||
s *AuthServer
|
||||
elog memlog.Logger
|
||||
elog events.Log
|
||||
se session.SessionServer
|
||||
}
|
||||
|
||||
func NewAPIServer(s *AuthServer, elog memlog.Logger, se session.SessionServer) *APIServer {
|
||||
func NewAPIServer(s *AuthServer, elog events.Log, se session.SessionServer) *APIServer {
|
||||
srv := &APIServer{
|
||||
s: s,
|
||||
elog: elog,
|
||||
@@ -488,7 +490,14 @@ func (s *APIServer) submitEvents(w http.ResponseWriter, r *http.Request, _ httpr
|
||||
reply(w, http.StatusBadRequest, fmt.Errorf("failed to read event"))
|
||||
return
|
||||
}
|
||||
if _, err := s.elog.Write(data); err != nil {
|
||||
var e *lunk.Entry
|
||||
if err := json.Unmarshal(data, &e); 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.LogEntry(*e); err != nil {
|
||||
log.Errorf("failed to write event: %v", err)
|
||||
reply(w, http.StatusInternalServerError,
|
||||
fmt.Errorf("failed to write event"))
|
||||
@@ -500,8 +509,19 @@ func (s *APIServer) submitEvents(w http.ResponseWriter, r *http.Request, _ httpr
|
||||
}
|
||||
|
||||
func (s *APIServer) getEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
roundtrip.ReplyJSON(
|
||||
w, http.StatusOK, &eventsResponse{Events: s.elog.LastEvents()})
|
||||
f, err := events.FilterFromURL(r.URL.Query())
|
||||
if err != nil {
|
||||
log.Errorf("failed to parse filter: %v", err)
|
||||
reply(w, http.StatusBadRequest, fmt.Errorf("failed to parse filter"))
|
||||
return
|
||||
}
|
||||
es, err := s.elog.GetEvents(*f)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get events: %v", err)
|
||||
reply(w, http.StatusInternalServerError, fmt.Errorf("failed to get events"))
|
||||
return
|
||||
}
|
||||
roundtrip.ReplyJSON(w, http.StatusOK, &eventsResponse{Events: es})
|
||||
}
|
||||
|
||||
func replyErr(w http.ResponseWriter, e error) {
|
||||
@@ -699,7 +719,7 @@ type tokenResponse struct {
|
||||
}
|
||||
|
||||
type eventsResponse struct {
|
||||
Events []interface{} `json:"events"`
|
||||
Events []lunk.Entry `json:"events"`
|
||||
}
|
||||
|
||||
type partyResponse struct {
|
||||
|
||||
+11
-16
@@ -6,14 +6,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
|
||||
authority "github.com/gravitational/teleport/auth/native"
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/backend/boltbk"
|
||||
"github.com/gravitational/teleport/events/boltlog"
|
||||
"github.com/gravitational/teleport/events/test"
|
||||
"github.com/gravitational/teleport/session"
|
||||
|
||||
"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/golang.org/x/crypto/ssh"
|
||||
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,7 @@ type APISuite struct {
|
||||
srv *httptest.Server
|
||||
clt *Client
|
||||
bk *boltbk.BoltBackend
|
||||
bl *boltlog.BoltLog
|
||||
scrt *secret.Service
|
||||
a *AuthServer
|
||||
dir string
|
||||
@@ -44,8 +46,11 @@ func (s *APISuite) SetUpTest(c *C) {
|
||||
s.bk, err = boltbk.New(filepath.Join(s.dir, "db"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.bl, err = boltlog.New(filepath.Join(s.dir, "eventsdb"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.a = NewAuthServer(s.bk, authority.New(), s.scrt)
|
||||
s.srv = httptest.NewServer(NewAPIServer(s.a, memlog.New(), session.New(s.bk)))
|
||||
s.srv = httptest.NewServer(NewAPIServer(s.a, s.bl, session.New(s.bk)))
|
||||
clt, err := NewClient(s.srv.URL)
|
||||
c.Assert(err, IsNil)
|
||||
s.clt = clt
|
||||
@@ -54,6 +59,7 @@ func (s *APISuite) SetUpTest(c *C) {
|
||||
func (s *APISuite) TearDownTest(c *C) {
|
||||
s.srv.Close()
|
||||
s.bk.Close()
|
||||
s.bl.Close()
|
||||
}
|
||||
|
||||
func (s *APISuite) TestHostCACRUD(c *C) {
|
||||
@@ -254,19 +260,8 @@ func (s *APISuite) TestServers(c *C) {
|
||||
}
|
||||
|
||||
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)
|
||||
suite := test.EventSuite{L: s.clt}
|
||||
suite.EventsCRUD(c)
|
||||
}
|
||||
|
||||
func (s *APISuite) TestTokens(c *C) {
|
||||
|
||||
+10
-3
@@ -11,11 +11,11 @@ import (
|
||||
authority "github.com/gravitational/teleport/auth/native"
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/backend/membk"
|
||||
"github.com/gravitational/teleport/events/boltlog"
|
||||
"github.com/gravitational/teleport/session"
|
||||
"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"
|
||||
@@ -30,17 +30,24 @@ type TunSuite struct {
|
||||
tsrv *TunServer
|
||||
a *AuthServer
|
||||
signer ssh.Signer
|
||||
bl *boltlog.BoltLog
|
||||
dir string
|
||||
}
|
||||
|
||||
var _ = Suite(&TunSuite{})
|
||||
|
||||
func (s *TunSuite) SetUpSuite(c *C) {
|
||||
s.dir = c.MkDir()
|
||||
|
||||
key, err := secret.NewKey()
|
||||
c.Assert(err, IsNil)
|
||||
srv, err := secret.New(&secret.Config{KeyBytes: key})
|
||||
c.Assert(err, IsNil)
|
||||
s.scrt = srv
|
||||
log.Init([]*log.LogConfig{&log.LogConfig{Name: "console"}})
|
||||
|
||||
s.bl, err = boltlog.New(filepath.Join(s.dir, "eventsdb"))
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *TunSuite) TearDownTest(c *C) {
|
||||
@@ -50,7 +57,7 @@ func (s *TunSuite) TearDownTest(c *C) {
|
||||
func (s *TunSuite) SetUpTest(c *C) {
|
||||
s.bk = membk.New()
|
||||
s.a = NewAuthServer(s.bk, authority.New(), s.scrt)
|
||||
s.srv = httptest.NewServer(NewAPIServer(s.a, memlog.New(), session.New(s.bk)))
|
||||
s.srv = httptest.NewServer(NewAPIServer(s.a, s.bl, session.New(s.bk)))
|
||||
|
||||
// set up host private key and certificate
|
||||
c.Assert(s.a.ResetHostCA(""), IsNil)
|
||||
@@ -83,7 +90,7 @@ func (s *TunSuite) TestUnixServerClient(c *C) {
|
||||
l, err := net.Listen("unix", socketPath)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
h := NewAPIServer(s.a, memlog.New(), session.New(s.bk))
|
||||
h := NewAPIServer(s.a, s.bl, session.New(s.bk))
|
||||
srv := &httptest.Server{
|
||||
Listener: l,
|
||||
Config: &http.Server{
|
||||
|
||||
+14
-14
@@ -135,7 +135,7 @@ func (b *BoltBackend) GetRemoteCerts(ctype string, fqdn string) ([]backend.Remot
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
hosts := []string{}
|
||||
if fqdn == "" {
|
||||
bkt, err := getBucket(tx, []string{"certs", ctype, "hosts"})
|
||||
bkt, err := GetBucket(tx, []string{"certs", ctype, "hosts"})
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
return nil
|
||||
@@ -150,7 +150,7 @@ func (b *BoltBackend) GetRemoteCerts(ctype string, fqdn string) ([]backend.Remot
|
||||
hosts = []string{fqdn}
|
||||
}
|
||||
for _, h := range hosts {
|
||||
bkt, err := getBucket(tx, []string{"certs", ctype, "hosts", h})
|
||||
bkt, err := GetBucket(tx, []string{"certs", ctype, "hosts", h})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (b *BoltBackend) GetUserKeys(user string) ([]backend.AuthorizedKey, error)
|
||||
}
|
||||
values := []backend.AuthorizedKey{}
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, []string{"users", user, "keys"})
|
||||
bkt, err := GetBucket(tx, []string{"users", user, "keys"})
|
||||
if err != nil {
|
||||
if _, ok := err.(*backend.NotFoundError); ok {
|
||||
return nil
|
||||
@@ -355,7 +355,7 @@ func (b *BoltBackend) GetWebSession(user, sid string) (*backend.WebSession, erro
|
||||
func (b *BoltBackend) GetWebSessionsKeys(user string) ([]backend.AuthorizedKey, error) {
|
||||
values := []backend.AuthorizedKey{}
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, []string{"users", user, "web-sessions"})
|
||||
bkt, err := GetBucket(tx, []string{"users", user, "web-sessions"})
|
||||
if err != nil {
|
||||
if _, ok := err.(*backend.NotFoundError); ok {
|
||||
return nil
|
||||
@@ -404,7 +404,7 @@ func (b *BoltBackend) DeleteWebTun(prefix string) error {
|
||||
func (b *BoltBackend) GetWebTuns() ([]backend.WebTun, error) {
|
||||
out := []backend.WebTun{}
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, []string{"web-tuns"})
|
||||
bkt, err := GetBucket(tx, []string{"web-tuns"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -426,7 +426,7 @@ func (b *BoltBackend) GetWebTuns() ([]backend.WebTun, error) {
|
||||
|
||||
func (b *BoltBackend) deleteBucket(buckets []string, bucket string) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, buckets)
|
||||
bkt, err := GetBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -440,7 +440,7 @@ func (b *BoltBackend) deleteBucket(buckets []string, bucket string) error {
|
||||
|
||||
func (b *BoltBackend) deleteKey(buckets []string, key string) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, buckets)
|
||||
bkt, err := GetBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -453,7 +453,7 @@ func (b *BoltBackend) deleteKey(buckets []string, key string) error {
|
||||
|
||||
func (b *BoltBackend) upsertKey(buckets []string, key string, bytes []byte) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt, err := upsertBucket(tx, buckets)
|
||||
bkt, err := UpsertBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -467,7 +467,7 @@ func (b *BoltBackend) upsertJSONKey(buckets []string, key string, val interface{
|
||||
return err
|
||||
}
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt, err := upsertBucket(tx, buckets)
|
||||
bkt, err := UpsertBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func (b *BoltBackend) upsertJSONKey(buckets []string, key string, val interface{
|
||||
|
||||
func (b *BoltBackend) getJSONKey(buckets []string, key string, val interface{}) error {
|
||||
return b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, buckets)
|
||||
bkt, err := GetBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -493,7 +493,7 @@ func (b *BoltBackend) getJSONKey(buckets []string, key string, val interface{})
|
||||
|
||||
func (b *BoltBackend) getKey(buckets []string, key string, val *[]byte) error {
|
||||
return b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, buckets)
|
||||
bkt, err := GetBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -511,7 +511,7 @@ func (b *BoltBackend) getKey(buckets []string, key string, val *[]byte) error {
|
||||
func (b *BoltBackend) getKeys(buckets []string) ([]string, error) {
|
||||
out := []string{}
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
bkt, err := getBucket(tx, buckets)
|
||||
bkt, err := GetBucket(tx, buckets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -527,7 +527,7 @@ func (b *BoltBackend) getKeys(buckets []string) ([]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func upsertBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
|
||||
func UpsertBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
|
||||
bkt, err := b.CreateBucketIfNotExists([]byte(buckets[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -541,7 +541,7 @@ func upsertBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
|
||||
return bkt, nil
|
||||
}
|
||||
|
||||
func getBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
|
||||
func GetBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
|
||||
bkt := b.Bucket([]byte(buckets[0]))
|
||||
if bkt == nil {
|
||||
return nil, &backend.NotFoundError{
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"github.com/gravitational/teleport/auth"
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/events"
|
||||
"github.com/gravitational/teleport/sshutils"
|
||||
"github.com/gravitational/teleport/sshutils/scp"
|
||||
"github.com/gravitational/teleport/utils"
|
||||
@@ -422,12 +423,19 @@ 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) {
|
||||
events, err := c.clt.GetEvents()
|
||||
f, err := events.FilterFromURL(r.URL.Query())
|
||||
if err != nil {
|
||||
log.Errorf("failed to retrieve events: %v")
|
||||
log.Errorf("failed to retrieve events: %v", err)
|
||||
replyErr(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
events, err := c.clt.GetEvents(*f)
|
||||
if err != nil {
|
||||
log.Errorf("failed to retrieve events: %v", err)
|
||||
replyErr(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
log.Infof("got events: %v", events)
|
||||
roundtrip.ReplyJSON(w, http.StatusOK, events)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package boltlog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gravitational/teleport/events"
|
||||
)
|
||||
|
||||
// cfg represents JSON config for bolt backlend
|
||||
type cfg struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// FromString initialized the backend from backend-specific string
|
||||
func FromString(v string) (events.Log, error) {
|
||||
if len(v) == 0 {
|
||||
return nil, fmt.Errorf(
|
||||
`please supply a valid dictionary, e.g. {"path": "/opt/bolt.db"}`)
|
||||
}
|
||||
var c *cfg
|
||||
if err := json.Unmarshal([]byte(v), &c); err != nil {
|
||||
return nil, fmt.Errorf("invalid backend configuration format, err: %v", err)
|
||||
}
|
||||
return New(c.Path)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package boltlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/backend/boltbk"
|
||||
"github.com/gravitational/teleport/events"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/boltdb/bolt"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
|
||||
)
|
||||
|
||||
type BoltLog struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
func New(path string) (*BoltLog, error) {
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BoltLog{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BoltLog) Close() error {
|
||||
return b.db.Close()
|
||||
}
|
||||
|
||||
func (b *BoltLog) Log(id lunk.EventID, e lunk.Event) {
|
||||
en := lunk.NewEntry(id, e)
|
||||
en.Time = time.Now()
|
||||
b.LogEntry(en)
|
||||
}
|
||||
|
||||
func (b *BoltLog) LogEntry(en lunk.Entry) error {
|
||||
sid := en.Properties[SessionID]
|
||||
btm, err := en.Time.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(en)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
if sid != "" {
|
||||
bkt, err := boltbk.UpsertBucket(tx, []string{"sessions", sid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bkt.Put([]byte(btm), bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bkt, err := boltbk.UpsertBucket(tx, []string{"events"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bkt.Put([]byte(btm), bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func (b *BoltLog) GetEvents(f events.Filter) ([]lunk.Entry, error) {
|
||||
log.Infof("Get events: %v", f)
|
||||
if f.Start.IsZero() {
|
||||
return nil, fmt.Errorf("supply either starting point")
|
||||
}
|
||||
if f.Limit == 0 {
|
||||
f.Limit = events.DefaultLimit
|
||||
}
|
||||
|
||||
out := []lunk.Entry{}
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
var bkt *bolt.Bucket
|
||||
var err error
|
||||
if f.SessionID != "" {
|
||||
bkt, err = boltbk.GetBucket(tx, []string{"sessions", f.SessionID})
|
||||
} else {
|
||||
bkt, err = boltbk.GetBucket(tx, []string{"events"})
|
||||
}
|
||||
if err != nil {
|
||||
if backend.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
startKey, err := f.Start.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endKey, err := f.End.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// choose start key and iter function based on start or end
|
||||
c := bkt.Cursor()
|
||||
var count = 0
|
||||
var flimit limitfn
|
||||
var fnext nextfn
|
||||
var key, val []byte
|
||||
if f.Order >= 0 { // ascending
|
||||
fnext = c.Next
|
||||
flimit = func() bool {
|
||||
if !f.End.IsZero() && bytes.Compare(key, endKey) > 0 {
|
||||
return false
|
||||
}
|
||||
return count < f.Limit
|
||||
}
|
||||
} else {
|
||||
fnext = c.Prev // descending
|
||||
flimit = func() bool {
|
||||
if !f.End.IsZero() && bytes.Compare(key, endKey) <= 0 {
|
||||
return false
|
||||
}
|
||||
return count < f.Limit
|
||||
}
|
||||
}
|
||||
key, val = c.Seek(startKey)
|
||||
// this can happen if we supplied the key after last key
|
||||
// we jump to last key to check if it still matches
|
||||
if key == nil && f.Order < 0 {
|
||||
key, val = c.Last()
|
||||
}
|
||||
|
||||
for ; key != nil && flimit(); key, val = fnext() {
|
||||
var e *lunk.Entry
|
||||
if err := json.Unmarshal(val, &e); err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, *e)
|
||||
count += 1
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
const SessionID = "sessionid"
|
||||
|
||||
type nextfn func() ([]byte, []byte)
|
||||
type limitfn func() bool
|
||||
@@ -0,0 +1,38 @@
|
||||
package boltlog
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gravitational/teleport/events/test"
|
||||
|
||||
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func TestBolt(t *testing.T) { TestingT(t) }
|
||||
|
||||
type BoltLogSuite struct {
|
||||
l *BoltLog
|
||||
suite test.EventSuite
|
||||
dir string
|
||||
}
|
||||
|
||||
var _ = Suite(&BoltLogSuite{})
|
||||
|
||||
func (s *BoltLogSuite) SetUpTest(c *C) {
|
||||
s.dir = c.MkDir()
|
||||
|
||||
var err error
|
||||
s.l, err = New(filepath.Join(s.dir, "db"))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.suite.L = s.l
|
||||
}
|
||||
|
||||
func (s *BoltLogSuite) TearDownTest(c *C) {
|
||||
c.Assert(s.l.Close(), IsNil)
|
||||
}
|
||||
|
||||
func (s *BoltLogSuite) TestEventsCRUD(c *C) {
|
||||
s.suite.EventsCRUD(c)
|
||||
}
|
||||
@@ -54,6 +54,11 @@ func NewExec(command string, out io.Reader, code int, err error) *Exec {
|
||||
|
||||
// Exec is a result of execution of a remote command on the target server
|
||||
type Exec struct {
|
||||
// User is SSH user
|
||||
User string `json:"user"`
|
||||
|
||||
SessionID string `json:"sid"`
|
||||
|
||||
// Command is a command name with arguments
|
||||
Command string `json:"command"`
|
||||
|
||||
@@ -85,6 +90,8 @@ func NewShell(conn ssh.ConnMetadata, shell string, log io.Reader, code int, err
|
||||
|
||||
// Shell is a result of execution of a in interactive shell
|
||||
type Shell struct {
|
||||
SessionID string `json:"sid"`
|
||||
|
||||
// Shell is a shell name
|
||||
Shell string `json:"command"`
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Limit int
|
||||
Order int
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (f Filter) String() string {
|
||||
st, err := f.Start.MarshalText()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if f.Start.IsZero() {
|
||||
st = []byte("<zero-time>")
|
||||
}
|
||||
et, err := f.End.MarshalText()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if f.End.IsZero() {
|
||||
et = []byte("<zero-time>")
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Filter(start=%v, end=%v, limit=%v, order=%v, sid=%v)",
|
||||
string(st), string(et), f.Limit, f.Order, f.SessionID)
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultLimit = 20
|
||||
Asc = 1
|
||||
Desc = -1
|
||||
)
|
||||
|
||||
type Log interface {
|
||||
Log(id lunk.EventID, e lunk.Event)
|
||||
LogEntry(lunk.Entry) error
|
||||
GetEvents(filter Filter) ([]lunk.Entry, error)
|
||||
}
|
||||
|
||||
func FilterToURL(f Filter) (url.Values, error) {
|
||||
st, err := f.Start.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
et, err := f.End.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals := make(url.Values)
|
||||
vals.Set("start", string(st))
|
||||
vals.Set("end", string(et))
|
||||
vals.Set("limit", strconv.Itoa(f.Limit))
|
||||
vals.Set("order", strconv.Itoa(f.Order))
|
||||
vals.Set("sid", f.SessionID)
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func FilterFromURL(vals url.Values) (*Filter, error) {
|
||||
var f Filter
|
||||
var err error
|
||||
if vals.Get("start") != "" {
|
||||
if err = f.Start.UnmarshalText([]byte(vals.Get("start"))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if vals.Get("end") != "" {
|
||||
if err = f.End.UnmarshalText([]byte(vals.Get("end"))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if vals.Get("limit") != "" {
|
||||
if f.Limit, err = strconv.Atoi(vals.Get("limit")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if vals.Get("order") != "" {
|
||||
if f.Order, err = strconv.Atoi(vals.Get("order")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
f.SessionID = vals.Get("sid")
|
||||
return &f, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// package test contains a log backend acceptance test suite that is
|
||||
// implementation independant each backend will use the suite to test itself
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"github.com/gravitational/teleport/events"
|
||||
|
||||
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func TestEventLog(t *testing.T) { TestingT(t) }
|
||||
|
||||
type EventSuite struct {
|
||||
L events.Log
|
||||
}
|
||||
|
||||
func (s *EventSuite) EventsCRUD(c *C) {
|
||||
start := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
|
||||
e1 := lunk.NewEntry(lunk.NewRootEventID(),
|
||||
&events.AuthAttempt{SessionID: "sid1", User: "bob"})
|
||||
e1.Time = start
|
||||
c.Assert(s.L.LogEntry(e1), IsNil)
|
||||
|
||||
e2 := lunk.NewEntry(
|
||||
lunk.NewRootEventID(),
|
||||
&events.AuthAttempt{SessionID: "sid1", User: "alice"})
|
||||
e2.Time = start.Add(time.Second)
|
||||
|
||||
c.Assert(s.L.LogEntry(e2), IsNil)
|
||||
|
||||
e3 := lunk.NewEntry(
|
||||
lunk.NewRootEventID(),
|
||||
&events.AuthAttempt{SessionID: "sid2", User: "bob"})
|
||||
e3.Time = start.Add(2 * time.Second)
|
||||
|
||||
c.Assert(s.L.LogEntry(e3), IsNil)
|
||||
|
||||
// get last 2 events
|
||||
es, err := s.L.GetEvents(
|
||||
events.Filter{
|
||||
Start: start.Add(2 * time.Second),
|
||||
Order: events.Desc,
|
||||
Limit: 2,
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(e2p(es...), DeepEquals, e2p(e3, e2))
|
||||
|
||||
// get last 2 events for session sid1
|
||||
es, err = s.L.GetEvents(
|
||||
events.Filter{
|
||||
Start: start.Add(2 * time.Second),
|
||||
Order: events.Desc,
|
||||
Limit: 2,
|
||||
SessionID: "sid1",
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(e2p(es...), DeepEquals, e2p(e2, e1))
|
||||
|
||||
// get events in range from start to end
|
||||
es, err = s.L.GetEvents(
|
||||
events.Filter{Start: start, End: start.Add(time.Second)})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(e2p(es...), DeepEquals, e2p(e1, e2))
|
||||
}
|
||||
|
||||
func e2p(es ...lunk.Entry) []map[string]string {
|
||||
out := make([]map[string]string, len(es))
|
||||
for i, a := range es {
|
||||
out[i] = a.Properties
|
||||
}
|
||||
return out
|
||||
}
|
||||
+27
-8
@@ -10,13 +10,14 @@ import (
|
||||
"github.com/gravitational/teleport/backend/boltbk"
|
||||
"github.com/gravitational/teleport/backend/etcdbk"
|
||||
"github.com/gravitational/teleport/cp"
|
||||
"github.com/gravitational/teleport/events"
|
||||
"github.com/gravitational/teleport/events/boltlog"
|
||||
"github.com/gravitational/teleport/session"
|
||||
"github.com/gravitational/teleport/srv"
|
||||
"github.com/gravitational/teleport/tun"
|
||||
"github.com/gravitational/teleport/utils"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"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/Godeps/_workspace/src/golang.org/x/crypto/ssh"
|
||||
@@ -79,6 +80,12 @@ func initAuth(t *TeleportService, cfg Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
elog, err := initEventBackend(
|
||||
cfg.Auth.EventBackend, cfg.Auth.EventBackendConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asrv, signer, err := auth.Init(
|
||||
b, authority.New(), cfg.FQDN, cfg.Auth.Domain, cfg.DataDir)
|
||||
if err != nil {
|
||||
@@ -88,7 +95,7 @@ func initAuth(t *TeleportService, cfg Config) error {
|
||||
|
||||
// register HTTP API endpoint
|
||||
t.RegisterFunc(func() error {
|
||||
apisrv := auth.NewAPIServer(asrv, memlog.New(), session.New(b))
|
||||
apisrv := auth.NewAPIServer(asrv, elog, session.New(b))
|
||||
t, err := trace.New(apisrv, log.GetLogger().Writer(log.SeverityInfo))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to start: %v", err)
|
||||
@@ -172,7 +179,7 @@ func initSSHEndpoint(t *TeleportService, cfg Config) error {
|
||||
elog := &FanOutEventLogger{
|
||||
Loggers: []lunk.EventLogger{
|
||||
lunk.NewTextEventLogger(log.GetLogger().Writer(log.SeverityInfo)),
|
||||
lunk.NewJSONEventLogger(client.GetLogWriter()),
|
||||
client,
|
||||
}}
|
||||
|
||||
s, err := srv.New(cfg.SSH.Addr,
|
||||
@@ -257,7 +264,7 @@ func initTunAgent(t *TeleportService, cfg Config) error {
|
||||
elog := &FanOutEventLogger{
|
||||
Loggers: []lunk.EventLogger{
|
||||
lunk.NewTextEventLogger(log.GetLogger().Writer(log.SeverityInfo)),
|
||||
lunk.NewJSONEventLogger(client.GetLogWriter()),
|
||||
client,
|
||||
}}
|
||||
|
||||
a, err := tun.NewAgent(
|
||||
@@ -292,6 +299,14 @@ func initBackend(btype, bcfg string) (backend.Backend, error) {
|
||||
return nil, fmt.Errorf("unsupported backend type: %v", btype)
|
||||
}
|
||||
|
||||
func initEventBackend(btype, bcfg string) (events.Log, error) {
|
||||
switch btype {
|
||||
case "bolt":
|
||||
return boltlog.FromString(bcfg)
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported backend type: %v", btype)
|
||||
}
|
||||
|
||||
func initLogging(ltype, severity string) error {
|
||||
s, err := log.SeverityFromString(severity)
|
||||
if err != nil {
|
||||
@@ -338,12 +353,16 @@ type Config struct {
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
Enabled bool
|
||||
HTTPAddr utils.NetAddr
|
||||
SSHAddr utils.NetAddr
|
||||
Domain string
|
||||
Enabled bool
|
||||
HTTPAddr utils.NetAddr
|
||||
SSHAddr utils.NetAddr
|
||||
Domain string
|
||||
|
||||
Backend string
|
||||
BackendConfig string
|
||||
|
||||
EventBackend string
|
||||
EventBackendConfig string
|
||||
}
|
||||
|
||||
type SSHConfig struct {
|
||||
|
||||
+9
-1
@@ -54,12 +54,20 @@ func main() {
|
||||
|
||||
flag.StringVar(
|
||||
&cfg.Auth.Backend, "authBackend", "etcd",
|
||||
"auth backend type, currently only 'etcd'")
|
||||
"auth backend type, 'etcd' or 'bolt'")
|
||||
|
||||
flag.StringVar(
|
||||
&cfg.Auth.BackendConfig, "authBackendConfig", "",
|
||||
"auth backend-specific configuration string")
|
||||
|
||||
flag.StringVar(
|
||||
&cfg.Auth.EventBackend, "authEventBackend", "bolt",
|
||||
"event backend type, currently only 'bolt'")
|
||||
|
||||
flag.StringVar(
|
||||
&cfg.Auth.EventBackendConfig, "authEventBackendConfig", "",
|
||||
"event backend-specific configuration string")
|
||||
|
||||
flag.Var(
|
||||
utils.NewNetAddrVal(
|
||||
utils.NetAddr{
|
||||
|
||||
+5
-3
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport/auth"
|
||||
"github.com/gravitational/teleport/backend"
|
||||
"github.com/gravitational/teleport/events"
|
||||
"github.com/gravitational/teleport/sshutils"
|
||||
"github.com/gravitational/teleport/utils"
|
||||
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
|
||||
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
|
||||
@@ -24,7 +26,7 @@ type RemoteSite interface {
|
||||
GetName() string
|
||||
GetServers() ([]backend.Server, error)
|
||||
GetStatus() string
|
||||
GetEvents() ([]interface{}, error)
|
||||
GetEvents(events.Filter) ([]lunk.Entry, error)
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
@@ -172,8 +174,8 @@ type remoteSite struct {
|
||||
clt *auth.Client
|
||||
}
|
||||
|
||||
func (s *remoteSite) GetEvents() ([]interface{}, error) {
|
||||
return s.clt.GetEvents()
|
||||
func (s *remoteSite) GetEvents(filter events.Filter) ([]lunk.Entry, error) {
|
||||
return s.clt.GetEvents(filter)
|
||||
}
|
||||
|
||||
func (s *remoteSite) String() string {
|
||||
|
||||
Reference in New Issue
Block a user