Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

luci-app-cloudflared: configure Cloudlfare Zero Trust Tunnel #6876

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <hilman0.0maulana@gmail.com>

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 <hilman0.0maulana@gmail.com>, Sergey Ponomarev <stokito@gmail.com>
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 <hilman0.0maulana@gmail.com>
*/

'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 <hilman0.0maulana@gmail.com>
*/

'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