Cleanup and make sure teleport CP is reusable

This commit is contained in:
klizhentas
2015-07-14 17:52:12 -07:00
parent 9da00a86cc
commit 3ce5afc00b
27 changed files with 797 additions and 651 deletions
+12 -5
View File
@@ -1,4 +1,4 @@
ETCD_NODE1 := http://127.0.0.1:4001
TCD_NODE1 := http://127.0.0.1:4001
ETCD_NODES := ${ETCD_NODE1}
ETCD_FLAGS := TELEPORT_TEST_ETCD_NODES=${ETCD_NODES}
@@ -81,7 +81,7 @@ run-tun: install
teleport -tun\
-log=console\
-logSeverity=INFO\
-dataDir=/tmp\
-dataDir=/var/lib/teleport\
-fqdn=node1.gravitational.io\
-tunToken=/tmp/token\
-tunSrvAddr=tcp://lens.gravitational.io:34000\
@@ -92,16 +92,23 @@ run-embedded: install
rm -f /tmp/teleport.auth.sock
teleport -auth\
-authBackend=bolt\
-authBackendConfig='{"path": "/tmp/teleport.auth.db"}'\
-authBackendConfig='{"path": "/var/lib/teleport/teleport.auth.db"}'\
-authDomain=gravitational.io\
-authEventBackend=bolt\
-authEventBackendConfig='{"path": "/var/lib/teleport/teleport.event.db"}'\
-authRecordBackend=bolt\
-authRecordBackendConfig='{"path": "/var/lib/teleport/records"}'\
-log=console\
-logSeverity=INFO\
-dataDir=/tmp\
-dataDir=/var/lib/teleport\
-fqdn=auth.gravitational.io\
-ssh\
-authServer=tcp://auth.gravitational.io:33000\
-tun\
-tunSrvAddr=tcp://lens.vendor.io:34000
-tunSrvAddr=tcp://lens.vendor.io:34000\
-cp\
-cpAssetsDir=$(GOPATH)/src/github.com/gravitational/teleport\
-cpDomain=gravitational.io
run-simple: install
+2 -2
View File
@@ -34,7 +34,7 @@ var EventsPage = React.createClass({
<LeftNavBar current="events"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Timeline" url="/events"/>
<PageHeader icon="fa fa-list" title="Timeline"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box colClass="col-lg-8">
<EventsBox events={this.state.entries} onShowEvent={this.showEvent}/>
@@ -239,6 +239,6 @@ var EventForm = React.createClass({
React.render(
<EventsPage url="/api/events" pollInterval={2000}/>,
<EventsPage url={grv.path("api", "events")} pollInterval={2000}/>,
document.body
);
+22 -18
View File
@@ -40,31 +40,31 @@ var LeftNavBar = React.createClass({
<ul className="nav" id="side-menu">
<UserBarItem/>
<li className={this.className("keys")}>
<a href="/keys">
<i className="fa fa-laptop"></i>
<a href={grv.path("keys")}>
<i className="fa fa-key"></i>
<span className="nav-label">Keys</span>
</a>
</li>
<li className={this.className("events")}>
<a href="/events">
<a href={grv.path("events")}>
<i className="fa fa-list"></i>
<span className="nav-label">Timeline</span>
</a>
</li>
<li className={this.className("webtuns")}>
<a href="/webtuns">
<a href={grv.path("webtuns")}>
<i className="fa fa-arrows-h"></i>
<span className="nav-label">Web Tunnels</span>
</a>
</li>
<li className={this.className("servers")}>
<a href="/servers">
<a href={grv.path("servers")}>
<i className="fa fa-hdd-o"></i>
<span className="nav-label">Servers</span>
</a>
</li>
<li className={this.className("sessions")}>
<a href="/sessions">
<a href={grv.path("sessions")}>
<i className="fa fa-wechat"></i>
<span className="nav-label">Sessions</span>
</a>
@@ -106,19 +106,23 @@ var TopNavBar = React.createClass({
var PageHeader = React.createClass({
render: function() {
var brs = this.props.breadcrumbs || [];
return (
<div className="row wrapper border-bottom white-bg page-heading">
<div className="col-lg-10">
<h2>{this.props.title}</h2>
<ol className="breadcrumb">
<li className="active">
<a href={this.props.url}>{this.props.title}</a>
</li>
</ol>
</div>
<div className="col-lg-2">
</div>
</div>);
<div className="row wrapper border-bottom white-bg page-heading">
<div className="col-lg-10">
<h2><i className={this.props.icon}></i> {this.props.title}</h2>
<ol className="breadcrumb">
{brs.map(function(b, idx) {
if (idx == brs.length -1 ) {
return <li className="active"><a href={b.url}><strong>{b.title}</strong></a></li>;
}
return <li><a href={b.url}>{b.title}</a></li>;
})}
</ol>
</div>
<div className="col-lg-2">
</div>
</div>);
}
});
+4 -4
View File
@@ -62,8 +62,8 @@ var KeysPage = React.createClass({
<LeftNavBar current="keys"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="SSH Keys" url="/keys"/>
<div className="wrapper wrapper-content animated fadeInRight">
<PageHeader icon="fa fa-key" title="Keys"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<KeysBox keys={this.state.keys} onOpenKeyForm={this.openKeyForm} onKeyDelete={this.deleteKey}/>
</Box>
@@ -231,6 +231,6 @@ var KeyRow = React.createClass({
React.render(
<KeysPage url="/api/keys"/>,
document.body
<KeysPage url={grv.path("api", "keys")}/>,
document.body
);
+1 -1
View File
@@ -18,7 +18,7 @@ Player.prototype.iterChunks = function(chunk) {
console.log("iterChunks: ", chunk);
var self = this;
$.ajax({
url: "/api/records/" +self.rid +"/chunks?"+$.param([{name: "start", value: chunk}, {name: "end", value: chunk+1}]),
url: grv.path("api", "records", self.rid, "chunks") + "?"+$.param([{name: "start", value: chunk}, {name: "end", value: chunk+1}]),
type: "GET",
dataType: 'json',
success: function(data) {
+45 -45
View File
@@ -1,54 +1,54 @@
'use strict';
var ServersPage = React.createClass({
getInitialState: function(){
return {servers: []};
},
componentDidMount: function() {
this.reload();
getInitialState: function(){
return {servers: []};
},
componentDidMount: function() {
this.reload();
setInterval(this.reload, this.props.pollInterval);
},
},
connect: function(srv){
this.refs.server.value = srv;
React.findDOMNode(this.refs.session).submit();
},
disconnect: function(srv){
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
if(this.modalOpen == true) {
return
}
this.setState({servers: 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="servers"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="SSH Servers" url="/servers"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<ServersTable servers={this.state.servers} onConnect={this.connect}/>
</Box>
</div>
<PageFooter/>
</div>
<form ref="session" action="/sessions" method="POST" style={{display: 'none'}}>
<input name="server" type="text" ref="server"/>
</form>
</div>
);
}
},
disconnect: function(srv){
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
if(this.modalOpen == true) {
return
}
this.setState({servers: 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="servers"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="SSH Servers" icon="fa fa-hdd-o"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<ServersTable servers={this.state.servers} onConnect={this.connect}/>
</Box>
</div>
<PageFooter/>
</div>
<form ref="session" action={grv.path("sessions")} method="POST" style={{display: 'none'}}>
<input name="server" type="text" ref="server"/>
</form>
</div>
);
}
});
@@ -93,6 +93,6 @@ var ServerRow = React.createClass({
});
React.render(
<ServersPage url="/api/servers" pollInterval={2000}/>,
<ServersPage url={grv.path("api","servers")} pollInterval={2000}/>,
document.body
);
+3 -3
View File
@@ -123,7 +123,7 @@ var ChatBox = React.createClass({
sendMessage: function(message) {
var box = $(React.findDOMNode(this.refs.message));
$.ajax({
url: "/api/sessions/" +session.id +"/messages",
url: grv.path("api", "sessions", session.id, "messages"),
type: "POST",
dataType: 'json',
data: JSON.stringify(message),
@@ -280,7 +280,7 @@ var ConsoleBox = React.createClass({
$(parent).append('<div id="'+self.term_id(srv)+'"></div>');
var termNode = document.getElementById(this.term_id(srv));
var hostport = location.hostname+(location.port ? ':'+location.port: '');
var socket = new WebSocket("ws://"+hostport+"/api/ssh/connect/"+srv+"/sessions/"+self.props.session.id, "proto");
var socket = new WebSocket("ws://"+hostport+grv.path("api", "ssh", "connect", srv, "sessions", self.props.session.id), "proto");
var term = new Terminal({
cols: 120,
rows: 32,
@@ -616,7 +616,7 @@ var DownloadForm = React.createClass({
React.render(
<SessionPage url={"/api/sessions/"+session.id} pollInterval={2000}/>,
<SessionPage url={grv.path("api", "sessions", session.id)} pollInterval={2000}/>,
document.body
);
+5 -5
View File
@@ -29,7 +29,7 @@ var SessionsPage = React.createClass({
<LeftNavBar current="sessions"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Active Sessions" url="/sessions"/>
<PageHeader title="Active Sessions" icon="fa fa-wechat"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<SessionsTable sessions={this.state.sessions}/>
@@ -74,21 +74,21 @@ var SessionRow = React.createClass({
var se = parseSession(this.props.se);
var users = se.users.map(function (u, index) {
return (
<a href={"/sessions/"+se.id}>{u}&nbsp;</a>
<a href={grv.path("sessions", se.id)}>{u}&nbsp;</a>
);
});
return (
<tr>
<td><a href={"/sessions/"+se.id}>{se.id}</a></td>
<td><a href={grv.path("sessions", se.id)}>{se.id}</a></td>
<td>{timeSince(se.last_active)} ago</td>
<td>{users}</td>
<td><a href={"/sessions/"+se.id}><i className="fa fa-tty text-navy"></i></a></td>
<td><a href={grv.path("sessions", se.id)}><i className="fa fa-tty text-navy"></i></a></td>
</tr>
);
}
});
React.render(
<SessionsPage url="/api/sessions" pollInterval={1000}/>,
<SessionsPage url={grv.path("api", "sessions")} pollInterval={1000}/>,
document.body
);
-214
View File
@@ -1,214 +0,0 @@
'use strict';
var WebTunsPage = React.createClass({
getInitialState: function(){
return {tuns: []};
},
componentDidMount: function() {
this.reload();
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: "json",
success: function(data) {
this.setState({tuns: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
openTunForm: function(){
this.refs.tun.open();
},
addTun: function(tun){
$.ajax({
url: this.props.url,
dataType: "json",
type: "POST",
data: tun,
success: function(data) {
this.refs.tun.reset();
this.reload();
}.bind(this),
error: function(xhr, status, err) {
this.refs.tun.reset();
console.error(this.props.url, status, err.toString());
alert(err.toString());
}.bind(this)
});
},
deleteTun: function(prefix) {
if (!confirm("Are you sure you want to delete tun " + prefix + " ?")) {
return;
}
$.ajax({
url: this.props.url+"/"+prefix,
type: "DELETE",
dataType: "json",
success: function(data) {
this.reload();
}.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="webtuns"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Web SSH Tunnels" url="/webtuns"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<WebTunsBox tuns={this.state.tuns} onOpenTunForm={this.openTunForm} onTunDelete={this.deleteTun}/>
</Box>
</div>
<PageFooter/>
</div>
<WebTunForm ref="tun" onAddTun={this.addTun}/>
</div>
);
}
});
var WebTunsBox = React.createClass({
render: function() {
if (this.props.tuns.length == 0) {
return (
<div className="text-center m-t-lg">
<h1>
Web SSH Tunnels
</h1>
<p>You have no Web SSH Tunnels.</p>
<p>Tunnels provide password protected access to a web server on remote machine through SSH tunnel.</p>
<br/><br/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenTunForm}>
<i className="fa fa-check"></i>&nbsp;Add Web Tunnel
</BootstrapButton>
</div>);
}
return (
<div>
<WebTunsTable keys={this.props.tuns} onTunDelete={this.props.onTunDelete}/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenTunForm}>
<i className="fa fa-check"></i>&nbsp;Add Web Tunnel
</BootstrapButton>
</div>
);
}
})
var WebTunForm = React.createClass({
open: function() {
this.refs.modal.open();
},
close: function() {
this.refs.modal.close();
},
reset: function() {
React.findDOMNode(this.refs.prefix).value = "";
React.findDOMNode(this.refs.target).value = "";
React.findDOMNode(this.refs.proxy).value = "";
this.refs.modal.close();
},
confirm: function() {
var prefix = React.findDOMNode(this.refs.prefix).value.trim();
var target = React.findDOMNode(this.refs.target).value.trim();
var proxy = React.findDOMNode(this.refs.proxy).value.trim();
if (!prefix || !target || !proxy) {
alert("Prefix, target and proxy can not be empty");
return;
}
this.props.onAddTun({prefix: prefix, target: target, proxy: proxy});
},
render: function() {
return (
<BootstrapModal
icon="fa-arrows-h"
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.reset}
onConfirm={this.confirm}
title="Add Web Tunel">
<div className="form-group">
<label>Subdomain</label>
<div className="input-group m-b">
<input placeholder="subdomain" className="form-control" ref="prefix" type="text"/>
<span className="input-group-addon">.gravitational.io</span>
</div>
<p>This subdomain will point to the remote web server when accessed. It will be protected by the standard portal password.</p>
</div>
<div className="form-group">
<label>SSH Proxy</label>
<input placeholder="host:port" className="form-control" ref="proxy"/>
<p>SSH proxy in form of host:port will be used to access the target address</p>
</div>
<div className="form-group">
<label>SSH Proxy</label>
<input placeholder="http://host:port" className="form-control" ref="target"/>
<p>Target web server address</p>
</div>
</BootstrapModal>
);
}
});
var WebTunsTable = React.createClass({
render: function() {
var tunDelete = this.props.onTunDelete
var rows = this.props.keys.map(function (tun, index) {
return (
<WebTunRow tun={tun} key={index} onTunDelete={tunDelete}/>
);
});
return (
<table className="table table-striped">
<thead>
<tr>
<th>Subdomain</th>
<th>Proxy</th>
<th>Target</th>
<th>Portal</th>
<th></th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>);
}
});
var WebTunRow = React.createClass({
handleDelete: function(e) {
e.preventDefault();
this.props.onTunDelete(this.props.tun.prefix);
},
render: function() {
var base = location.hostname.split(".").slice(1).join(".");
var hostport = base+(location.port ? ':'+location.port: '');
return (
<tr className="tun">
<td>{this.props.tun.prefix}</td>
<td>{this.props.tun.proxy}</td>
<td>{this.props.tun.target}</td>
<td><a href={"http://"+this.props.tun.prefix+"."+hostport} target="_blank">{"http://"+this.props.tun.prefix+"."+hostport}</a></td>
<td><a href="#" onClick={this.handleDelete}><i className="fa fa-times text-navy"></i></a></td>
</tr>
);
}
});
React.render(
<WebTunsPage url="/api/tunnels/web"/>,
document.body
);
+212
View File
@@ -0,0 +1,212 @@
'use strict';
var WebTunsPage = React.createClass({
getInitialState: function(){
return {tuns: []};
},
componentDidMount: function() {
this.reload();
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: "json",
success: function(data) {
this.setState({tuns: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
openTunForm: function(){
this.refs.tun.open();
},
addTun: function(tun){
$.ajax({
url: this.props.url,
dataType: "json",
type: "POST",
data: tun,
success: function(data) {
this.refs.tun.reset();
this.reload();
}.bind(this),
error: function(xhr, status, err) {
this.refs.tun.reset();
toastr.error(err.toString());
}.bind(this)
});
},
deleteTun: function(prefix) {
if (!confirm("Are you sure you want to delete tun " + prefix + " ?")) {
return;
}
$.ajax({
url: this.props.url+"/"+prefix,
type: "DELETE",
dataType: "json",
success: function(data) {
this.reload();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
toastr.error(err.toString());
}.bind(this)
});
},
render: function() {
return (
<div id="wrapper">
<LeftNavBar current="webtuns"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Web Tunnels" icon="fa fa-arrows-h"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<WebTunsBox tuns={this.state.tuns} onOpenTunForm={this.openTunForm} onTunDelete={this.deleteTun}/>
</Box>
</div>
<PageFooter/>
</div>
<WebTunForm ref="tun" onAddTun={this.addTun}/>
</div>
);
}
});
var WebTunsBox = React.createClass({
render: function() {
if (this.props.tuns.length == 0) {
return (
<div className="text-center m-t-lg">
<h1>
Web SSH Tunnels
</h1>
<p>You have no Web SSH Tunnels.</p>
<p>Tunnels provide password protected access to a web server on remote machine through SSH tunnel.</p>
<br/><br/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenTunForm}>
<i className="fa fa-check"></i>&nbsp;Add Web Tunnel
</BootstrapButton>
</div>);
}
return (
<div>
<WebTunsTable keys={this.props.tuns} onTunDelete={this.props.onTunDelete}/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenTunForm}>
<i className="fa fa-check"></i>&nbsp;Add Web Tunnel
</BootstrapButton>
</div>
);
}
})
var WebTunForm = React.createClass({
open: function() {
this.refs.modal.open();
},
close: function() {
this.refs.modal.close();
},
reset: function() {
React.findDOMNode(this.refs.prefix).value = "";
React.findDOMNode(this.refs.target).value = "";
React.findDOMNode(this.refs.proxy).value = "";
this.refs.modal.close();
},
confirm: function() {
var prefix = React.findDOMNode(this.refs.prefix).value.trim();
var target = React.findDOMNode(this.refs.target).value.trim();
var proxy = React.findDOMNode(this.refs.proxy).value.trim();
if (!prefix || !target || !proxy) {
alert("Prefix, target and proxy can not be empty");
return;
}
this.props.onAddTun({prefix: prefix, target: target, proxy: proxy});
},
render: function() {
return (
<BootstrapModal icon="fa-arrows-h"
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.reset}
onConfirm={this.confirm}
title="Add Web Tunel">
<div className="form-group">
<label>Subdomain</label>
<div className="input-group m-b">
<input placeholder="subdomain" className="form-control" ref="prefix" type="text"/>
<span className="input-group-addon">.gravitational.io</span>
</div>
<p>This subdomain will point to the remote web server when accessed. It will be protected by the standard portal password.</p>
</div>
<div className="form-group">
<label>SSH Proxy</label>
<input placeholder="host:port" className="form-control" ref="proxy"/>
<p>SSH proxy in form of host:port will be used to access the target address</p>
</div>
<div className="form-group">
<label>SSH Proxy</label>
<input placeholder="http://host:port" className="form-control" ref="target"/>
<p>Target web server address</p>
</div>
</BootstrapModal>
);
}
});
var WebTunsTable = React.createClass({
render: function() {
var tunDelete = this.props.onTunDelete
var rows = this.props.keys.map(function (tun, index) {
return (
<WebTunRow tun={tun} key={index} onTunDelete={tunDelete}/>
);
});
return (
<table className="table table-striped">
<thead>
<tr>
<th>Subdomain</th>
<th>Proxy</th>
<th>Target</th>
<th>Portal</th>
<th></th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>);
}
});
var WebTunRow = React.createClass({
handleDelete: function(e) {
e.preventDefault();
this.props.onTunDelete(this.props.tun.prefix);
},
render: function() {
var base = location.hostname.split(".").slice(1).join(".");
var hostport = base+(location.port ? ':'+location.port: '');
return (
<tr className="tun">
<td>{this.props.tun.prefix}</td>
<td>{this.props.tun.proxy}</td>
<td>{this.props.tun.target}</td>
<td><a href={"http://"+this.props.tun.prefix+"."+hostport} target="_blank">{"http://"+this.props.tun.prefix+"."+hostport}</a></td>
<td><a href="#" onClick={this.handleDelete}><i className="fa fa-times text-navy"></i></a></td>
</tr>
);
}
});
React.render(
<WebTunsPage url="/api/tunnels/web"/>,
document.body
);
+42 -30
View File
@@ -7,48 +7,60 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ template "title" . }}</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/css/animate.css" rel="stylesheet">
<link href="/static/css/style.min.css" rel="stylesheet">
<link href="/static/css/plugins/chosen/chosen.css" rel="stylesheet">
<link href="/static/css/plugins/toastr/toastr.min.css" rel="stylesheet">
<link href="/static/css/plugins/fileupload/jquery.fileupload.css" rel="stylesheet">
<link href="/static/css/plugins/fileupload/jquery.fileupload-ui.css" rel="stylesheet">
<link href="/static/css/plugins/jsTree/style.min.css" rel="stylesheet">
<link href="/static/css/plugins/datapicker/datepicker3.css" rel="stylesheet">
<link href="{{Path "/static/css/bootstrap.min.css"}}" rel="stylesheet">
<link href="{{Path "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/animate.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/style.min.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/chosen/chosen.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/toastr/toastr.min.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/fileupload/jquery.fileupload.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/fileupload/jquery.fileupload-ui.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/jsTree/style.min.css"}}" rel="stylesheet">
<link href="{{Path "/static/css/plugins/datapicker/datepicker3.css"}}" rel="stylesheet">
</head>
<body>
{{ template "body" . }}
<!-- Mainly scripts -->
<script src="/static/js/JSXTransformer.js"></script>
<script src="/static/js/react.js"></script>
<script src="{{Path "/static/js/JSXTransformer.js"}}"></script>
<script src="{{Path "/static/js/react.js"}}"></script>
<script src="/static/js/jquery-2.1.1.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/plugins/metisMenu/jquery.metisMenu.js"></script>
<script src="/static/js/plugins/slimscroll/jquery.slimscroll.min.js"></script>
<script src="{{Path "/static/js/jquery-2.1.1.js"}}"></script>
<script src="{{Path "/static/js/bootstrap.min.js"}}"></script>
<script src="{{Path "/static/js/plugins/metisMenu/jquery.metisMenu.js"}}"></script>
<script src="{{Path "/static/js/plugins/slimscroll/jquery.slimscroll.min.js"}}"></script>
<!-- Custom and plugin javascript -->
<script src="/static/js/inspinia.js"></script>
<script src="/static/js/plugins/pace/pace.min.js"></script>
<script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
<script src="{{Path "/static/js/inspinia.js"}}"></script>
<script src="{{Path "/static/js/plugins/pace/pace.min.js"}}"></script>
<script src="{{Path "/static/js/plugins/chosen/chosen.jquery.js"}}"></script>
<script src="/static/js/jquery.ui.widget.js"></script>
<script src="/static/js/jquery.iframe-transport.js"></script>
<script src="/static/js/jquery.fileupload.js"></script>
<script src="/static/js/jquery.fileupload-process.js"></script>
<script src="/static/js/plugins/toastr/toastr.min.js"></script>
<script src="/static/js/plugins/jsTree/jstree.min.js"></script>
<script src="/static/js/plugins/download/jquery.fileDownload.js"></script>
<script src="/static/js/plugins/datapicker/bootstrap-datepicker.js"></script>
<script src="{{Path "/static/js/jquery.ui.widget.js"}}"></script>
<script src="{{Path "/static/js/jquery.iframe-transport.js"}}"></script>
<script src="{{Path "/static/js/jquery.fileupload.js"}}"></script>
<script src="{{Path "/static/js/jquery.fileupload-process.js"}}"></script>
<script src="{{Path "/static/js/plugins/toastr/toastr.min.js"}}"></script>
<script src="{{Path "/static/js/plugins/jsTree/jstree.min.js"}}"></script>
<script src="{{Path "/static/js/plugins/download/jquery.fileDownload.js"}}"></script>
<script src="{{Path "/static/js/plugins/datapicker/bootstrap-datepicker.js"}}"></script>
<!-- Gravity stuff -->
<script src="/static/js/grv/lib.js"></script>
<script type="text/jsx" src="/static/js/grv/modal.jsx"></script>
<script type="text/jsx" src="/static/js/grv/frame.jsx"></script>
<script type="text/javascript">
grv = {
prefix: "{{.Prefix}}",
path: function() {
var path = ["{{.Prefix}}" != ""? "{{.Prefix}}":""];
for(var i = 0; i < arguments.length; i++) {
path.push(arguments[i]);
}
return path.join("/");
}
};
</script>
<script src="{{Path "/static/js/grv/lib.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/modal.jsx"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/frame.jsx"}}"></script>
{{ template "script" . }}
</body>
</html>
+3 -3
View File
@@ -9,7 +9,7 @@
{{ end }}
{{ define "script" }}
<script type="text/javascript" src="/static/js/term.js"></script>
<script type="text/javascript" src="/static/js/grv/player.js"></script>
<script type="text/jsx" src="/static/js/grv/events.jsx"></script>
<script type="text/javascript" src="{{Path "/static/js/term.js"}}"></script>
<script type="text/javascript" src="{{Path "/static/js/grv/player.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/events.jsx"}}"></script>
{{ end }}
+1 -1
View File
@@ -9,5 +9,5 @@
{{ end }}
{{ define "script" }}
<script type="text/jsx" src="/static/js/grv/keys.js"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/keys.js"}}"></script>
{{ end }}
+2 -2
View File
@@ -9,6 +9,6 @@
{{ end }}
{{ define "script" }}
<script type="text/javascript" src="/static/js/term.js"></script>
<script type="text/jsx" src="/static/js/grv/servers.jsx"></script>
<script type="text/javascript" src="{{Path "/static/js/term.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/servers.jsx"}}"></script>
{{ end }}
+5 -5
View File
@@ -9,15 +9,15 @@
{{ end }}
{{ define "script" }}
<script type="text/javascript" src="/static/js/term.js"></script>
<script type="text/javascript" src="{{Path "/static/js/term.js"}}"></script>
<script type="text/javascript">
session = {
id: "{{.SessionID}}",
first_server: "{{.ServerAddr}}"
};
</script>
<script type="text/javascript" src="/static/js/grv/sessionlib.js"></script>
<script type="text/javascript" src="/static/js/grv/player.js"></script>
<script type="text/jsx" src="/static/js/grv/events.jsx"></script>
<script type="text/jsx" src="/static/js/grv/session.jsx"></script>
<script type="text/javascript" src="{{Path "/static/js/grv/sessionlib.js"}}"></script>
<script type="text/javascript" src="{{Path "/static/js/grv/player.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/events.jsx"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/session.jsx"}}"></script>
{{ end }}
+3 -3
View File
@@ -9,7 +9,7 @@
{{ end }}
{{ define "script" }}
<script type="text/javascript" src="/static/js/term.js"></script>
<script type="text/javascript" src="/static/js/grv/sessionlib.js"></script>
<script type="text/jsx" src="/static/js/grv/sessions.jsx"></script>
<script type="text/javascript" src="{{Path "/static/js/term.js"}}"></script>
<script type="text/javascript" src="{{Path "/static/js/grv/sessionlib.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/sessions.jsx"}}"></script>
{{ end }}
+1 -1
View File
@@ -9,5 +9,5 @@
{{ end }}
{{ define "script" }}
<script type="text/jsx" src="/static/js/grv/webtuns.js"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/webtuns.jsx"}}"></script>
{{ end }}
+42
View File
@@ -617,3 +617,45 @@ func (c *chunkRW) WriteChunks(chs []recorder.Chunk) error {
func (c *chunkRW) Close() error {
return nil
}
// TOODO(klizhentas) this should be just including appropriate backends
type ClientI interface {
GetSessions() ([]session.Session, error)
GetSession(id string) (*session.Session, error)
DeleteSession(id string) error
UpsertSession(id string, ttl time.Duration) error
UpsertParty(id string, p session.Party, ttl time.Duration) error
UpsertRemoteCert(cert backend.RemoteCert, ttl time.Duration) error
GetRemoteCerts(ctype string, fqdn string) ([]backend.RemoteCert, error)
DeleteRemoteCert(ctype string, fqdn, id string) error
GenerateToken(fqdn string, ttl time.Duration) (string, error)
Log(id lunk.EventID, e lunk.Event)
LogEntry(en lunk.Entry) error
GetEvents(filter events.Filter) ([]lunk.Entry, error)
GetChunkWriter(id string) (recorder.ChunkWriteCloser, error)
GetChunkReader(id string) (recorder.ChunkReadCloser, error)
UpsertServer(s backend.Server, ttl time.Duration) error
GetServers() ([]backend.Server, error)
UpsertWebTun(wt backend.WebTun, ttl time.Duration) error
GetWebTuns() ([]backend.WebTun, error)
GetWebTun(prefix string) (*backend.WebTun, error)
DeleteWebTun(prefix string) error
UpsertPassword(user string, password []byte) error
CheckPassword(user string, password []byte) error
SignIn(user string, password []byte) (string, error)
GetWebSession(user string, sid string) (string, error)
GetWebSessionsKeys(user string) ([]backend.AuthorizedKey, error)
DeleteWebSession(user string, sid string) error
GetUsers() ([]string, error)
DeleteUser(user string) error
UpsertUserKey(username string, key backend.AuthorizedKey, ttl time.Duration) ([]byte, error)
GetUserKeys(user string) ([]backend.AuthorizedKey, error)
DeleteUserKey(username string, id string) error
GetHostCAPub() ([]byte, error)
GetUserCAPub() ([]byte, error)
GenerateKeyPair(pass string) ([]byte, []byte, error)
GenerateHostCert(key []byte, id, hostname string, ttl time.Duration) ([]byte, error)
GenerateUserCert(key []byte, id, user string, ttl time.Duration) ([]byte, error)
ResetHostCA() error
ResetUserCA() error
}
-1
View File
@@ -39,7 +39,6 @@ func Init(b backend.Backend, a Authority,
// check that user CA and host CA are present and set the certs if needed
asrv := NewAuthServer(b, a, scrt)
log.Infof("I AM HERE")
if _, e := asrv.GetHostCAPub(); e != nil {
log.Infof("Host CA error: %v", e)
if _, ok := e.(*backend.NotFoundError); ok {
+205
View File
@@ -0,0 +1,205 @@
package cp
import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gravitational/teleport/auth"
"github.com/gravitational/teleport/sshutils"
"github.com/gravitational/teleport/utils"
"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/Godeps/_workspace/src/golang.org/x/crypto/ssh"
"github.com/mailgun/ttlmap"
)
type Cookie struct {
User string
SID string
}
func EncodeCookie(user, sid string) (string, error) {
bytes, err := json.Marshal(Cookie{User: user, SID: sid})
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func DecodeCookie(b string) (*Cookie, error) {
bytes, err := hex.DecodeString(b)
if err != nil {
return nil, err
}
var c *Cookie
if err := json.Unmarshal(bytes, &c); err != nil {
return nil, err
}
return c, nil
}
type Context interface {
io.Closer
ConnectUpstream(addr string) (*sshutils.Upstream, error)
GetAuthMethods() ([]ssh.AuthMethod, error)
GetWebSID() string
GetUser() string
GetClient() auth.ClientI
}
type LocalContext struct {
sid string
user string
clt *auth.TunClient
}
func (c *LocalContext) GetClient() auth.ClientI {
return c.clt
}
func (c *LocalContext) GetUser() string {
return c.user
}
func (c *LocalContext) GetWebSID() string {
return c.sid
}
func (c *LocalContext) GetAuthMethods() ([]ssh.AuthMethod, error) {
a, err := c.clt.GetAgent()
if err != nil {
log.Errorf("failed to get agent: %v", err)
return nil, err
}
signers, err := a.Signers()
if err != nil {
log.Errorf("failed to get signers: %v", err)
return nil, err
}
return []ssh.AuthMethod{ssh.PublicKeys(signers...)}, nil
}
func (c *LocalContext) Close() error {
if c.clt != nil {
return c.clt.Close()
}
return nil
}
func (c *LocalContext) ConnectUpstream(addr string) (*sshutils.Upstream, error) {
agent, err := c.clt.GetAgent()
if err != nil {
return nil, fmt.Errorf("failed to get agent: %v", err)
}
signers, err := agent.Signers()
if err != nil {
return nil, fmt.Errorf("no signers: %v", err)
}
return sshutils.DialUpstream(c.user, addr, signers)
}
type RequestHandler func(http.ResponseWriter, *http.Request, httprouter.Params, Context)
type AuthHandler interface {
GetHost() string
Auth(user, pass string) (string, error)
ValidateSession(user, sid string) (Context, error)
SetSession(w http.ResponseWriter, user, sid string) error
ClearSession(w http.ResponseWriter)
}
func NewLocalAuth(host string, servers []utils.NetAddr) (*LocalAuth, error) {
m, err := ttlmap.NewMap(1024)
if err != nil {
return nil, err
}
return &LocalAuth{
host: host,
sessions: m,
authServers: servers,
}, nil
}
type LocalAuth struct {
sessions *ttlmap.TtlMap
authServers []utils.NetAddr
host string
}
func (s *LocalAuth) GetHost() string {
return s.host
}
func (s *LocalAuth) Auth(user, pass string) (string, error) {
method, err := auth.NewWebPasswordAuth(user, []byte(pass))
if err != nil {
return "", err
}
clt, err := auth.NewTunClient(s.authServers[0], user, method)
if err != nil {
return "", err
}
return clt.SignIn(user, []byte(pass))
}
func (s *LocalAuth) ValidateSession(user, sid string) (Context, error) {
val, ok := s.sessions.Get(user + sid)
if ok {
log.Infof("retrieving session from cache")
return val.(Context), nil
}
method, err := auth.NewWebSessionAuth(user, []byte(sid))
if err != nil {
return nil, err
}
clt, err := auth.NewTunClient(s.authServers[0], user, method)
if err != nil {
log.Infof("failed to connect: %v", clt, err)
return nil, err
}
if _, err := clt.GetWebSession(user, sid); err != nil {
log.Infof("session not found: %v", err)
return nil, err
}
log.Infof("session validated")
c := &LocalContext{
clt: clt,
user: user,
sid: sid,
}
if err := s.sessions.Set(user+sid, c, 600); err != nil {
log.Infof("something is wrong: %v", err)
return nil, err
}
return c, nil
}
func (s *LocalAuth) SetSession(w http.ResponseWriter, user, sid string) error {
d, err := EncodeCookie(user, sid)
if err != nil {
return err
}
c := &http.Cookie{
Domain: fmt.Sprintf(".%v", s.host),
Name: "session",
Value: d,
Path: "/",
}
http.SetCookie(w, c)
return nil
}
func (s *LocalAuth) ClearSession(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Domain: fmt.Sprintf(".%v", s.host),
Name: "session",
Value: "",
Path: "/",
})
}
+111 -284
View File
@@ -1,8 +1,6 @@
package cp
import (
"archive/tar"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -15,10 +13,9 @@ import (
"time"
"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"
@@ -27,30 +24,22 @@ import (
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
"github.com/mailgun/ttlmap"
)
// cpHandler implements methods for control panel
type cpHandler struct {
// CPHandler implements methods for control panel
type CPHandler struct {
httprouter.Router
host string
authServers []utils.NetAddr
sessions *ttlmap.TtlMap
auth AuthHandler
prefix string
}
func newCPHandler(host string, auth []utils.NetAddr, assetsDir string) *cpHandler {
initTemplates(assetsDir)
m, err := ttlmap.NewMap(1024)
if err != nil {
panic(err)
func NewCPHandler(auth AuthHandler, assetsDir, prefix string) *CPHandler {
h := &CPHandler{
auth: auth,
prefix: prefix,
}
h := &cpHandler{
authServers: auth,
host: host,
sessions: m,
}
h.initTemplates(assetsDir)
h.GET("/login", h.login)
h.GET("/logout", h.logout)
h.POST("/auth", h.authForm)
@@ -106,12 +95,37 @@ func newCPHandler(host string, auth []utils.NetAddr, assetsDir string) *cpHandle
return h
}
func (s *cpHandler) ls(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) needsAuth(fn RequestHandler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
Cookie, err := r.Cookie("session")
if err != nil {
log.Infof("error getting Cookie: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
d, err := DecodeCookie(Cookie.Value)
if err != nil {
log.Warningf("failed to decode Cookie '%v', err: %v", Cookie.Value, err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
ctx, err := s.auth.ValidateSession(d.User, d.SID)
if err != nil {
log.Warningf("failed to validate session: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
fn(w, r, p, ctx)
}
return nil
}
func (s *CPHandler) ls(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
root := r.URL.Query().Get("node")
addr := p[0].Value
up, err := c.connectUpstream(addr)
up, err := c.ConnectUpstream(addr)
if err != nil {
log.Errorf("file err: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -166,7 +180,7 @@ func (s *cpHandler) ls(w http.ResponseWriter, r *http.Request, p httprouter.Para
roundtrip.ReplyJSON(w, http.StatusOK, jsnodes)
}
func (s *cpHandler) downloadFiles(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) downloadFiles(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
addr := p[0].Value
@@ -196,7 +210,7 @@ func (s *cpHandler) downloadFiles(w http.ResponseWriter, r *http.Request, p http
return
}
up, err := c.connectUpstream(addr)
up, err := c.ConnectUpstream(addr)
if err != nil {
log.Errorf("file err: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -224,17 +238,17 @@ func (s *cpHandler) downloadFiles(w http.ResponseWriter, r *http.Request, p http
}
ck := &http.Cookie{
Domain: fmt.Sprintf(".%v", s.host),
Domain: fmt.Sprintf(".%v", s.auth.GetHost()),
Name: "fileDownload",
Value: "true",
Path: "/",
}
http.SetCookie(w, ck)
w.Header().Set("Content-Disposition", "attachment; filename=download.tar")
writeArchive(dir, w)
utils.WriteArchive(dir, w)
}
func (s *cpHandler) uploadFile(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
func (s *CPHandler) uploadFile(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
file, fh, err := r.FormFile("file")
if err != nil {
log.Errorf("file err: %v", err)
@@ -245,7 +259,7 @@ func (s *cpHandler) uploadFile(w http.ResponseWriter, r *http.Request, _ httprou
path, addr := r.Form.Get("path"), r.Form.Get("addr")
up, err := c.connectUpstream(addr)
up, err := c.ConnectUpstream(addr)
if err != nil {
log.Errorf("file err: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -309,8 +323,8 @@ func (s *cpHandler) uploadFile(w http.ResponseWriter, r *http.Request, _ httprou
roundtrip.ReplyJSON(w, http.StatusOK, res)
}
func (s *cpHandler) getSessions(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
ses, err := c.clt.GetSessions()
func (s *CPHandler) getSessions(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
ses, err := c.GetClient().GetSessions()
if err != nil {
log.Errorf("failed to retrieve sessions: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -319,7 +333,7 @@ func (s *cpHandler) getSessions(w http.ResponseWriter, r *http.Request, _ httpro
roundtrip.ReplyJSON(w, http.StatusOK, ses)
}
func (s *cpHandler) getChunks(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) getChunks(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
st, en := r.URL.Query().Get("start"), r.URL.Query().Get("end")
start, err := strconv.Atoi(st)
if err != nil {
@@ -337,7 +351,7 @@ func (s *cpHandler) getChunks(w http.ResponseWriter, r *http.Request, p httprout
}
recordID := p[0].Value
rdr, err := c.clt.GetChunkReader(recordID)
rdr, err := c.GetClient().GetChunkReader(recordID)
if err != nil {
log.Errorf("failed to retrieve reader: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -354,7 +368,7 @@ func (s *cpHandler) getChunks(w http.ResponseWriter, r *http.Request, p httprout
roundtrip.ReplyJSON(w, http.StatusOK, chunks)
}
func (s *cpHandler) sendMessage(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) sendMessage(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
sid := p[0].Value
b, err := ioutil.ReadAll(r.Body)
@@ -370,29 +384,29 @@ func (s *cpHandler) sendMessage(w http.ResponseWriter, r *http.Request, p httpro
return
}
c.clt.Log(lunk.NewRootEventID(), &events.Message{
c.GetClient().Log(lunk.NewRootEventID(), &events.Message{
SessionID: sid,
User: c.user,
User: c.GetUser(),
Message: message,
})
roundtrip.ReplyJSON(w, http.StatusOK, "ok")
}
func (s *cpHandler) getSession(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
ses, err := c.clt.GetSession(p[0].Value)
func (s *CPHandler) getSession(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
ses, err := c.GetClient().GetSession(p[0].Value)
if err != nil {
if !backend.IsNotFound(err) {
log.Errorf("failed to retrieve session: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
}
if err = c.clt.UpsertSession(p[0].Value, 60*time.Second); err != nil {
if err = c.GetClient().UpsertSession(p[0].Value, 60*time.Second); err != nil {
log.Errorf("failed to upsert session: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
}
if ses, err = c.clt.GetSession(p[0].Value); err != nil {
if ses, err = c.GetClient().GetSession(p[0].Value); err != nil {
log.Errorf("failed to upsert session: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
@@ -404,13 +418,13 @@ func (s *cpHandler) getSession(w http.ResponseWriter, r *http.Request, p httprou
Limit: 20,
Start: time.Now(),
}
events, err := c.clt.GetEvents(f)
events, err := c.GetClient().GetEvents(f)
if err != nil {
log.Errorf("failed to retrieve servers: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
}
srvs, err := c.clt.GetServers()
srvs, err := c.GetClient().GetServers()
if err != nil {
log.Errorf("failed to retrieve servers: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -424,8 +438,8 @@ func (s *cpHandler) getSession(w http.ResponseWriter, r *http.Request, p httprou
})
}
func (s *cpHandler) getServers(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
servers, err := c.clt.GetServers()
func (s *CPHandler) getServers(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
servers, err := c.GetClient().GetServers()
if err != nil {
log.Errorf("failed to retrieve servers: %v")
replyErr(w, http.StatusInternalServerError, err)
@@ -434,20 +448,19 @@ func (s *cpHandler) getServers(w http.ResponseWriter, r *http.Request, _ httprou
roundtrip.ReplyJSON(w, http.StatusOK, servers)
}
func (s *cpHandler) connect(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) connect(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
log.Infof("connect request authorized to: %v", p[0].Value)
ws := wsHandler{
authServers: s.authServers,
ctx: c,
addr: p[0].Value,
sid: p[1].Value,
ctx: c,
addr: p[0].Value,
sid: p[1].Value,
}
defer ws.Close()
ws.Handler().ServeHTTP(w, r)
}
func (s *cpHandler) getWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
tun, err := c.clt.GetWebTun(p[0].Value)
func (s *CPHandler) getWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
tun, err := c.GetClient().GetWebTun(p[0].Value)
if err != nil {
replyErr(w, http.StatusInternalServerError, err)
return
@@ -455,25 +468,25 @@ func (s *cpHandler) getWebTun(w http.ResponseWriter, r *http.Request, p httprout
roundtrip.ReplyJSON(w, http.StatusOK, tun)
}
func (s *cpHandler) deleteWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
if err := c.clt.DeleteWebTun(p[0].Value); err != nil {
func (s *CPHandler) deleteWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
if err := c.GetClient().DeleteWebTun(p[0].Value); err != nil {
replyErr(w, http.StatusInternalServerError, err)
return
}
roundtrip.ReplyJSON(w, http.StatusOK, "deleted")
}
func (s *cpHandler) getWebTuns(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
tuns, err := c.clt.GetWebTuns()
func (s *CPHandler) getWebTuns(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
tuns, err := c.GetClient().GetWebTuns()
if err != nil {
log.Errorf("failed to retrieve tunnels: %v")
log.Errorf("failed to retrieve tunnels: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
}
roundtrip.ReplyJSON(w, http.StatusOK, tuns)
}
func (s *cpHandler) upsertWebTun(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
func (s *CPHandler) upsertWebTun(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
var prefix, target, proxy string
err := form.Parse(r,
@@ -491,7 +504,7 @@ func (s *cpHandler) upsertWebTun(w http.ResponseWriter, r *http.Request, _ httpr
roundtrip.ReplyJSON(w, http.StatusBadRequest, message(err.Error()))
return
}
if err := c.clt.UpsertWebTun(*wt, 0); err != nil {
if err := c.GetClient().UpsertWebTun(*wt, 0); err != nil {
log.Errorf("failed to upsert keys: %v", err)
roundtrip.ReplyJSON(w, http.StatusBadRequest, err.Error())
return
@@ -499,14 +512,14 @@ func (s *cpHandler) upsertWebTun(w http.ResponseWriter, r *http.Request, _ httpr
roundtrip.ReplyJSON(w, http.StatusOK, wt)
}
func (s *cpHandler) getEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
func (s *CPHandler) getEvents(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
f, err := events.FilterFromURL(r.URL.Query())
if err != nil {
log.Errorf("failed to retrieve events: %v", err)
replyErr(w, http.StatusInternalServerError, err)
return
}
events, err := c.clt.GetEvents(*f)
events, err := c.GetClient().GetEvents(*f)
if err != nil {
log.Errorf("failed to retrieve events: %v", err)
replyErr(w, http.StatusInternalServerError, err)
@@ -516,23 +529,23 @@ func (s *cpHandler) getEvents(w http.ResponseWriter, r *http.Request, _ httprout
roundtrip.ReplyJSON(w, http.StatusOK, events)
}
func (s *cpHandler) keysIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) {
executeTemplate(w, "keys", nil)
func (s *CPHandler) keysIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ Context) {
s.executeTemplate(w, "keys", nil)
}
func (s *cpHandler) eventsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) {
executeTemplate(w, "events", nil)
func (s *CPHandler) eventsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ Context) {
s.executeTemplate(w, "events", nil)
}
func (s *cpHandler) webTunsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) {
executeTemplate(w, "webtuns", nil)
func (s *CPHandler) webTunsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ Context) {
s.executeTemplate(w, "webtuns", nil)
}
func (s *cpHandler) serversIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) {
executeTemplate(w, "servers", nil)
func (s *CPHandler) serversIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ Context) {
s.executeTemplate(w, "servers", nil)
}
func (s *cpHandler) newSession(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
func (s *CPHandler) newSession(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
var server string
err := form.Parse(r, form.String("server", &server))
if err != nil {
@@ -541,30 +554,31 @@ func (s *cpHandler) newSession(w http.ResponseWriter, r *http.Request, _ httprou
return
}
sid := uuid.New()
if err := c.clt.UpsertSession(sid, 30*time.Second); err != nil {
if err := c.GetClient().UpsertSession(sid, 30*time.Second); err != nil {
replyErr(w, http.StatusInternalServerError, err)
return
}
u := url.URL{
Path: fmt.Sprintf("/sessions/%v", sid),
Path: s.Path("sessions", sid),
}
u.Query().Set("server", server)
http.Redirect(w, r, u.String(), http.StatusFound)
}
func (s *cpHandler) sessionsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ *ctx) {
executeTemplate(w, "sessions", nil)
func (s *CPHandler) sessionsIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, _ Context) {
s.executeTemplate(w, "sessions", nil)
}
func (s *cpHandler) sessionIndex(w http.ResponseWriter, r *http.Request, p httprouter.Params, _ *ctx) {
executeTemplate(w, "session", map[string]interface{}{
func (s *CPHandler) sessionIndex(w http.ResponseWriter, r *http.Request, p httprouter.Params, _ Context) {
s.executeTemplate(w, "session", map[string]interface{}{
"SessionID": p[0].Value,
"ServerAddr": r.URL.Query().Get("server"),
})
}
func (s *cpHandler) getKeys(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
keys, err := c.clt.GetUserKeys(c.user)
func (s *CPHandler) getKeys(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
keys, err := c.GetClient().GetUserKeys(c.GetUser())
log.Infof("Keys: %v", keys)
if err != nil {
log.Errorf("failed to retrieve keys: %v")
replyErr(w, http.StatusInternalServerError, err)
@@ -573,7 +587,7 @@ func (s *cpHandler) getKeys(w http.ResponseWriter, r *http.Request, _ httprouter
roundtrip.ReplyJSON(w, http.StatusOK, keys)
}
func (s *cpHandler) postKey(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c *ctx) {
func (s *CPHandler) postKey(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) {
var key, id string
err := form.Parse(r,
@@ -584,7 +598,7 @@ func (s *cpHandler) postKey(w http.ResponseWriter, r *http.Request, _ httprouter
roundtrip.ReplyJSON(w, http.StatusBadRequest, message(err.Error()))
return
}
cert, err := c.clt.UpsertUserKey(c.user, backend.AuthorizedKey{ID: id, Value: []byte(key)}, 0)
cert, err := c.GetClient().UpsertUserKey(c.GetUser(), backend.AuthorizedKey{ID: id, Value: []byte(key)}, 0)
if err != nil {
log.Errorf("failed to upsert keys: %v", err)
roundtrip.ReplyJSON(w, http.StatusBadRequest, message("invalid key format"))
@@ -593,10 +607,10 @@ func (s *cpHandler) postKey(w http.ResponseWriter, r *http.Request, _ httprouter
roundtrip.ReplyJSON(w, http.StatusOK, backend.AuthorizedKey{ID: key, Value: cert})
}
func (s *cpHandler) deleteKey(w http.ResponseWriter, r *http.Request, p httprouter.Params, c *ctx) {
func (s *CPHandler) deleteKey(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) {
key := p[0].Value
err := c.clt.DeleteUserKey(c.user, key)
err := c.GetClient().DeleteUserKey(c.GetUser(), key)
if err != nil {
log.Errorf("failed to upsert keys: %v", err)
roundtrip.ReplyJSON(w, http.StatusBadRequest, message("invalid key format"))
@@ -605,20 +619,16 @@ func (s *cpHandler) deleteKey(w http.ResponseWriter, r *http.Request, p httprout
roundtrip.ReplyJSON(w, http.StatusOK, message("key deleted"))
}
func (s *cpHandler) login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
executeTemplate(w, "login", nil)
func (s *CPHandler) login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
s.executeTemplate(w, "login", nil)
}
func (s *cpHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := s.clearSession(w); err != nil {
log.Errorf("failed to clear session: %v", err)
replyErr(w, http.StatusInternalServerError, fmt.Errorf("failed to logout"))
return
}
http.Redirect(w, r, "/login", http.StatusFound)
func (s *CPHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
s.auth.ClearSession(w)
http.Redirect(w, r, s.Path("login"), http.StatusFound)
}
func (s *cpHandler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
func (s *CPHandler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var user, pass string
err := form.Parse(r,
@@ -629,150 +639,24 @@ func (s *cpHandler) authForm(w http.ResponseWriter, r *http.Request, p httproute
replyErr(w, http.StatusBadRequest, err)
return
}
sid, err := s.auth(user, pass)
sid, err := s.auth.Auth(user, pass)
if err != nil {
log.Warningf("auth error: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
if err := s.setSession(w, user, sid); err != nil {
if err := s.auth.SetSession(w, user, sid); err != nil {
replyErr(w, http.StatusInternalServerError, err)
return
}
http.Redirect(w, r, "/", http.StatusFound)
}
func (s *cpHandler) auth(user, pass string) (string, error) {
method, err := auth.NewWebPasswordAuth(user, []byte(pass))
if err != nil {
return "", err
func (s *CPHandler) executeTemplate(w http.ResponseWriter, name string, data map[string]interface{}) {
if data == nil {
data = map[string]interface{}{}
}
clt, err := auth.NewTunClient(s.authServers[0], user, method)
if err != nil {
return "", err
}
return clt.SignIn(user, []byte(pass))
}
func (s *cpHandler) validateSession(user, sid string) (*ctx, error) {
val, ok := s.sessions.Get(user + sid)
if ok {
log.Infof("retrieving session from cache")
return val.(*ctx), nil
}
method, err := auth.NewWebSessionAuth(user, []byte(sid))
if err != nil {
return nil, err
}
clt, err := auth.NewTunClient(s.authServers[0], user, method)
if err != nil {
log.Infof("failed to connect: %v", clt, err)
return nil, err
}
if _, err := clt.GetWebSession(user, sid); err != nil {
log.Infof("session not found: %v", err)
return nil, err
}
log.Infof("session validated")
c := &ctx{
clt: clt,
user: user,
sid: sid,
}
if err := s.sessions.Set(user+sid, c, 600); err != nil {
log.Infof("something is wrong: %v", err)
return nil, err
}
return c, nil
}
func (s *cpHandler) setSession(w http.ResponseWriter, user, sid string) error {
d, err := encodeCookie(user, sid)
if err != nil {
return err
}
c := &http.Cookie{
Domain: fmt.Sprintf(".%v", s.host),
Name: "session",
Value: d,
Path: "/",
}
http.SetCookie(w, c)
return nil
}
func (s *cpHandler) clearSession(w http.ResponseWriter) error {
http.SetCookie(w, &http.Cookie{
Domain: fmt.Sprintf(".%v", s.host),
Name: "session",
Value: "",
Path: "/",
})
return nil
}
func (s *cpHandler) needsAuth(fn authHandle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
cookie, err := r.Cookie("session")
if err != nil {
log.Infof("getting cookie: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
d, err := decodeCookie(cookie.Value)
if err != nil {
log.Warningf("failed to decode cookie '%v', err: %v", cookie.Value, err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
ctx, err := s.validateSession(d.User, d.SID)
if err != nil {
log.Warningf("failed to validate session: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
fn(w, r, p, ctx)
}
return nil
}
func replyErr(w http.ResponseWriter, code int, err error) {
roundtrip.ReplyJSON(w, code, message(err.Error()))
}
func message(msg string) map[string]interface{} {
return map[string]interface{}{"message": msg}
}
type cookie struct {
User string
SID string
}
func encodeCookie(user, sid string) (string, error) {
bytes, err := json.Marshal(cookie{User: user, SID: sid})
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func decodeCookie(b string) (*cookie, error) {
bytes, err := hex.DecodeString(b)
if err != nil {
return nil, err
}
var c *cookie
if err := json.Unmarshal(bytes, &c); err != nil {
return nil, err
}
return c, nil
}
func executeTemplate(w http.ResponseWriter, name string, data interface{}) {
data["Prefix"] = s.prefix
tpl, ok := templates[name]
if !ok {
replyErr(w, http.StatusInternalServerError, fmt.Errorf("template '%v' not found", name))
@@ -783,76 +667,19 @@ func executeTemplate(w http.ResponseWriter, name string, data interface{}) {
log.Errorf("Execute template: %v", err)
replyErr(w, http.StatusInternalServerError, fmt.Errorf("internal render error"))
}
}
type ctx struct {
sid string
user string
clt *auth.TunClient
func replyErr(w http.ResponseWriter, code int, err error) {
roundtrip.ReplyJSON(w, code, message(err.Error()))
}
func (c *ctx) Close() error {
if c.clt != nil {
return c.clt.Close()
}
return nil
func message(msg string) map[string]interface{} {
return map[string]interface{}{"message": msg}
}
func (c *ctx) connectUpstream(addr string) (*sshutils.Upstream, error) {
agent, err := c.clt.GetAgent()
if err != nil {
return nil, fmt.Errorf("failed to get agent: %v", err)
}
signers, err := agent.Signers()
if err != nil {
return nil, fmt.Errorf("no signers: %v", err)
}
return sshutils.DialUpstream(c.user, addr, signers)
}
type authHandle func(http.ResponseWriter, *http.Request, httprouter.Params, *ctx)
type jsNode struct {
ID string `json:"id"`
Text string `json:"text"`
Children bool `json:"children"`
Icon string `json:"icon"`
}
func writeArchive(root_directory string, w io.Writer) error {
ar := tar.NewWriter(w)
walkFn := func(path string, info os.FileInfo, err error) error {
if info.Mode().IsDir() {
return nil
}
// Because of scoping we can reference the external root_directory variable
new_path := path[len(root_directory):]
if len(new_path) == 0 {
return nil
}
fr, err := os.Open(path)
if err != nil {
return err
}
defer fr.Close()
if h, err := tar.FileInfoHeader(info, new_path); err != nil {
return err
} else {
h.Name = new_path
if err = ar.WriteHeader(h); err != nil {
return err
}
}
if length, err := io.Copy(ar, fr); err != nil {
return err
} else {
fmt.Println(length)
}
return nil
}
return filepath.Walk(root_directory, walkFn)
}
+1 -1
View File
@@ -137,7 +137,7 @@ func authClient(authSrv []utils.NetAddr, r *http.Request) (string, *auth.TunClie
if err != nil {
return "", nil, fmt.Errorf("no session cookie: %v", err)
}
d, err := decodeCookie(cookie.Value)
d, err := DecodeCookie(cookie.Value)
if err != nil {
return "", nil, fmt.Errorf("failed to decode session cookie: %v", err)
}
+5 -1
View File
@@ -26,7 +26,11 @@ func NewServer(cfg Config) (*CPServer, error) {
if cfg.Host == "" {
return nil, fmt.Errorf("need an base host")
}
cp := newCPHandler(cfg.Host, cfg.AuthSrv, cfg.AssetsDir)
auth, err := NewLocalAuth(cfg.Host, cfg.AuthSrv)
if err != nil {
return nil, err
}
cp := NewCPHandler(auth, cfg.AssetsDir, "")
proxy := newProxyHandler(cp, cfg.AuthSrv, cfg.Host)
return &CPServer{
cfg: cfg,
+13 -2
View File
@@ -2,11 +2,17 @@ package cp
import (
"html/template"
"path/filepath"
)
var templates map[string]*template.Template
func initTemplates(baseDir string) {
func (s *CPHandler) Path(params ...string) string {
out := append([]string{"/", s.prefix}, params...)
return filepath.Join(out...)
}
func (s *CPHandler) initTemplates(baseDir string) {
tpls := []tpl{
tpl{name: "login", include: []string{"assets/static/tpl/login.tpl", "assets/static/tpl/base.tpl"}},
tpl{name: "keys", include: []string{"assets/static/tpl/keys.tpl", "assets/static/tpl/base.tpl"}},
@@ -19,7 +25,12 @@ func initTemplates(baseDir string) {
templates = make(map[string]*template.Template)
for _, t := range tpls {
tpl := template.New(t.name)
template.Must(tpl.ParseFiles(t.include...))
tpl.Funcs(map[string]interface{}{
"Path": s.Path,
})
for _, i := range t.include {
template.Must(tpl.ParseFiles(filepath.Join(baseDir, i)))
}
templates[t.name] = tpl
}
}
+12 -18
View File
@@ -1,12 +1,9 @@
package cp
import (
"fmt"
"net/http"
"github.com/gravitational/teleport/sshutils"
"github.com/gravitational/teleport/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
@@ -15,11 +12,10 @@ import (
// wsHandler
type wsHandler struct {
authServers []utils.NetAddr
ctx *ctx
addr string
up *sshutils.Upstream
sid string
ctx Context
addr string
up *sshutils.Upstream
sid string
}
func (w *wsHandler) Close() error {
@@ -41,15 +37,7 @@ func (w *wsHandler) connect(ws *websocket.Conn) {
}
func (w *wsHandler) connectUpstream() (*sshutils.Upstream, error) {
agent, err := w.ctx.clt.GetAgent()
if err != nil {
return nil, fmt.Errorf("failed to get agent: %v", err)
}
signers, err := agent.Signers()
if err != nil {
return nil, fmt.Errorf("no signers: %v", err)
}
up, err := sshutils.DialUpstream(w.ctx.user, w.addr, signers)
up, err := w.ctx.ConnectUpstream(w.addr)
if err != nil {
return nil, err
}
@@ -69,7 +57,13 @@ func (w *wsHandler) connectUpstream() (*sshutils.Upstream, error) {
}
func (w *wsHandler) Handler() http.Handler {
return websocket.Handler(w.connect)
// TODO(klizhentas)
// we instantiate a server explicitly here instead of using
// websocket.HandlerFunc to set empty origin checker
// make sure we check origin when in prod mode
return &websocket.Server{
Handler: w.connect,
}
}
func newWSHandler(host string, auth []string) *wsHandler {
+5 -2
View File
@@ -24,9 +24,8 @@ type RemoteSite interface {
ConnectToServer(addr, user string, auth []ssh.AuthMethod) (*ssh.Client, error)
GetLastConnected() time.Time
GetName() string
GetServers() ([]backend.Server, error)
GetStatus() string
GetEvents(events.Filter) ([]lunk.Entry, error)
GetClient() *auth.Client
}
type Server interface {
@@ -174,6 +173,10 @@ type remoteSite struct {
clt *auth.Client
}
func (s *remoteSite) GetClient() *auth.Client {
return s.clt
}
func (s *remoteSite) GetEvents(filter events.Filter) ([]lunk.Entry, error) {
return s.clt.GetEvents(filter)
}
+40
View File
@@ -1,8 +1,11 @@
package utils
import (
"archive/tar"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@@ -21,3 +24,40 @@ func ReadPath(path string) ([]byte, error) {
}
return bytes, nil
}
func WriteArchive(root_directory string, w io.Writer) error {
ar := tar.NewWriter(w)
walkFn := func(path string, info os.FileInfo, err error) error {
if info.Mode().IsDir() {
return nil
}
// Because of scoping we can reference the external root_directory variable
new_path := path[len(root_directory):]
if len(new_path) == 0 {
return nil
}
fr, err := os.Open(path)
if err != nil {
return err
}
defer fr.Close()
if h, err := tar.FileInfoHeader(info, new_path); err != nil {
return err
} else {
h.Name = new_path
if err = ar.WriteHeader(h); err != nil {
return err
}
}
if length, err := io.Copy(ar, fr); err != nil {
return err
} else {
fmt.Println(length)
}
return nil
}
return filepath.Walk(root_directory, walkFn)
}