Skip to content

fix: user data with a blank line was sent unencoded, and only four public key types were recognised - #63

Open
tas50 wants to merge 2 commits into
mainfrom
fix-userdata-and-keypair-detection
Open

fix: user data with a blank line was sent unencoded, and only four public key types were recognised#63
tas50 wants to merge 2 commits into
mainfrom
fix-userdata-and-keypair-detection

Conversation

@tas50

@tas50 tas50 commented Aug 30, 2026

Copy link
Copy Markdown
Member

Two bugs I hit while going over the driver, each with a regression test that fails on main.

User data containing a blank line was sent to CloudStack unencoded

ServerOptions tries to avoid double-encoding user data that someone already
base64-encoded themselves. The check was:

BASE64_PATTERN = %r{^(?:[A-Za-z0-9+/]{4}\n?)*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$}

In Ruby ^ and $ anchor to a line, not to the string, and every branch of
that pattern is optional — so the pattern matches an empty line. Any user data
with a blank line anywhere in it therefore looked "already base64" and went out
raw:

"#cloud-config\npackages:\n - htop\n"    encoded   (no blank line)
"#cloud-config\n\npackages:\n - htop\n"  NOT encoded
"#!/bin/bash\n\necho hello\n"            NOT encoded

Blank lines between sections are completely ordinary in cloud-config and in
shell scripts, so this is the common case, not an edge one. The two existing
specs both happened to use user data with no blank line, which is why it
survived.

Anchored with \A/\z and made at least one group mandatory so the empty
string is not treated as pre-encoded either.

While in there: Base64.encode64 wraps at 60 characters, so anything over 45
bytes of user data — again, any real cloud-config — was going out with line
breaks embedded in an API query parameter. Switched to strict_encode64, which
produces a single line.

A short word made only of base64 characters (packages) is genuinely ambiguous
and is still passed through. There is no way to tell that apart from data
someone encoded, and I have noted it in the comment rather than guessing.

Only four public key types were recognised as "not a private key"

Credentials warns when the .pem it found is a public key, which is an easy
mistake to make and otherwise surfaces as an unreadable authentication failure
much later. It did that by comparing the file's first token against:

%w{ssh-rsa ssh-dsa ssh-ed25519 ecdsa-sha2-nistp256}

ssh-dsa is not a key type — the real one is ssh-dss. And the list has no
ecdsa-sha2-nistp384, no ecdsa-sha2-nistp521, and none of the security key
types. Those all went through silently.

Turned the test around: every private key format an SSH transport accepts —
PKCS#1, PKCS#8, OpenSSH's own — is PEM and opens with -----BEGIN, so ask
whether the file is a PEM. An unusual public key type, a PuTTY .ppk and an
empty file are all now reported. It also stops reading the whole key file into
memory to look at one token.

Testing

76 examples, 0 failures
9 files inspected, no offenses detected

The seven new examples all fail on main.

Conflicts

Deliberately kept clear of #62 — the server_options.rb changes here are the
BASE64_PATTERN constant and the userdata method, neither of which that
branch touches, and credentials.rb is not in it at all.

tas50 added 2 commits August 29, 2026 19:09
The check for user data that is already base64 was anchored with ^ and $,
which in Ruby anchor to a line rather than to the string, and every branch
of the pattern was optional. A single blank line therefore satisfied the
whole pattern, and cloud-config and shell scripts are full of blank lines,
so the ordinary case was handed to CloudStack unencoded.

Anchor with \A and \z, require at least one group so the empty string is
not treated as pre-encoded, and encode strictly so the result is one line
rather than wrapped at 60 characters inside an API query parameter.

Signed-off-by: Tim Smith <tim@mondoo.com>
The warning for a .pem that is not a private key compared the file's first
token against a list of four public key types. That list named ssh-dsa,
which is not a key type at all, and left out ssh-dss, ecdsa-sha2-nistp384,
ecdsa-sha2-nistp521 and the security key types, so those went through
silently and turned into an unreadable authentication failure later.

Ask whether the file is a PEM instead. Every private key format an SSH
transport accepts opens with -----BEGIN, so anything else -- an unusual
public key type, a PuTTY .ppk, an empty file -- is now reported.

Signed-off-by: Tim Smith <tim@mondoo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant