fix: preserve file permissions in safe_open()#1117
Conversation
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Narrow the validity period from 1 month to 24 hours.
- Automatically stop the support.service unit 24 hours after support begins. - The auxiliary unit support-expire.timer can be stopped manually, to extend the support session duration as wanted. - In any case support.service will die after 30 days.
Return the session expiry timestamp in ISO 8601 format. Standard session duration is 24 hours, but it can be extended up to 30 days.
Co-authored-by: DavidePrincipi <2920838+DavidePrincipi@users.noreply.github.com>
Re-read per-user allowed networks from Redis in IdentityHandler and check the client IP in Authorizator before the GET bypass, so all HTTP methods are gated by the allowlist on every request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Usernames matching 'cluster', 'node/*', or 'module/*' are NS8 agent credentials. Return loopback + VPN network (cluster/network) as their forced allowlist so they cannot be used from outside the cluster, regardless of any per-user networks entry in Redis. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After credentials are verified, check the client IP against the per-user allowed networks (GetUserNetworks) before proceeding to role/action authorization. Returns 403 if the IP is not allowed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set Gin's trusted proxy list to 127.0.0.1 and ::1 so that X-Forwarded-For is honoured only for connections coming from the local Traefik instance. Direct connections (e.g. cluster agents over the VPN) use the TCP peer address, preventing clients from spoofing their source IP via forged headers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend user-attributes schema with an allowed_networks array of IPv4/IPv6 addresses and CIDR networks. Add ipv6-cidr and ip-cidr definitions to cluster.json. add-user stores the array as a CSV string in HSET user/<username> allowed_networks <csv> alter-user updates it when the key is present in the set block. An empty array removes all IP restrictions. Fix GetUserNetworks in api-server to read the allowed_networks field instead of the old networks field name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test suite 61__allowed_networks_login.robot covers all three enforcement points of the allowed_networks feature: - JWT login denied when source IP not in allowed_networks (401) - JWT login succeeds after restriction is removed (200) - JWT rejected mid-session after restriction is re-applied (403) - cluster/node/module agent credentials blocked from external IP (401) - HTTP-Basic module endpoint blocked from external IP (403) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agent credentials (cluster, node/*, module/*) are always restricted to loopback + cluster VPN network by the API server. Regular user accounts can optionally carry an allowed_networks restriction. Document this in: - core/api-server/README.md (new "Network access restrictions" section) - docs/core/api_server.md (new "Network access control" section) - docs/core/agents.md (note in the task-submission section) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass a space-separated list of IPs/CIDRs to restrict which source addresses the new user may log in from. add-user bob --role owner --allowed-networks 192.168.1.0/24 10.0.0.1 When the flag is omitted no IP restriction is applied. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow nethsupport cluster-admin access only from official Nethesis IP addresses. The loopback address is required for cluster-admin and SSH access from the support VPN.
The client IP address must be read from X-Forwarded-For header, set by the local, trusted Traefik proxy.
- Decrease session age hard limit from 30 to 7 days - Extend SSH key expire period to 7 days - Update subscription docs
The validation error protocol expects an array output.
Translate-URL: https://hosted.weblate.org/projects/ns8/core/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/ar/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/de/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/es/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/eu/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/fr/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/it/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/nl/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/pt/ Translate-URL: https://hosted.weblate.org/projects/ns8/core/pt_BR/ Translation: NS8/core Co-authored-by: Davide Principi <davide.principi@nethesis.it> Co-authored-by: Prefill add-on <noreply-addon-prefill@weblate.org>
Automatic stop of support.service unit
api-server: enforce per-user IP/network allowlist at login
Translate-URL: https://hosted.weblate.org/projects/ns8/core/it/ Translation: NS8/core Co-authored-by: Davide Principi <davide.principi@nethesis.it>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> [skip ci]
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> [skip ci]
[skip ci]
13ca326 to
c9192e8
Compare
Check the image existence with skopeo. [skip ci]
24836f7 to
3007c7f
Compare
3007c7f to
ccf044a
Compare
Revert environment file permissions to default mode 644.
- If the file is new, generate a unique temporary file name and write to it. Use os.open() to honor the process umask. - If the file already exists, copy its permissions to a temporary file created with tempfile.mkstemp().
ccf044a to
8666106
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| # Try to create a new file on tmp_path. Kernel | ||
| # applies current process umask on file creation | ||
| fd = os.open(tmp_path, base_flags | os.O_CREAT | os.O_EXCL, 0o666) |
There was a problem hiding this comment.
In the FileNotFoundError fallback branch, the temp file is created with os.open(..., base_flags | os.O_CREAT | os.O_EXCL, 0o666) but without os.O_CLOEXEC. Unlike tempfile.mkstemp() (which uses close-on-exec), this can leak the FD into any subsequently exec'd process before the context manager closes it. Consider adding os.O_CLOEXEC to the os.open flags (and/or using tempfile APIs that ensure CLOEXEC) to keep behavior consistent and avoid FD leaks.
| fd = os.open(tmp_path, base_flags | os.O_CREAT | os.O_EXCL, 0o666) | |
| fd = os.open( | |
| tmp_path, | |
| base_flags | os.O_CREAT | os.O_EXCL | getattr(os, "O_CLOEXEC", 0), | |
| 0o666, | |
| ) |
There was a problem hiding this comment.
https://docs.python.org/3.11/library/os.html#inheritance-of-file-descriptors
FDs are not inheritable by default
[root@rl1 state]# strace python3 -c 'import agent
with agent.safe_open("x") as fd:
print("x", file=fd)
'
...
openat(AT_FDCWD, "./.x.tmp-61119-0", O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0666) = 3
tempfile.mkstemp()unconditionally creates files with mode0600, ignoring both the existing file's permissions and the process umask, causing every file written throughsafe_open()to end up with0600.Refs NethServer/dev#7915