smallstep / cli

🧰 A zero trust swiss army knife for working with X509, OAuth, JWT, OATH OTP, etc.

Home Page:https://smallstep.com/cli

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Step CLI should not try to open a TTY if one is not available

jdoss opened this issue · comments

I have run into some cases where step cli tries to open a TTY to prompt for input on an error while being called by systemd. This results in an unhelpful error message such as in the journal:

May 02 08:55:28 sw-0608 step[702563]: error allocating terminal: open /dev/tty: no such device or address

Which doesn't give the user any clear information as to why the command failed when being called via systemd. For example this is the unit file that caused the above error:

[Unit]
Description=Issue a smallstep certificate for %i
After=network-online.target step-ca-bootstrap.service
Wants=network-online.target step-ca-bootstrap.service
Before=%i.service

[Service]
Type=oneshot
PassEnvironment=HOSTNAME
EnvironmentFile=/etc/step/env
ExecStartPre=step ca root /etc/%i/tls/%i-ca.crt --force
ExecStart=step ca certificate ${HOSTNAME} \
  --ca-url ${STEP_CA_URL} \
  --provisioner ${STEP_CA_PROVISIONER} \
  /etc/%i/tls/%i.crt \
  /etc/%i/tls/%i.key \
  --san 127.0.0.1 \
  --not-after 24h \
  --force \
  --provisioner-password-file=${STEP_PROVISIONER_PASSWORD_FILE}

Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

The above systemd error was generated by systemd's EnvironmentFile directive removing the PassEnvironment=HOSTNAME and it caused ${HOSTNAME} to be blank. This caused step to try to open a TTY and prompt for input which then fails because one does not exist when running step via systemd.

A reproducer for this specific case:

go run cmd/step/main.go ca certificate "" s.crt s.key
✔ What DNS names or IP addresses would you like to use? (e.g. internal.smallstep.com): <some input>
✔ Provisioner: Admin JWK (JWK) [kid: -bpK0AkvgJpt4Ju9_MaWDrzQL_fnOKOJiI521JkiuHU]
Please enter the password to decrypt the provisioner key:
token subject '<some input>' and argument '' do not match
exit status 1

Normally an invocation without the SAN set would result in the CLI returning with an error already. Apparently The "" is a valid input, but results in hitting the following code in utils/cautils/certificate_flow.go:

if subject == "" {
    subject, err = ui.Prompt("What DNS names or IP addresses would you like to use? (e.g. internal.smallstep.com)", ui.WithValidateNotEmpty())
    if err != nil {
        return "", err
    }
}

It is that prompt for <some input> that results in the open /dev/tty: no such device or address error.

To solve this, the CLI 1) needs to be aware of the input and validate that the input is not empty (and otherwise OK) and 2) needs to be aware of the environment its operating in. If these are true, the CLI can return a clearer error message about missing parameters or prompt for them when that's supported by its environment. This is not specific to this error; there are more cases where a similar error can happen.

I just got bit by this, it took me a while to understand what was actually going wrong, as I couldn't understand why the system was trying to open the tty. In my case it was being triggered by the certificate being in place already but not passing --force so step was trying to prompt me for the overwrite.

A related issue: #502

Following, hit this issue in github actions since the runner was being re-used and the cert already existed, a step ca bootstrap was requesting on stdin overwrite (y/n)? in a non-tty context. Same a @the-maldridge

I get same error too use by ansible

    - name: Generate an OpenSSL certificate signed with your Step-CA
      ansible.builtin.shell:
        cmd: sudo step-cli certificate sign --profile intermediate-ca ipa.csr root_ca.crt /etc/step-ca/secrets/root_ca_key | sudo tee -a ipa.crt
        chdir: /etc/step-ca/certs