Docker environment
When using Docker environments, each user session runs in a separate Docker container. This is in contrast to the local environment, where each user session runs on the same host as Bifröst.
This is useful if you explicitly do not want to give users access to the host itself, but to environments where they can work with defined toolsets. This is especially true, if you want to create demo or training environments.
In another use case you can set up a Bastion/Jump host, what allows the user to jump from one server to another network. Using different networks can be beneficial, too.
Configuration
type
Environment Type = "docker"
Has to be set to docker to enable the docker environment.
variables
Defines environment variables for commands and shells in the container. They override image values and values received from the SSH client, authorized_keys and authorization. Bifröst-generated runtime variables take precedence.
loginAllowed
bool Authorization = true
Has to be true (after being evaluated) that the user is allowed to use this environment.
Examples
- Require that the existing local user has the group
ssh:1 2 3 4 5
loginAllowed: | {{ or (.authorization.user.group.name | eq "ssh" ) (.authorization.user.groups | firstMatching `{{.name | eq "ssh"}}`) }}
host
string Authorization = "{{ env `DOCKER_HOST` }}"
URL how to connect to the API endpoint of the docker compatible daemon.
Accepted protocols are:
httphttpstcpunix(only supported on unix based systems)npipe(only supported on Windows)
If this variable is empty (which can be also the case if {{ env `DOCKER_HOST` }} evaluates to empty, because the environment variable is not set), the system specific connection will be chosen:
- Unix (such as Linux):
unix:///var/run/docker.sock - Windows:
npipe:////./pipe/docker_engine
apiVersion
string Authorization =
"{{ env \`DOCKER_API_VERSION\` }}"
Defines which version of Docker API should be chosen for communication. In doubt leave this blank.
certPath
"{{ env \`DOCKER_CERT_PATH\` }}"
Is a directory which should contain the following files:
key.pem: Private key Bifröst should connect to the Docker API with.cert.pemCertificate Bifröst should present when connect to the Docker API.ca.pem: Certificate authorities to check the certificate of the Docker API Host against.
If this variable is empty (which can be also the case if {{ env `DOCKER_CERT_PATH` }} evaluates to empty) and tlsVerify is set to false, all Docker API Hosts are accepted.
tlsVerify
bool Authorization =
"{{ env \`DOCKER_TLS_VERIFY\` | ne \`\` }}"
If this variable is false (which can be also the case if {{ env `DOCKER_TLS_VERIFY` }} evaluates to empty), all Docker API Hosts are accepted.
Danger
Setting this to false is only recommend, if connecting to the local socket connection (on the same machine - see host's default behavior).
image
string Authorization = "alpine"
Which OCI/Docker image should be used for this environment.
Everything available within this image will be also available to the user who is connecting to the container using this image. Therefore, you should consider to creating your custom images if you need additional features like kubectl, skopeo, ... and use this one here.
This image needs to contain a valid shell executable.
ENTRYPOINT and CMD settings of the image will be ignored.
On Linux, image ENV values are not inherited by SSH commands. Bifröst deliberately clears them before starting its privileged IMP and exec wrapper, then supplies only the environment assembled for the individual authorization and SSH execution. This prevents image-controlled loader variables such as LD_PRELOAD from affecting the privileged wrapper. Windows containers retain their image environment for IMP and wrapper startup; the SSH target command still receives only its explicitly assembled execution environment.
imagePullPolicy
Pull Policy Authorization = "ifAbsent"
Defines what should happen if the container starts with the required image of the container.
If the image needs to be pulled, it will trigger the image-pull preparation process.
imagePullCredentials
Defines credentials which should be used to pull the defined image.
Examples
- Using direct json:
1 2
imagePullCredentials: | {"username":"foo","password":"bar"} - Using base64 URL encoded json:
1 2
imagePullCredentials: | eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoiYmFyIn0 - Using content from file:
1imagePullCredentials: "{{ file `/etc/engity/bifroest/secrets/my-great-secret` }}" - Using content from environment variable:
1imagePullCredentials: "{{ env `MY_GREAT_SECRET` }}"
networks
[]string Authorization = ["default"]
Defines the container networks this container should be connected to.
Empty always defaults to ["default"].
Note
As long as impPublishHost isn't set, the first network should be always reachable by Bifröst itself. This can be either the case if Bifröst itself runs inside of Docker (Bifröst in Docker) or it runs on the host machine and there is a valid route (which is the default Linux native, but not on Docker/Podman for Desktop).
volumes
[]string Authorization
Defines which volumes should be mounted into the container. Each entry is an individual mount statement.
This is the equivalent of -v/--volume flag of Docker. See Bind mounts documentation of Docker about the syntax of these entries.
We recommend to use mounts instead, because it is easier to understand. volumes is the older version of the notation and known by experienced users.
mounts
[]string Authorization
Defines which volumes should be mounted into the container. Each entry is an individual mount statement.
This is the equivalent of --mount flag of Docker. See Bind mounts documentation of Docker about the syntax of these entries.
capabilities
[]string Authorization
List of Unix kernel capabilities to be added to the container. This enables a more fine-grained version in contrast to give all capabilities to the container with privileged = true.
Does only work on Unix based systems.
privileged
bool Authorization = false
If this is set to true this container will have all capabilities of the system.
Danger
Only enable this feature if you really need this, and you know what you're doing.
dnsServers
[]string Authorization
Defines a list of external DNS server the container should use.
dnsSearch
[]string Authorization
Defines custom DNS search domains for the container.
shellCommand
[]string Authorization = "<os specific>"
The shell which should be used to execute the user into.
If not defined, the following command will be used:
- Linux:
["/bin/sh"] - Windows:
["C:\WINDOWS\system32\cmd.exe"]
execCommand
[]string Authorization = "<os specific>"
If execute is used, this is the command prefix which will used for the command.
If not defined, the following command will be used:
- Linux:
["/bin/sh", "-c"] - Windows:
["C:\WINDOWS\system32\cmd.exe", "/C"]
sftpCommand
[]string Authorization = ["bifroest", "sftp-server"]
Defines the sftp server command which should be used. Usually you should not be required to modify this, because by default Bifröst is handling this by itself.
directory
Defines the working directory of the initial process inside the container for each execution.
If not defined the value WORKDIR will be used. If this is absent it defaults to: /.
user
string Authorization
Defines the user will run with inside the container.
If not defined the value USER will be used. If this is absent it defaults to: root.
Inside Linux containers, the IMP starts as root so that it can switch to this target identity. The SSH command itself runs as the selected target user and receives that user's supplementary groups when they can be resolved. Numeric UID/GID values do not require an /etc/passwd or /etc/group entry.
banner
string Authorization = ""
Will be displayed to the user upon connection to its environment.
Examples
- If local user is used, show its name in a message:
1banner: "Hello, {{.authorization.user.name}}!\n" - If users authorized via OIDC is used, show its name in a message:
1banner: "Hello, {{.authorization.idToken.name}}!\n"
portForwardingAllowed
bool Authorization = true
If true, users are allowed to use SSH's port forwarding mechanism, subject to the applicable authorized-key policy. For ssh -R, the listener binds inside the session's container network namespace, not on the Bifröst host. An empty bind host uses loopback inside the container; an explicit * requests a wildcard bind that may be reachable over the container network, subject to policy and network configuration. The forwarded destination is reached from the SSH client. Reverse forwarding does not automatically publish a Docker host port.
Each container accepts at most 64 simultaneous reverse-forwarded TCP connections across all its reverse listeners. Additional connections are closed. This limit is independent of the SSH channel limits.
On Linux, reverse TCP ports 1-1023 require a target user with UID 0 (for example user: root or user: "0"). An empty user setting is also permitted when the resolved image user is root. An unresolvable user cannot authorize these ports. Port 0 and ports 1024 and above are unchanged. Windows containers do not have this restriction.
impPublishHost
string
If this property is set, only the IMP port 8683 is published with a dynamically allocated host port in addition to being exposed on the container network. Other ports declared with the image's EXPOSE instruction are not published automatically.
At this address Bifröst will then connect to the published IMP port. The value is not passed to the Docker daemon as a host-interface binding; the daemon chooses the publish interface according to its own defaults. This property is static and does not support template evaluation.
Warning
To set this property makes only sense as long you have a firewall in place, which prevents external attackers to connect to the host ports, and you have no other choice. Usually Bifröst can connect via the container networks to IMP directly (see networks).
This is usually required, if you run Bifröst on a Docker/Podman for Desktop installation (such as on Windows or macOS) where the Docker daemon does not run on the host directly, but inside a virtual machine.
cleanOrphan
bool Container = true
While the housekeeping iterations this environment will look for containers that can be inspected by its docker daemon connection if there is any container that does not belong to any flow of this Bifröst instance.
This is useful to clean up old containers which are leftovers after you have changed the configuration of Bifröst.
Warning
If multiple Bifröst installations are using the same Docker host, this should be disabled. Otherwise, each instance is removing the container of the other instance.
Execution lifecycle and upgrades
Docker containers created by the current version carry the labels org.engity.bifroest/execution-lifecycle=execution-id-v1 and org.engity.bifroest/imp-protocol-revision=2. These declare the execution-lifecycle and IMP protocol revisions assigned when the container was created; they do not verify the actual IMP binary, whether it is running or whether an execution is reachable. Bifröst uses a unique execution ID to route signals and exit statuses to the correct command and supervises the command's process tree.
On Windows, the command is resumed only after it has been assigned to Bifröst's kill-on-close Job Object. If restrictions on an inherited Job Object reject nested assignment, the command fails while still suspended rather than running without descendant-cleanup guarantees.
On Linux, execution-scoped signaling requires Linux kernel 5.3 or later for pidfd_open and pidfd_send_signal support. The container's seccomp policy must allow both system calls; Bifröst rejects the signal request rather than falling back to an unsafe numeric PID when they are unavailable.
Containers without matching declared lifecycle and IMP protocol revisions cannot be reused. A missing IMP protocol revision is treated as revision 1. A normal login rejects reuse without deleting the container, even when automatic cleanup is allowed. Housekeeping proactively disposes sessions with known incompatible metadata as a whole, even if still active and not expired, including their environments; explicit environment disposal also removes the container. Invalid metadata or failed inspections require operator review instead. Session storage is deleted only after the required cleanup succeeds and its retention period has elapsed.
Warning
Removing the container can destroy container-local data and anonymous volumes. Back up anything you need before upgrading or allowing disposal; metadata alone is not a guarantee that the container or its data will be preserved.
Preparation Processes
If events about preparation processes are emitted by this environment, they are picked up by connections (like SSH) and handled.
The docker environment emits the following processes:
pull-image
In cases an image needs to be pulled, either it does not exist or imagePullPolicy is to always, the image pull process starts. As this will block the user interaction with its session, this process event is emitted. It makes it possible to show the progress of download to the user.
Properties
image
string
Holds the tag of the image to be downloaded.
Examples
- Simple:
1type: docker - With ubuntu image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
type: docker image: ubuntu ## Using /bin/bash instead of /bin/sh, ## because it does exist in the image shellCommand: [/bin/bash] execCommand: [/bin/bash, -c] variables: APPLICATION_ENV: production ## Only allow login if the OIDC's groups has "my-great-group-uuid" ## ...and the tid (tenant ID) is "my-great-tenant-uuid" loginAllowed: | {{ and (.authorization.idToken.groups | has "my-great-group-uuid") (.authorization.idToken.tid | eq "my-great-tenant-uuid") }} - Using my own registry with secret from file:
1 2 3 4 5
type: docker image: my.own.registry.com/foo/bar ## Using the pull credentials, which are stored inside: ## /etc/engity/bifroest/secrets/my.own.registry.com imagePullCredentials: "{{ file `/etc/engity/bifroest/secrets/my.own.registry.com` }}" - Using my own registry with secret from environment variable:
1 2 3 4 5
type: docker image: my.own.registry.com/foo/bar ## Using the pull credentials, which are stored inside ## MY_GREAT_SECRET environment variable imagePullCredentials: "{{ env `MY_GREAT_SECRET` }}"
Compatibility
linux |
darwin |
windows |
|---|---|---|
| / | / | / |