Skip to content

SSH environment

The SSH environment terminates the incoming SSH connection at Bifröst and creates a separately authenticated SSH connection to a target server. All sessions and direct TCP forwarding channels of one incoming connection share one target SSH transport.

Each target transport allows up to 64 concurrently active or pending shell, SFTP, forwarding and agent channels. Additional locally requested channels wait for capacity; excess agent channels initiated by the target are rejected.

If a channel-open request is still unanswered when its source operation is canceled, Bifröst closes the target transport so the pending request cannot retain a channel slot. This also interrupts other active channels of the same incoming connection; later requests can establish a new transport.

If the open result has already arrived, Bifröst normally closes only that operation's channel or forwarded connection. A subsystem target that does not finish within two seconds after its source is canceled causes the shared target transport to close. An open result arriving concurrently with cancellation may also cause the shared transport to close.

Configuration

type

Environment Type = "ssh"

Has to be set to ssh to enable the SSH environment.

variables

Defines environment variables sent to the target for commands, shells and SFTP. They override values received from the SSH client, authorized_keys and authorization. Bifröst-generated runtime variables take precedence. Variables are sent as best-effort SSH env requests; the target server decides which variables it accepts, commonly through OpenSSH AcceptEnv.

address

string Authorization

Target SSH address. The port defaults to 22. DNS names, IPv4 addresses and IPv6 addresses can omit it; an explicit IPv6 port uses [address]:port form.

user

string Authorization

User used to authenticate at the target SSH server.

os

Os = "linux"

Operating system of the target. This controls case-sensitive or case-insensitive environment-variable precedence.

knownHosts

string

Inline OpenSSH known_hosts content. A YAML block string can contain multiple entries.

knownHostsFile

string

Explicit path to one OpenSSH known_hosts file. Bifröst never implicitly reads user or system known_hosts files. knownHosts and knownHostsFile can be used together.

acceptAllHostKeys

bool = false

Disables target host-key verification. This cannot be combined with knownHosts or knownHostsFile and should only be used in controlled test environments.

Danger

Setting acceptAllHostKeys: true makes the target connection vulnerable to on-path attacks.

identityFiles

list of strings Authorization

Private SSH keys offered to the target. Every configured file has to exist and contain an unencrypted private key. If the list is absent or empty, Bifröst offers all configured server host keys as client identities. An invalid explicit entry never activates this fallback.

identityFiles and certificate are mutually exclusive. If neither is configured, the server-host-key fallback remains active.

certificate

Enables a persistent OpenSSH user certificate for the target connection. See below.

connectTimeout

duration Authorization = "10s"

Maximum duration for TCP connection establishment and SSH handshake. 0 disables this timeout.

loginAllowed

bool Authorization = true

Controls whether the environment accepts an authorization.

banner

string Authorization = ""

Displayed before an interactive target shell is opened.

portForwardingAllowed

bool Authorization = true

Controls local and dynamic forwarding after the applicable authorized-key policy has also been checked. Must also be true for reverse forwarding.

reversePortForwardingAllowed

bool Authorization = false

Enables ssh -R only when portForwardingAllowed is also true and the applicable authorized-key policy permits the bind address. Disabled by default. The listener is requested on the target SSH server, not on Bifröst. The target sshd must permit forwarding (AllowTcpForwarding, including remote forwarding) and its GatewayPorts setting controls which requested bind addresses it accepts. The destination of each forwarded connection is reached from the SSH client.

Warning

Bifröst checks permitlisten against the requested bind address. The SSH protocol does not report the address actually bound by the target. With GatewayPorts yes, a target sshd can turn a permitted loopback request into a wildcard listener. If permitlisten is intended to restrict network exposure, ensure the target server does not widen the bind address (for example, use GatewayPorts no) before enabling reverse forwarding.

allowedSubsystems

regular expression = "^sftp$"

Only matching SSH subsystem names are forwarded to the target. The regular expression must match the entire name, even if no anchors are written. By default, only sftp is allowed; allowedSubsystems: '' denies all subsystems, while YAML null is invalid. For multiple names, use an expression such as sftp|netconf|powershell. Shell and exec are unaffected. An authorized-key forced command is executed instead of forwarding the requested subsystem.

At least one host-key verification source is required unless acceptAllHostKeys is explicitly enabled.

SSH User Certificate

Bifröst issues exactly one certificate for each persistent Bifröst session. Reconnecting and restarting Bifröst reuse the byte-identical certificate until its immutable validity boundary is reached.

Configuration

identityFile

Static path to the private subject key used with the certificate. The default is /etc/engity/bifroest/client-key on Linux, /Library/Application Support/Engity/Bifroest/client-key on Darwin, and C:\ProgramData\Engity\Bifroest\client-key on Windows. The default key is shared by all certificate-enabled flows of one Bifröst instance. If the file does not exist, an Ed25519 key is generated. Existing unreadable, encrypted or invalid files cause startup to fail and are never overwritten. The private key is not copied into session storage.

authorityIdentityFile

Static path to the private SSH certificate-authority key. The default is /etc/engity/bifroest/ca on Linux, /Library/Application Support/Engity/Bifroest/ca on Darwin, and C:\ProgramData\Engity\Bifroest\ca on Windows. The default CA is shared by all certificate-enabled flows of one Bifröst instance. If the file does not exist, an Ed25519 key is generated. Existing unreadable, encrypted or invalid files cause startup to fail and are never overwritten. The server host key is never used as certificate authority. Existing certificates remain bound to their original CA after a configured CA rotation.

Bifröst does not create a .pub companion file for either private key. The CA public key is logged at every startup and can be exported for a specific flow with bifroest key export ca.

validity

duration = "15m"

Positive lifetime of a newly issued certificate. The first issuance persists MaxValidUntil, and reconnects, activity and later configuration increases never move that boundary. This lifetime is separate from the dynamic Bifröst session idle timeout.

Certificate expiry does not disconnect an already authenticated downstream SSH transport, the incoming client connection or the Bifröst session. It only prevents the expired certificate from authenticating a new or re-established downstream SSH connection. Connection and session timeouts control their respective lifetimes independently.

validAfterSkew

duration = "30s"

Non-negative clock skew subtracted from the issuance time for the OpenSSH ValidAfter field.

audience

string Authorization

Enables the Bifröst delegation profile and identifies the intended downstream authorization, normally its flow name. If absent, the certificate uses the interoperable audit profile. The two profiles are described below.

principals

list of strings Authorization

Additional OpenSSH principals. The rendered target user is always included and empty rendered principals are rejected.

extensions

map of strings Authorization

OpenSSH certificate extensions and their values. Standard extensions such as permit-pty, permit-port-forwarding and permit-agent-forwarding are removed when the effective incoming authorization policy denies the corresponding capability. Names ending in @bifroest.engity.org are reserved for Bifröst metadata.

Certificate metadata uses only the reserved evidence-v1@bifroest.engity.org extension. The size-limited evidence document contains allowlisted origin, hop, target and capability fields; passwords, OAuth tokens and unrestricted authorization data are never included.

Certificate profiles

Without audience, Bifröst issues an audit-profile certificate. Its evidence is a non-critical extension, so standard OpenSSH and Bifröst's simple and local authorizations can authenticate it while ignoring the metadata.

With audience, Bifröst additionally sets the critical option bifroest-delegation@bifroest.engity.org. Standard OpenSSH and authorization types that do not understand this option reject the certificate. Only authorization.type: bifroest validates the signed evidence and accepts the delegation.

When a bifroest authorization forwards to another SSH environment, the original identity and validated hop history are retained. The new hop cannot move ValidAfter earlier, extend ValidBefore, or restore a PTY, port-forwarding or agent-forwarding capability denied upstream. Because destination-specific permitopen and permitlisten rules are hop-local, their presence conservatively disables delegated port forwarding rather than broadening it downstream.

Supported operations

Operation Behavior
Shell and exec Opened as separate channels on the shared target transport
stdin, stdout and stderr Forwarded without merging stderr into stdout
Exit status Returned from the target command
PTY and resize Forwarded for shell and exec sessions; subsystem requests with a PTY are rejected
Signals Forwarded for shell and exec sessions
Allowed SSH subsystems (including SFTP) The requested subsystem name is sent unchanged to the target; stdin, stdout and stderr are streamed without interpreting the protocol. The client receives success only after the target accepts the subsystem request.
SCP Modern SCP uses SFTP; legacy SCP is handled as an exec command
Agent forwarding Forwarded for shell, exec and non-SFTP subsystems only when requested and permitted by the authorization policy
ssh -L and ssh -D Connections originate from the target SSH server's network
ssh -R When enabled, the target SSH server binds the listener according to its GatewayPorts setting; each forwarded connection reaches its destination from the SSH client

The environment allowlist is checked before connecting to the target. A subsystem denied by the allowlist or rejected by the target receives a failed SSH subsystem request. The target has 30 seconds by default to answer an allowed subsystem request before the incoming request is rejected. Subsystem names must be non-empty, valid UTF-8, contain no NUL byte and be at most 256 bytes long. Subsystem requests with a PTY are rejected to prevent terminal newline conversion from changing protocol data. Agent forwarding must be requested before the subsystem starts. Audit events record the requested subsystem name. Subsystem streams are not terminal-recorded, and no login notification is written into their stdout. SSH break requests and other arbitrary session requests are not forwarded. Target exit signals are not forwarded as SSH exit signals; a target exit status and the target output must both finish within 30 seconds after either one finishes to report a successful session completion.

If an ssh -R request is canceled while the target has not replied to the listen request, Bifröst closes the shared target SSH transport. If the target does not reply to cancel-tcpip-forward when a listener closes, Bifröst also closes that transport. Other active channels and reverse forwards sharing it are interrupted.

If the client's stdin remains blocked for 30 seconds after a subsystem session ends, Bifröst closes the entire incoming SSH connection. This also terminates other sessions and forwards on that connection. The resulting connection.closed audit event has the reason deadline-exceeded.

Warning

OpenSSH agent forwarding is scoped to an SSH connection rather than an individual session. After one permitted session enables forwarding, the target can access that source agent until the incoming SSH connection ends. Only enable agent forwarding for trusted targets.

Examples

Private key authentication

1
2
3
4
5
6
7
8
9
type: ssh
address: target.example.org
user: '{{ .session.created.remote.user }}'
knownHosts: |
  target.example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
identityFiles:
  - /etc/engity/bifroest/id_target
variables:
  LC_ALL: C.UTF-8

OpenSSH certificate authentication

  1. Import the running OpenSSH server's host key, then use this flow:

    1
    2
    3
    4
    bifroest key import host \
      --knownHostsFile /etc/engity/bifroest/known_hosts \
      --address internal.example.org \
      --expectedFingerprint SHA256:...
    

    Warning

    A plain password and --expectedFingerprint unknown are suitable only for testing. Verify production host-key fingerprints independently and use a password hash or another authorization.

  2. Configure Bifröst:

    /etc/engity/bifroest/configuration.yaml
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    flows:
      - name: openssh
        authorization:
          type: simple
          entries:
            - name: alice
              password: plain:change-me
        environment:
          type: ssh
          address: internal.example.org
          user: alice
          knownHostsFile: /etc/engity/bifroest/known_hosts
          certificate:
            extensions:
              permit-pty: ""
    

    Note

    The OpenSSH server needs a local alice account. Leave audience unset because OpenSSH does not understand Bifröst's delegation critical option.

  3. Export the CA and deploy the output as /etc/ssh/bifroest-ca.pub on the OpenSSH server:

    1
    2
    3
    4
    bifroest key export ca \
      -c /etc/engity/bifroest/configuration.yaml \
      openssh \
      --output /tmp/ca.pub
    

  4. Configure sshd:

    /etc/ssh/sshd_config.d/bifroest.conf
    1
    2
    PubkeyAuthentication yes
    TrustedUserCAKeys /etc/ssh/bifroest-ca.pub
    

  5. Restart sshd:

    1
    systemctl restart sshd
    

Bifröst delegation

For Bifröst-to-Bifröst certificate delegation, see the Bifröst authorization example.

Compatibility

linux darwin windows
/ / /