Skip to content
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
7 changes: 7 additions & 0 deletions core/imageroot/etc/systemd/system/support.service
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ ExecStart=runagent -m node podman run \
${SUPPORT_IMAGE}
ExecStartPost=runagent -m node bash -c "nsenter -t $$(podman ps --format='{{.Pid}}' --filter=id=$$(< %t/support.cid)) -n -- nft -f $${AGENT_INSTALL_DIR}/etc/support/nft.conf"
ExecStartPost=runagent -m cluster support-clusteradminctl add
ExecStartPost=/usr/bin/systemd-run \
--collect \
--unit=%N-expire.timer \
--on-active=24h \
/usr/bin/systemctl stop %n
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/support.cid -t 10
ExecStopPost=runagent -m node support-sshkeyctl remove
ExecStopPost=runagent -m cluster support-clusteradminctl remove
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/support.cid
ExecStopPost=-/usr/bin/systemctl --no-block stop %N-expire.timer
PIDFile=%t/support.pid
Type=forking
SyslogIdentifier=support
RuntimeMaxSec=7d
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@

if [[ "$(systemctl is-active support.service)" == "active" ]]; then
jactive=true
if [[ "$(systemctl is-active support-expire.timer)" == "active" ]]; then
# 24 hours expiry
expires_at=$(date --utc -d "$(systemctl show --timestamp=utc -P ActiveEnterTimestamp support-expire.timer) + 24 hours" --iso-8601=seconds)
else
# 7 days expiry
expires_at=$(date --utc -d "$(systemctl show --timestamp=utc -P ActiveEnterTimestamp support.service) + 7 days" --iso-8601=seconds)
fi
else
jactive=false
fi

jq -c -n \
--argjson active "${jactive}" \
--arg session_id "$(grep VPN_PASSWORD support.env | cut -f 2 -d =)" \
'{"session_id": $session_id, "active": $active}'
--arg expires_at "${expires_at}" \
'{"session_id": $session_id, "active": $active, "expires_at": $expires_at}'
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"examples": [
{
"session_id": "811630de-67f4-5b6d-8b2f-7cc01f592b1b",
"expires_at": "2026-03-03T15:53:22+00:00",
"active": true
},
{
"session_id": "811630de-67f4-5b6d-8b2f-7cc01f592b1b",
"expires_at": "",
"active": false
}
],
Expand All @@ -23,6 +25,10 @@
"type": "string",
"description": "Last opened session identifier for the support team"
},
"expires_at": {
"type":"string",
"description": "Scheduled and automatic session stop time, in ISO 8601 format. An empty string is returned if support is not active."
},
"active": {
"type": "boolean",
"description": "If the opened session is still active or not"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pubkey_file=${3:-${AGENT_INSTALL_DIR:?}/etc/support/ssh-rsa.pub}
function add_ssh_key
{
local keyopts
keyopts=$(printf 'expiry-time="%s",from="127.0.0.1"' "$(date -d 'next month' +'%Y%m%dZ')")
keyopts=$(printf 'expiry-time="%s",from="127.0.0.1"' "$(date --utc -d '7 days' +'%Y%m%d%H%M%SZ')")
umask 077
printf "%s %s %s\n" "${keyopts}" "$(cat "${pubkey_file}")" "${key_comment}" >> "${authkeys_file}"
}
Expand Down
2 changes: 1 addition & 1 deletion core/ui/public/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@
"start_session_support": "Start session",
"session_id": "Session ID",
"paste_session_id_to_support_team": "Paste the session ID to the support team",
"remote_support_in_progress": "Remote support session in progress",
"remote_support_in_progress": "The remote support session will end automatically in {time}. You can end it manually at any time.",
"stop_session_support": "End session",
"remove_cluster_subscription_title": "Remove the cluster subscription",
"remove_cluster_subscription_description": "You are going to remove the subscription plan. This will disable the subscription features.",
Expand Down
2 changes: 1 addition & 1 deletion core/ui/public/i18n/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,7 @@
"remove_cluster_subscription_title": "Rimuovi la subscription del cluster",
"no_expiration": "Senza scadenza",
"paste_session_id_to_support_team": "Incolla l'ID di sessione al team di supporto",
"remote_support_in_progress": "Sessione di supporto remoto in corso",
"remote_support_in_progress": "La sessione di supporto remoto terminerà automaticamente tra {time}. Puoi terminarla manualmente in qualsiasi momento.",
"remote_support": "Supporto remoto",
"plan_name": "Piano",
"request_subscription": "Registra",
Expand Down
24 changes: 24 additions & 0 deletions core/ui/src/i18n/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,27 @@ export async function loadLanguage(lang) {
}
}
}

export async function getDateFnsLocale() {
const lang = navigator.language;
try {
const mod = await import(
/* webpackChunkName: "date-fns-locale-[request]" */
`date-fns/locale/${lang}`
);
return mod.default || mod;
} catch {
// try base language (e.g. "it" from "it-IT")
const baseLang = lang.split("-")[0];
try {
const mod = await import(
/* webpackChunkName: "date-fns-locale-[request]" */
`date-fns/locale/${baseLang}`
);
return mod.default || mod;
} catch {
const mod = await import("date-fns/locale/en-US");
return mod.default || mod;
}
}
}
35 changes: 34 additions & 1 deletion core/ui/src/views/settings/SettingsSubscription.vue
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,9 @@
<div class="mg-top-sm icon-and-text mg-bottom-lg">
<NsSvg :svg="InformationFilled16" class="icon ns-info" />
<span>{{
$t("settings_subscription.remote_support_in_progress")
$t("settings_subscription.remote_support_in_progress", {
time: formatExpiryDate(sessionExpireDate),
})
}}</span>
</div>
<NsButton
Expand Down Expand Up @@ -346,6 +348,8 @@ import {
PageTitleService,
DateTimeService,
} from "@nethserver/ns8-ui-lib";
import { intervalToDuration, parseISO, formatDuration } from "date-fns";
import { getDateFnsLocale } from "@/i18n";
import { mapGetters } from "vuex";

import NotificationService from "@/mixins/notification";
Expand Down Expand Up @@ -389,6 +393,8 @@ export default {
session_id: "",
termsUrl: "",
agreeTerms: false,
sessionExpireDate: "",
dateFnsLocale: null,
loading: {
getSubscription: false,
setSubscription: false,
Expand All @@ -412,6 +418,11 @@ export default {
created() {
this.getSubscription();
this.getSupportSession();

// load date-fns locale based on user language
getDateFnsLocale().then((locale) => {
this.dateFnsLocale = locale;
});
},
beforeRouteEnter(to, from, next) {
next((vm) => {
Expand All @@ -424,6 +435,27 @@ export default {
next();
},
methods: {
formatExpiryDate(dateString) {
const parsedDate = parseISO(dateString);
const duration = intervalToDuration({
start: new Date(),
end: parsedDate,
});

const dateTimeStr = new Intl.DateTimeFormat(navigator.language, {
hour: "2-digit",
minute: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric",
}).format(parsedDate);
const durationStr = formatDuration(duration, {
format: ["days", "hours"],
delimiter: ", ",
locale: this.dateFnsLocale,
});
return `${durationStr} (${dateTimeStr})`;
},
showRemoveSubcriptionModal() {
this.isShownRemoveSubcription = true;
},
Expand Down Expand Up @@ -693,6 +725,7 @@ export default {
const output = taskResult.output;
this.session_id = output.session_id;
this.active = output.active;
this.sessionExpireDate = output.expires_at;
},

async startSessionSupport() {
Expand Down
24 changes: 19 additions & 5 deletions docs/core/subscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,17 @@ with this command:
systemctl start support
systemctl status support

The second command prints a journal excerpt that should contain the session ID.

When a node support session is started

- support SSH key is added to the node /root/.ssh/authorized_keys file.
Only connections from the node itself are allowed with that key
- support SSH key is added to the node `/root/.ssh/authorized_keys` file
with an `expiry-time=` attribute. Only connections from the node itself
are allowed with that key.
- support credentials are enabled for cluster-admin access. The user name
is defined in Redis. Type `HGET cluster/subscription support_user` to
see its value, by default it is `nethsupport`. The password is set to
the support session ID value
the support session ID value. `nethsupport` can connect from localhost
and a restricted set of IP addresses, coded in the
`support-clusteradminctl` command.
- an OpenVPN tunnel is established with the support server, allowing
connections to sshd and cluster-admin. The support server address and
port can be overridden in the Redis `cluster/subscription` key
Expand All @@ -100,6 +101,19 @@ When the support session is terminated
- cluster-admin access is removed
- SSH key is removed

The support session is automatically terminated after 24 hours. To avoid
automatic termination and allow it to run up to the maximum allowed
duration of 7 days, execute this command on the relevant node:

systemctl stop support-expire.timer

After 7 days the session is terminated unconditionally.

Inspect the node session expiry with the `get-support-session` action. For
example:

api-cli run node/7/get-support-session

## OpenVPN support client

The Systemd unit `support.service` controls an OpenVPN client running in a
Expand Down