A program reading the info API opens the pipe by name and has no owner
check of its own, so for a name any local user could create first,
whoever did would be answering its queries. nebula refuses to serve a
held name and logs it, but the program cannot tell. The daemon now warns
at bind when the configured name is not under
\\.\pipe\ProtectedPrefix\Administrators\, and the example config and
CHANGELOG say to put it there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A program that verifies certificates against its own CAs, as
nebula-auth-request does, needs the certificate itself rather than the
parsed identity, and it asked the ssh debug server for it. The info API
now serves print-cert, so it can ask there instead.
- print-cert declares Identity: it reads a certificate from the hostmap or our own, and nothing operational
- -address names the vpn addr, with a port accepted and stripped and 4in6 unmapped, as identity takes it; the operand stays, and giving both is refused
- an address of our own answers with our own certificate, and a tunnel without one is not found, a 404 on the API
- with no address the certificate is the one whoami reports, the v2 when both versions are loaded, not the one handshakes start with
- GET /v1/print-cert?address=<addr>&raw=true answers {"pem": ...}; example config, CHANGELOG, surface test, goldens and an e2e check
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The info API is for other programs on the host, and on unix the only
setting between "the user nebula runs as" and "everyone" was a manual
chgrp that the next bind undid; on windows it was not served at all,
because which accounts a pipe admits had not been decided. The control
socket's `group` decides it, and the info API takes the same key.
- `local_api.info.group`: the socket is handed to the group at 0660
unless `socket_mode` says otherwise, and a mode that gives a named
group nothing is refused with the mode that would
- windows: served on a named pipe, the group an ACE on it as on the
control pipe; the e2e test's helpers dial unix sockets, so it still
skips there
- a changed group or mode on a reload is applied to the socket in place
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Build slackhq/nebula#1821 ("who is this vpn address?" over a local
HTTP+JSON socket) on the command registry, so the lookup is one command
on every surface and the socket is one more local API.
identity
- `identity [-address <vpn addr>[:port]]`: no address reports this node;
with one, this node or the peer behind an established tunnel
- Certificate fields only: name, vpn addresses, networks, unsafe
networks, groups, fingerprint, issuer, validity, cert version; a peer's
fingerprint is the one nebula computed when it verified the cert
- Port accepted and stripped, 4in6 unmapped, so a RemoteAddr passes
straight through
- No tunnel is a commands.NotFound; an empty -address is refused
- Lighthouse TXT lookup shares the owner and peer lookups, keeps answering
with the initiating certificate, and no longer panics without a default cert
- Served on ssh and nebula ctl too
Info API
- Off until `local_api.info.enabled` with `socket` and optional quoted
octal `socket_mode` (default "0600"); nothing derived
- Serves commands declaring Command.Identity plus help; others 403 naming
their surfaces. TestInfoAPISurface pins the set to identity
- GET /v1 is the schema; GET /v1/<cmd>?<flag>=<value> runs one; body is
the -json rendering via commands.Render; commands.PresentationFormat
reads json/pretty from the query the way a line is read
- 400 commands.ParamError, 403 commands.WithheldError (what Resolve now
returns for a withheld name, so help and dispatch type-check it too),
404 NotFound or unknown, 500 Run error, refusal with no reason, or
recovered panic with no detail; no redirects; every miss is JSON
- listen takes a mode: bind under umask 0077 then chmod; "0660" plus a
searchable dir opens it to a group
- The chmod, at bind and on a mode-only reload, goes through chmodSocket,
which never follows a link: a user who can write the socket's directory
could otherwise swap the node for a link and have a root daemon put
0660 or 0666 on any file. Only a socket we own is chmoded. On linux the
node is held open O_PATH|O_NOFOLLOW, checked by fstat, and chmoded
through /proc/self/fd, so the check and the chmod land on one inode;
fchmodat2 is the fallback without /proc. On darwin and the BSDs it is
an Lstat check and fchmodat(AT_SYMLINK_NOFOLLOW). clearStale Lstats
too, so a dangling link is refused rather than followed by bind on
darwin
- One endpoint type owns bind under mutex, bind-before-release move,
context watch and stop/unlink; each server supplies what it serves
- Reloadable through the same reconcile shape as the control socket:
mode applied in place, unparsable section keeps what runs, control
socket path refused by name
- Wire returns Endpoints{Control, Info} with one Stop
- Windows unsupported for now (a pipe has no mode); warned once
Tests: identity lookups and shapes; API filter, status codes, panic
recovery, socket mode, rebind, cancellation; config parsing incl. bare
0660; Wire transitions; e2e of two nebulas with the info API resolving
the peer by address and port, through shared test.HTTPDo/HTTPGet helpers.
Goldens gain identity.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add support for accessing the previously ssh-only debug commands via
`nebula ctl <command>.`
- unix socket (named pipe on windows) for communication
- sockets scoped to the user + an optional group
- in cmd/nebula and cmd/nebula-service, but not nebula.Main
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* examples: set socket buffers and raise tun tx_queue
* examples: use the default tcp max window for socket buffers
* examples: note buffers are linux only, clarify tx_queue default
* tun: default tx_queue to 5000
* add IPv6 reject packet generation (ICMPv6 Destination Unreachable and TCP RST)
* use ICMPv6 code 1 (administratively prohibited) and cap body at 1000 bytes
* cleanup, use ICMP error code 13 for ipv4
* better docs
* cleanup
* add sshd.sandbox_dir config option
Sanitize SSH profile paths (ssh.go:514,683,719) — restrict os.Create(a[0]) to a safe directory.
Add a config option in the config file to specify the sandbox directory. For backwards compatibility, if the config is not specified, keep the current behavior.
* update default and example
* use os.TempDir() for sshd.sandbox_dir default
* split sandbox path validation into separate conditionals
Separate the combined && check in sshSanitizeFilePath into two distinct
conditionals with specific error messages: one for paths resolving to the
sandbox directory itself, and one for paths outside the sandbox.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
* fix: trim leading zeros from p256 signature swap result
bigmod.Nat.Bytes() returns fixed-size 32-byte slices, but ASN.1 INTEGER
parsing strips leading zeros. This caused a flaky test failure (~1/256
chance) when the S value's high byte was zero.
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
---------
Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
- Accept certs signed by trusted CAs
- Username must match the cert principal if set
- Any username can be used if cert principal is empty
- Don't allow removed pubkeys/CAs to be used after reload
* add calculated_remotes
This setting allows us to "guess" what the remote might be for a host
while we wait for the lighthouse response. For networks that hard
designed with in mind, it can help speed up handshake performance, as well as
improve resiliency in the case that all lighthouses are down.
Example:
lighthouse:
# ...
calculated_remotes:
# For any Nebula IPs in 10.0.10.0/24, this will apply the mask and add
# the calculated IP as an initial remote (while we wait for the response
# from the lighthouse). Both CIDRs must have the same mask size.
# For example, Nebula IP 10.0.10.123 will have a calculated remote of
# 192.168.1.123
10.0.10.0/24:
- mask: 192.168.1.0/24
port: 4242
* figure out what is up with this test
* add test
* better logic for sending handshakes
Keep track of the last light of hosts we sent handshakes to. Only log
handshake sent messages if the list has changed.
Remove the test Test_NewHandshakeManagerTrigger because it is faulty and
makes no sense. It relys on the fact that no handshake packets actually
get sent, but with these changes we would send packets now (which it
should!)
* use atomic.Pointer
* cleanup to make it clearer
* fix typo in example