mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Cleanup and make sure teleport CP is reusable
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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>);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
);
|
||||
|
||||
@@ -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} </a>
|
||||
<a href={grv.path("sessions", se.id)}>{u} </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
|
||||
);
|
||||
|
||||
@@ -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> 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> 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
|
||||
);
|
||||
@@ -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> 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> 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
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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: "/",
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user