Skip to content

Commit

Permalink
luci-app-cloudflared: configure Cloudflare Zero Trust Tunnel
Browse files Browse the repository at this point in the history
Configure a tunnel and see logs of the daemon.

Signed-off-by: Hilman Maulana <[email protected]>
Signed-off-by: Sergey Ponomarev <[email protected]>
  • Loading branch information
animegasan authored and systemcrash committed Feb 1, 2024
1 parent f63e823 commit faf6c4f
Show file tree
Hide file tree
Showing 40 changed files with 6,139 additions and 0 deletions.
17 changes: 17 additions & 0 deletions applications/luci-app-cloudflared/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This is free software, licensed under the Apache License, Version 2.0
#
# Copyright (C) 2024 Hilman Maulana <[email protected]>

include $(TOPDIR)/rules.mk

LUCI_TITLE:=LuCI for Cloudflared
LUCI_DEPENDS:=+cloudflared
LUCI_DESCRIPTION:=LuCI support for Cloudflare Zero Trust Tunnels

PKG_MAINTAINER:=Hilman Maulana <[email protected]>, Sergey Ponomarev <[email protected]>
PKG_VERSION:=1.0
PKG_LICENSE:=Apache-2.0

include ../../luci.mk

# call BuildPackage - OpenWrt buildroot signature
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* This is free software, licensed under the Apache License, Version 2.0
*
* Copyright (C) 2024 Hilman Maulana <[email protected]>
*/

'use strict';
'require form';
'require poll';
'require rpc';
'require uci';
'require view';

var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});

function getServiceStatus() {
return L.resolveDefault(callServiceList('cloudflared'), {}).then(function (res) {
var isRunning = false;
try {
isRunning = res['cloudflared']['instances']['cloudflared']['running'];
} catch (ignored) {}
return isRunning;
});
}

function renderStatus(isRunning) {
var spanTemp = '<label class="cbi-value-title">Status</label><div class="cbi-value-field"><em><span style="color:%s">%s</span></em></div>';
var renderHTML;
if (isRunning) {
renderHTML = String.format(spanTemp, 'green', _('Running'));
} else {
renderHTML = String.format(spanTemp, 'red', _('Not Running'));
}

return renderHTML;
}

return view.extend({
load: function () {
return Promise.all([
uci.load('cloudflared')
]);
},

render: function (data) {
var m, s, o;

m = new form.Map('cloudflared', _('Cloudflare Zero Trust Tunnel'),
_('Cloudflare Zero Trust Security services help you get maximum security both from outside and within the network.') + '<br />' +
_('Create and manage your network on the <a %s>Cloudflare Zero Trust</a> dashboard.')
.format('href="https://one.dash.cloudflare.com" target="_blank"') + '<br />' +
_('See <a %s>documentation</a>.')
.format('href="https://openwrt.org/docs/guide-user/services/vpn/cloudfare_tunnel" target="_blank"')
);

s = m.section(form.NamedSection, 'config', 'cloudflared');

o = s.option(form.DummyValue, 'service_status', _('Status'));
o.load = function () {
poll.add(function () {
return L.resolveDefault(getServiceStatus()).then(function (res) {
var view = document.getElementById('cbi-cloudflared-config-service_status');
if (view) {
view.innerHTML = renderStatus(res);
}
});
});
};
o.value = _('Collecting data...');

o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;

o = s.option(form.TextValue, 'token', _('Token'),
_('The tunnel token is shown in the dashboard once you create a tunnel.')
);
o.optional = true;
o.rmempty = false;
o.monospace = true;

o = s.option(form.FileUpload, 'config', _('Config file path'),
_('See <a %s>documentation</a>.')
.format('href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/local-management/configuration-file/" target="_blank"')
);
o.default = '/etc/cloudflared/config.yml';
o.root_directory = '/etc/cloudflared/';
o.optional = true;

o = s.option(form.FileUpload, 'origincert', _('Certificate of Origin'),
_('The account certificate for your zones authorizing the client to serve as an Origin for that zone') + '<br />' +
_('Obtain a certificate <a %s>here</a>.')
.format('href="https://dash.cloudflare.com/argotunnel" target="_blank"')
);
o.default = '/etc/cloudflared/cert.pem';
o.root_directory = '/etc/cloudflared/';
o.optional = true;

o = s.option(form.ListValue, 'region', _('Region'),
_('The region to which connections are established.')
);
o.value('us', _('United States'));
o.optional = true;

o = s.option(form.ListValue, 'loglevel', _('Debug level'));
o.value('fatal', _('Fatal'));
o.value('error', _('Error'));
o.value('warn', _('Warning'));
o.value('info', _('Info'));
o.value('debug', _('Debug'));
o.default = 'info';

return m.render();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* This is free software, licensed under the Apache License, Version 2.0
*
* Copyright (C) 2024 Hilman Maulana <[email protected]>
*/

'use strict';
'require fs';
'require ui';
'require view';
'require poll';

function formatLogEntry(logObj) {
var formattedTime = new Date(logObj.time).toISOString().replace('T', ' ').split('.')[0];
var tunnelIDMessage = logObj.tunnelID ? ', ID: ' + logObj.tunnelID : '';
var errorMessage = logObj.error ? ', Error: ' + logObj.error : '';
var ipMessage = logObj.ip ? ', IP: ' + logObj.ip : '';
var configMessage = logObj.config ? ', Config: ' + JSON.stringify(logObj.config) : '';
var connectionMessage = logObj.connection ? ', Connection: ' + JSON.stringify(logObj.connection) : '';
var locationMessage = logObj.location ? ', Location: ' + logObj.location : '';
var protocolMessage = logObj.protocol ? ', Protocol: ' + logObj.protocol : '';

return '[' + formattedTime + '] [' + logObj.level + '] : ' + logObj.message + ipMessage + tunnelIDMessage + errorMessage + configMessage + connectionMessage + locationMessage + protocolMessage;
}

return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
poll.add(function () {
return fs.read('/var/log/cloudflared.log').then(function(res) {
if (!res || res.trim() === '') {
ui.addNotification(null, E('p', {}, _('Unable to read the interface info from /var/log/cloudflared.log.')));
return '';
}

var logs = res.trim().split('\n').map(function(entry) {
try {
var logObj = JSON.parse(entry);
return logObj.time && logObj.message && logObj.level
? formatLogEntry(logObj)
: '';
} catch (error) {
console.error('Error parsing log entry:', error);
return '';
}
});

logs = logs.filter(function(entry) {
return entry.trim() !== '';
});

var info = logs.join('\n');
var view = document.getElementById('syslog');
var filterLevel = document.getElementById('filter-level').value;
var logDirection = document.getElementById('log-direction').value;

if (view) {
var filteredLogs;
if (filterLevel !== 'all') {
filteredLogs = logs.filter(function(entry) {
var logLevel = entry.match(/\[.*\] \[(.*)\]/)[1].toLowerCase();
return logLevel.includes(filterLevel.toLowerCase());
});
} else {
filteredLogs = logs;
}

if (logDirection === 'up') {
filteredLogs = filteredLogs.reverse();
}

info = filteredLogs.join('\n');
view.innerHTML = info;
}

return info;
});
});

return Promise.resolve('');
},
render: function(info) {
return E([], [
E('h2', { 'class': 'section-title' }, _('Log')),
E('div', { 'id': 'logs' }, [
E('label', { 'for': 'filter-level', 'style': 'margin-right: 8px;' }, _('Filter Level:')),
E('select', { 'id': 'filter-level', 'style': 'margin-right: 8px;' }, [
E('option', { 'value': 'all', 'selected': 'selected' }, _('All')),
E('option', { 'value': 'info' }, _('Info')),
E('option', { 'value': 'warn' }, _('Warn')),
E('option', { 'value': 'error' }, _('Error')),
]),
E('label', { 'for': 'log-direction', 'style': 'margin-right: 8px;' }, _('Log Direction:')),
E('select', { 'id': 'log-direction', 'style': 'margin-right: 8px;' }, [
E('option', { 'value': 'down', 'selected': 'selected' }, _('Down')),
E('option', { 'value': 'up' }, _('Up')),
]),
E('button', {
'id': 'download-log',
'class': 'cbi-button cbi-button-save',
'click': L.bind(this.handleDownloadLog, this),
'style': 'margin-bottom: 8px;'
}, _('Download Log')),
E('textarea', {
'id': 'syslog',
'class': 'cbi-input-textarea',
'style': 'height: 500px; overflow-y: scroll;',
'readonly': 'readonly',
'wrap': 'off',
'rows': 1
}, [ info ])
])
]);
},

handleDownloadLog: function() {
var logs = document.getElementById('syslog').value;
var blob = new Blob([logs], { type: 'text/plain' });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'cloudflared.log';
link.click();
}
});
Loading

0 comments on commit faf6c4f

Please sign in to comment.