diff --git a/Makefile b/Makefile
index 5eebfb068ce..daa5b434641 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/assets/static/js/grv/events.js b/assets/static/js/grv/events.js
deleted file mode 100644
index 000cd0ee487..00000000000
--- a/assets/static/js/grv/events.js
+++ /dev/null
@@ -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 (
-
- );
- }
-});
-
-
-var EventsBox = React.createClass({
- render: function() {
- if (this.props.entries.length == 0) {
- return (
-
-
Logged Events
- There are no events logged. Log in via SSH to watch the events.
-);
- }
- return (
-
-
-
- );
- }
-});
-
-
-var EventsTable = React.createClass({
- render: function() {
- var show = this.props.onShowEvent
- var keyNodes = this.props.entries.map(function (event, index) {
- return (
-
- );
- });
- return (
-
-
-
- |
- Time |
- Event |
- User |
- Client IP |
- Remote IP |
- Data |
-
-
-
- {keyNodes}
-
-
- );
- }
-});
-
-
-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 (
-
- |
- {e.time} |
- {e.schema} |
- {e.properties.user} |
- {e.properties.localaddr} |
- {e.properties.remoteaddr} |
- {d.text} |
-
- );
- }
-});
-
-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 (
-
-
-
- );
- }
-});
-
-
-React.render(
- ,
- document.body
-);
diff --git a/assets/static/js/grv/events.jsx b/assets/static/js/grv/events.jsx
new file mode 100644
index 00000000000..69023580a8e
--- /dev/null
+++ b/assets/static/js/grv/events.jsx
@@ -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 (
+ );
+ }
+});
+
+
+var EventsBox = React.createClass({
+ render: function() {
+ if (this.props.entries.length == 0) {
+ return (
+
+
Logged Events
+ There are no events logged. Log in via SSH to watch the events.
+ );
+ }
+ return (
+
+
+
+ );
+ }
+});
+
+
+var EventsTable = React.createClass({
+ render: function() {
+ var show = this.props.onShowEvent
+ var keyNodes = this.props.entries.map(function (event, index) {
+ return (
+
+ );
+ });
+ return (
+
+
+
+ |
+ Time |
+ Event |
+ User |
+ Client IP |
+ Remote IP |
+ Data |
+
+
+
+ {keyNodes}
+
+
+ );
+ }
+});
+
+
+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 (
+
+ |
+ {e.time} |
+ {e.schema} |
+ {e.properties.user} |
+ {e.properties.localaddr} |
+ {e.properties.remoteaddr} |
+ {d.text} |
+
+ );
+ }
+});
+
+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 (
+
+
+
+ );
+ }
+});
+
+
+React.render(
+ ,
+ document.body
+);
diff --git a/assets/static/js/grv/lib.js b/assets/static/js/grv/lib.js
index 14dad3fdf1c..521aacfaa12 100644
--- a/assets/static/js/grv/lib.js
+++ b/assets/static/js/grv/lib.js
@@ -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;
+}
diff --git a/assets/static/tpl/events.tpl b/assets/static/tpl/events.tpl
index 7d6a195235d..1cb69c4ad1c 100644
--- a/assets/static/tpl/events.tpl
+++ b/assets/static/tpl/events.tpl
@@ -10,5 +10,5 @@
{{ define "script" }}
-
+
{{ end }}
diff --git a/auth/api.go b/auth/api.go
index 08d39f4fba3..242abcb7760 100644
--- a/auth/api.go
+++ b/auth/api.go
@@ -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
diff --git a/auth/clt.go b/auth/clt.go
index 968dd0e39b2..0dd31f1083c 100644
--- a/auth/clt.go
+++ b/auth/clt.go
@@ -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()
-}
diff --git a/auth/srv.go b/auth/srv.go
index 5ee7b672615..456da0c1603 100644
--- a/auth/srv.go
+++ b/auth/srv.go
@@ -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 {
diff --git a/auth/srv_test.go b/auth/srv_test.go
index d269a9a87f2..148c04e1c45 100644
--- a/auth/srv_test.go
+++ b/auth/srv_test.go
@@ -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) {
diff --git a/auth/tun_test.go b/auth/tun_test.go
index 647a5856a98..3544dd5e1ae 100644
--- a/auth/tun_test.go
+++ b/auth/tun_test.go
@@ -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{
diff --git a/backend/boltbk/boltbk.go b/backend/boltbk/boltbk.go
index bea54381a5f..5f8e74c70a0 100644
--- a/backend/boltbk/boltbk.go
+++ b/backend/boltbk/boltbk.go
@@ -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{
diff --git a/cp/cp.go b/cp/cp.go
index 393601cd68e..6d6cc205858 100644
--- a/cp/cp.go
+++ b/cp/cp.go
@@ -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)
}
diff --git a/events/boltlog/bcfg.go b/events/boltlog/bcfg.go
new file mode 100644
index 00000000000..4d84cd7df83
--- /dev/null
+++ b/events/boltlog/bcfg.go
@@ -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)
+}
diff --git a/events/boltlog/bl.go b/events/boltlog/bl.go
new file mode 100644
index 00000000000..a4e7dfd6c4b
--- /dev/null
+++ b/events/boltlog/bl.go
@@ -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
diff --git a/events/boltlog/bl_test.go b/events/boltlog/bl_test.go
new file mode 100644
index 00000000000..026d9db1144
--- /dev/null
+++ b/events/boltlog/bl_test.go
@@ -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)
+}
diff --git a/events/events.go b/events/events.go
index 9318de91dae..26a11446dde 100644
--- a/events/events.go
+++ b/events/events.go
@@ -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"`
diff --git a/events/log.go b/events/log.go
new file mode 100644
index 00000000000..ff2ae110e05
--- /dev/null
+++ b/events/log.go
@@ -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("")
+ }
+ et, err := f.End.MarshalText()
+ if err != nil {
+ return err.Error()
+ }
+ if f.End.IsZero() {
+ et = []byte("")
+ }
+ 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
+}
diff --git a/events/test/logsuite.go b/events/test/logsuite.go
new file mode 100644
index 00000000000..712bf3059fe
--- /dev/null
+++ b/events/test/logsuite.go
@@ -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
+}
diff --git a/service/service.go b/service/service.go
index a0f9c502d25..b702b83deac 100644
--- a/service/service.go
+++ b/service/service.go
@@ -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 {
diff --git a/teleport/main.go b/teleport/main.go
index 5b384e583b3..987c13bebb5 100644
--- a/teleport/main.go
+++ b/teleport/main.go
@@ -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{
diff --git a/tun/srv.go b/tun/srv.go
index ad8f13674d8..5e2b298949f 100644
--- a/tun/srv.go
+++ b/tun/srv.go
@@ -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 {