Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9e28bc2
Merge pull request #843 from skalenetwork/beta
dmytrotkk Mar 28, 2025
69971c0
Merge pull request #848 from skalenetwork/beta
DmytroNazarenko Apr 10, 2025
8f7c70a
Merge pull request #958 from skalenetwork/develop
dmytrotkk Feb 16, 2026
b1a501a
Merge pull request #960 from skalenetwork/develop
dmytrotkk Feb 20, 2026
8f70a85
Merge pull request #962 from skalenetwork/develop
dmytrotkk Feb 24, 2026
85c5be1
Merge pull request #965 from skalenetwork/develop
dmytrotkk Mar 3, 2026
6f1b886
Merge pull request #970 from skalenetwork/develop
dmytrotkk Mar 6, 2026
2c4d370
Merge pull request #973 from skalenetwork/develop
dmytrotkk Apr 15, 2026
2641840
Merge pull request #975 from skalenetwork/beta
dmytrotkk Apr 22, 2026
6a9646e
Update nftables
badrogger Sep 1, 2026
9215f79
Fix for existing nodes
badrogger Sep 1, 2026
a239ddb
Small improvement
badrogger Sep 1, 2026
abc8af9
Fix passive node init
badrogger Sep 1, 2026
fb77a0c
Small improvements
badrogger Sep 2, 2026
200d83b
Improve exception handling
badrogger Sep 8, 2026
ef28f02
Extract user rules to seprate chain
badrogger Sep 9, 2026
8c083b9
Fix for ssh port
badrogger Sep 10, 2026
1eebf76
Small improvements
badrogger Sep 11, 2026
c969d32
Improve exception handling
badrogger Sep 11, 2026
58c0abb
Fix setup
badrogger Sep 11, 2026
959be02
Remove MONITORING_PORTS functionality
badrogger Sep 11, 2026
8cc24cf
Bump version
badrogger Sep 11, 2026
ed032a4
Improve cleanup
badrogger Sep 11, 2026
5aa6b97
Fix monitoring container removal
badrogger Sep 11, 2026
7baaa6d
Remove redundant env option
badrogger Sep 14, 2026
f6f26de
Merge pull request #979 from skalenetwork/update-nftables
badrogger Sep 14, 2026
5918f48
Merge pull request #981 from skalenetwork/develop
badrogger Sep 16, 2026
ddaa946
Bump version
badrogger Sep 16, 2026
feb00ef
Merge pull request #982 from skalenetwork/bump-version
badrogger Sep 17, 2026
ff61de7
Fix publish
badrogger Sep 17, 2026
e4e03fa
Merge pull request #983 from skalenetwork/fix-publish
badrogger Sep 17, 2026
ffce261
Wrap sgx api into node-cli
badrogger Sep 22, 2026
b8462e8
Add sgx options call
badrogger Sep 23, 2026
905cc59
Add sgx api tests
badrogger Sep 23, 2026
b577c7d
Rename skale sgx status to skale sgx cert-status
badrogger Sep 23, 2026
9ee6157
Merge pull request #984 from skalenetwork/sgx-api
badrogger Sep 23, 2026
301f06b
Bunp version to 3.3.2
badrogger Sep 28, 2026
e8a9daa
Revert version bump
badrogger Sep 28, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION }}
target_commitish: ${{ github.sha }}
name: Release ${{ env.VERSION }}
draft: false
prerelease: ${{ steps.release_info.outputs.prerelease }}
Expand Down
92 changes: 89 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ SKALE Node CLI, part of the SKALE suite of validator tools, is the command line
4. [sChain commands (Standard)](#schain-commands-standard)
5. [Health commands (Standard)](#health-commands-standard)
6. [SSL commands (Standard)](#ssl-commands-standard)
7. [Logs commands (Standard)](#logs-commands-standard)
7. [SGX commands (Standard)](#sgx-commands-standard)
8. [Logs commands (Standard)](#logs-commands-standard)
3. [Passive Node Usage (`skale` - Passive Build)](#passive-node-usage-skale---passive-build)
1. [Top level commands (Passive)](#top-level-commands-passive)
2. [Passive node commands](#passive-node-commands)
Expand All @@ -32,8 +33,9 @@ SKALE Node CLI, part of the SKALE suite of validator tools, is the command line
5. [Fair Wallet commands](#fair-wallet-commands)
6. [Fair Logs commands](#fair-logs-commands)
7. [Fair SSL commands](#fair-ssl-commands)
8. [Fair Staking commands](#fair-staking-commands)
9. [Passive Fair Node commands](#passive-fair-node-commands)
8. [Fair SGX commands](#fair-sgx-commands)
9. [Fair Staking commands](#fair-staking-commands)
10. [Passive Fair Node commands](#passive-fair-node-commands)
5. [Exit codes](#exit-codes)
6. [Development](#development)

Expand Down Expand Up @@ -119,6 +121,29 @@ Options:

> Prefix: `skale node`

#### Configure firewall

Firewall setup does not automatically open monitoring ports 9100 and 8080.
Reconfiguration removes their legacy allow rules from the managed base chain
and saves the updated rules for reboot. `MONITORING_CONTAINERS` controls the
containers only; the firewall's `--monitoring` option has been removed.
Explicit rules in `/etc/nft.conf.d/skale/user.conf` remain under operator control.

SSH allow rules use all listening ports reported by `sshd -T`, including ports
configured through included files and `ListenAddress`. If detection fails, the
command stops before enabling the default-drop policy.

For a port configured through sshd command-line options or socket activation,
set the `SSH_PORT` environment variable explicitly when running commands that
configure the firewall (including node init and update):

```shell
sudo SSH_PORT=2222 skale node configure-firewall
```

The override replaces automatic detection and accepts one port from 1 to 65535.
It must be passed in the command environment, not only in the node settings file.

#### Node information

Get base info about the standard SKALE node.
Expand Down Expand Up @@ -476,6 +501,48 @@ Options:
* `--port/-p` - Port to start healthcheck server (default: `4536`).
* `--no-client` - Skip client connection (only make sure server started without errors).

### SGX commands (Standard)

> Prefix: `skale sgx`

Manage the client certificate that node services use to authenticate to the SGX wallet.
The files live in `~/.skale/node_data/sgx_certs` and are read by the SKALE containers.
These commands work directly with those files and the SGX server; they do not go through
the node API.

#### SGX certificate status

Show the certificate files, the certificate details and its expiry.

```shell
skale sgx status [--json] [--check]
```

Options:

* `--json` - Show data in JSON format.
* `--check` - Also verify that the SGX server accepts the certificate.

#### Renew SGX certificate

Issue a new client certificate from the SGX server and install it. The current
certificate stays in place until the new one is signed and verified against the server.
The previous files are copied to `~/.skale/node_data/sgx_certs_backup/<timestamp>`.
If the SGX server requires manual approval of signing requests, the command prints the
request hash and waits until it is approved. Node services pick up the new certificate
on their next SGX request; no restart is needed. `skale health sgx` confirms afterwards
that node services reach the SGX server.

```shell
skale sgx renew [--yes] [--timeout <SECONDS>] [--skip-verify]
```

Options:

* `--yes` - Do not ask for confirmation.
* `--timeout` - Seconds to wait for the SGX server to sign the request (default: `600`).
* `--skip-verify` - Install the certificate without testing it against the SGX server first.

### Logs commands (Standard)

> Prefix: `skale logs`
Expand Down Expand Up @@ -1096,6 +1163,25 @@ Options:
* `--no-client` - Skip client connection for openssl check.
* `--no-wss` - Skip WSS server starting for skaled check.

### Fair SGX commands

> Prefix: `fair sgx`

Manage the client certificate that node services use to authenticate to the SGX wallet.
See [SGX commands (Standard)](#sgx-commands-standard) for details; the behaviour is the same.

#### Fair SGX Status

```shell
fair sgx status [--json] [--check]
```

#### Fair SGX Renew

```shell
fair sgx renew [--yes] [--timeout <SECONDS>] [--skip-verify]
```

### Fair Staking commands

> Prefix: `fair staking`
Expand Down
5 changes: 2 additions & 3 deletions node_cli/cli/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,15 @@ def check(network):


@node.command(help='Reconfigure nftables rules')
@click.option('--monitoring', is_flag=True)
@click.option(
'--yes',
is_flag=True,
callback=abort_if_false,
expose_value=False,
prompt='Are you sure you want to reconfigure firewall rules?',
)
def configure_firewall(monitoring):
configure_firewall_rules(enable_monitoring=monitoring)
def configure_firewall():
configure_firewall_rules()


@node.command(help='Show node version information')
Expand Down
191 changes: 191 additions & 0 deletions node_cli/cli/sgx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
#
# This file is part of node-cli
#
# Copyright (C) 2026 SKALE Labs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import json

import click
from terminaltables import SingleTable

from node_cli.configs.sgx import SGX_SIGN_TIMEOUT
from node_cli.core.sgx import (
SgxCertificateError,
check_certificate,
get_certificate_status,
get_server_options,
renew_certificate,
)
from node_cli.utils.decorators import check_inited, check_user
from node_cli.utils.exit_codes import CLIExitCodes
from node_cli.utils.helper import abort_if_false, error_exit
from node_cli.utils.settings import get_sgx_url
from node_cli.utils.texts import safe_load_texts

G_TEXTS = safe_load_texts()
TEXTS = G_TEXTS['sgx']


@click.group()
def sgx_cli():
pass


@sgx_cli.group('sgx', help=TEXTS['help'])
def sgx():
pass


@sgx.command('options', help=TEXTS['options']['help'])
@click.option('--json', 'json_format', is_flag=True, help=G_TEXTS['common']['json'])
@check_inited
@check_user
def options(json_format: bool) -> None:
_configured_sgx_url()
status, payload = get_server_options()
if status != 'ok':
error_exit(payload, exit_code=CLIExitCodes.BAD_API_RESPONSE)
if json_format:
print(json.dumps(payload))
else:
rows = [['SGX option', 'Value']]
for group, values in payload.items():
entries = (
[(f'{group}.{key}', value) for key, value in values.items()]
if isinstance(values, dict)
else [(group, values)]
)
rows.extend(
[key, value if isinstance(value, str) else json.dumps(value)]
for key, value in entries
)
print(SingleTable(rows).table)


@sgx.command('cert-status', help=TEXTS['status']['help'])
@click.option('--json', 'json_format', is_flag=True, help=G_TEXTS['common']['json'])
@click.option('--check', is_flag=True, help=TEXTS['status']['check'])
def cert_status(json_format: bool, check: bool) -> None:
try:
info = get_certificate_status()
except SgxCertificateError as err:
error_exit(str(err), exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)
check_error = None
if check:
try:
info['server_version'] = check_certificate(_configured_sgx_url())
except SgxCertificateError as err:
check_error = str(err)
if json_format:
if check_error:
info['check_error'] = check_error
print(json.dumps(info))
else:
print_certificate_status(info)
if check_error:
error_exit(check_error, exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)


@sgx.command('renew', help=TEXTS['renew']['help'])
@click.option(
'--yes',
is_flag=True,
callback=abort_if_false,
expose_value=False,
prompt=TEXTS['renew']['prompt'],
)
@click.option(
'--timeout',
type=int,
default=SGX_SIGN_TIMEOUT,
show_default=True,
help=TEXTS['renew']['timeout'],
)
@click.option('--skip-verify', is_flag=True, help=TEXTS['renew']['skip_verify'])
@check_inited
@check_user
def renew(timeout: int, skip_verify: bool) -> None:
sgx_url = _configured_sgx_url()
try:
result = renew_certificate(sgx_url, timeout=timeout, verify=not skip_verify, log=print)
except SgxCertificateError as err:
error_exit(str(err), exit_code=CLIExitCodes.OPERATION_EXECUTION_ERROR)
print_certificate_status(result)
if result['backup']:
print(TEXTS['renew']['backup'].format(path=result['backup']))
print(TEXTS['renew']['done'])


def _configured_sgx_url() -> str:
try:
sgx_url = get_sgx_url()
except Exception as err: # settings files are missing or invalid
error_exit(f'Cannot read node settings: {err}', exit_code=CLIExitCodes.NODE_STATE_ERROR)
if not sgx_url:
error_exit(TEXTS['no_sgx'], exit_code=CLIExitCodes.NODE_STATE_ERROR)
return sgx_url


def print_certificate_status(info: dict) -> None:
present = info['present']
rows = [
['SGX client certificate', ''],
['Directory', info['directory']],
['Private key', _presence(present['key'])],
['Signing request', _presence(present['csr'])],
['Certificate', _presence(present['crt'])],
]
if 'subject' in info:
rows.extend(
[
['Subject CN', info['subject']],
['Issuer CN', info['issuer']],
['Valid from', info['not_valid_before']],
['Valid until', info['not_valid_after']],
['Days left', str(info['days_left'])],
['SHA-256', info['fingerprint_sha256']],
['Key matches', _yes_no(info['key_matches'])],
]
)
if info.get('server_version'):
rows.append(['SGX server', f'accepted the certificate, version {info["server_version"]}'])
print(SingleTable(rows).table)
for notice in _notices(info):
print(notice)


def _notices(info: dict) -> list[str]:
notices = []
if not info['complete']:
notices.append(TEXTS['status']['missing'])
elif info.get('expired'):
notices.append(TEXTS['status']['expired'])
elif info.get('expires_soon'):
notices.append(TEXTS['status']['expires_soon'].format(days=info['days_left']))
if info.get('key_matches') is False:
notices.append(TEXTS['status']['key_mismatch'])
return notices


def _presence(present: bool) -> str:
return 'present' if present else 'missing'


def _yes_no(value: bool | None) -> str:
if value is None:
return 'unknown'
return 'yes' if value else 'no'
2 changes: 2 additions & 0 deletions node_cli/configs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

SKALE_DIR = os.path.join(G_CONF_HOME, '.skale')
SKALE_TMP_DIR = os.path.join(SKALE_DIR, '.tmp')
AUTH_DIR = Path(SKALE_DIR) / 'auth'
ADMIN_API_TOKEN_PATH = AUTH_DIR / 'admin-api.token'

NODE_DATA_PATH = os.path.join(SKALE_DIR, 'node_data')
SCHAIN_NODE_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains')
Expand Down
2 changes: 1 addition & 1 deletion node_cli/configs/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
'update-safe',
],
'health': ['containers', 'schains'],
'info': ['sgx'],
'info': ['sgx', 'sgx-options'],
'schains': ['config', 'list', 'dkg-statuses', 'firewall-rules', 'repair', 'get'],
'ssl': ['status', 'upload'],
'wallet': ['info', 'send-eth'],
Expand Down
Loading
Loading