From 2e4f3df9fea3d4bb9ce04b8b2a1c5550fd671664 Mon Sep 17 00:00:00 2001 From: SMWCoder <166565533+SMWCoder@users.noreply.github.com> Date: Thu, 8 May 2025 12:18:52 -0400 Subject: [PATCH] Add files via upload --- dogfood/redhat-envbuilder/Dockerfile | 152 +++++++ dogfood/redhat-envbuilder/README.md | 67 +++ dogfood/redhat-envbuilder/devcontainer.json | 8 + dogfood/redhat-envbuilder/main.tf | 454 ++++++++++++++++++++ 4 files changed, 681 insertions(+) create mode 100644 dogfood/redhat-envbuilder/Dockerfile create mode 100644 dogfood/redhat-envbuilder/README.md create mode 100644 dogfood/redhat-envbuilder/devcontainer.json create mode 100644 dogfood/redhat-envbuilder/main.tf diff --git a/dogfood/redhat-envbuilder/Dockerfile b/dogfood/redhat-envbuilder/Dockerfile new file mode 100644 index 0000000000000..a2de7d2614553 --- /dev/null +++ b/dogfood/redhat-envbuilder/Dockerfile @@ -0,0 +1,152 @@ +FROM registry.access.redhat.com/ubi9/ubi:latest + +SHELL ["/bin/bash", "-c"] + +# Set ARGs that will be used throughout the build +ARG DEBIAN_FRONTEND="noninteractive" +ARG GO_VERSION=1.24.3 # Latest stable version for Coder 2.20.3 +ARG NODE_VERSION=20.19.0 # Latest LTS version +ARG NVM_DIR=/usr/local/nvm + +# Install basic packages and FIPS compliance tools +RUN dnf update -y && \ + dnf install -y \ + bash \ + bash-completion \ + bind-utils \ + ca-certificates \ + cmake \ + crypto-policies \ + crypto-policies-scripts \ + curl \ + dnsutils \ + file \ + findutils \ + fipscheck \ + git \ + gnupg \ + graphviz \ + htop \ + iproute \ + iputils \ + jq \ + less \ + lsof \ + make \ + man \ + net-tools \ + openssh-server \ + openssl \ + openssl-fips \ + pkg-config \ + python3 \ + python3-pip \ + rsync \ + screen \ + strace \ + sudo \ + tmux \ + traceroute \ + unzip \ + util-linux \ + vim \ + wget \ + which \ + zip \ + zsh && \ + dnf clean all && \ + # Configure FIPS-compliant policies - explicitly set FIPS mode + update-crypto-policies --set FIPS && \ + # Verify FIPS mode is active + update-crypto-policies --show + +# Install Go with FIPS-compliant download validation +RUN curl --silent --show-error --location \ + "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" \ + -o /usr/local/go.tar.gz && \ + # Verify Go checksum to ensure FIPS compliance + echo "$(curl -s https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz.sha256) /usr/local/go.tar.gz" | sha256sum --check && \ + mkdir -p /usr/local/go && \ + tar --extract --gzip --directory=/usr/local/go --file=/usr/local/go.tar.gz --strip-components=1 + +ENV PATH=$PATH:/usr/local/go/bin + +# Install Docker +RUN dnf install -y dnf-plugins-core && \ + dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \ + dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin && \ + systemctl enable docker + +# Install Terraform with FIPS-compliant validation +RUN wget -O /tmp/terraform.zip "https://releases.hashicorp.com/terraform/1.11.5/terraform_1.11.5_linux_amd64.zip" && \ + wget -O /tmp/terraform_SHA256SUMS "https://releases.hashicorp.com/terraform/1.11.5/terraform_1.11.5_SHA256SUMS" && \ + grep linux_amd64 /tmp/terraform_SHA256SUMS | sha256sum --check --status && \ + unzip /tmp/terraform.zip -d /usr/local/bin && \ + rm -f /tmp/terraform.zip /tmp/terraform_SHA256SUMS && \ + chmod +x /usr/local/bin/terraform && \ + terraform version + +# Install Node.js using NVM with FIPS-compliant validation +RUN mkdir -p $NVM_DIR && \ + # Download NVM with verification + curl -o nvm_install.sh https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.8/install.sh && \ + echo "$(curl -s https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.8/install.sh | sha256sum | cut -d ' ' -f1) nvm_install.sh" | sha256sum --check && \ + bash nvm_install.sh && \ + # Install Node using NVM with FIPS-compatible settings + source $NVM_DIR/nvm.sh && \ + # Force Node to use OpenSSL FIPS + export NODE_OPTIONS="--openssl-config=/etc/crypto-policies/back-ends/openssl.config" && \ + nvm install $NODE_VERSION && \ + nvm use $NODE_VERSION && \ + nvm alias default $NODE_VERSION && \ + # Verify that Node.js will use FIPS compliant crypto + node -e "console.log('FIPS mode:', process.versions.openssl)" + +ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + +# Install pnpm and npm +RUN npm install -g npm@10.12.1 && \ + npm install -g pnpm@9.17.4 + +# Setup GitHub CLI +RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && \ + dnf install -y gh + +# Configure systemd services for our container +RUN systemctl enable \ + docker \ + ssh + +# Add coder user and allow use of docker/sudo +RUN useradd coder \ + --create-home \ + --shell=/bin/bash \ + --groups=wheel \ + --uid=1000 \ + --user-group && \ + echo "coder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/coder && \ + chmod 0440 /etc/sudoers.d/coder + +# Add Docker group and add coder user to it +RUN groupadd -f docker && \ + usermod -aG docker coder + +# Adjust SSH config for X11 forwarding +RUN echo "PermitUserEnvironment yes" >>/etc/ssh/sshd_config && \ + echo "X11Forwarding yes" >>/etc/ssh/sshd_config && \ + echo "X11UseLocalhost no" >>/etc/ssh/sshd_config + +# Set environment variables +ENV GOPATH="/home/coder/go" +ENV PATH="$GOPATH/bin:$PATH" +ENV GOPRIVATE="coder.com,cdr.dev,go.coder.com,github.com/cdr,github.com/coder" +# Set OpenSSL to use FIPS mode +ENV OPENSSL_FORCE_FIPS_MODE=1 +# Configure Node.js for FIPS compliance and memory settings +ENV NODE_OPTIONS="--max-old-space-size=8192 --openssl-config=/etc/crypto-policies/back-ends/openssl.config" +# Ensure Python respects system crypto policy +ENV PYTHONHTTPSVERIFY=1 + +USER coder +WORKDIR /home/coder \ No newline at end of file diff --git a/dogfood/redhat-envbuilder/README.md b/dogfood/redhat-envbuilder/README.md new file mode 100644 index 0000000000000..291c8c45d840c --- /dev/null +++ b/dogfood/redhat-envbuilder/README.md @@ -0,0 +1,67 @@ +# FIPS-Compliant Red Hat envbuilder template for Coder 2.20.3 + +This template creates a Red Hat Enterprise Linux (RHEL) compatible development environment using the [envbuilder](https://github.com/coder/envbuilder) tool. It's based on the Red Hat Universal Base Image (UBI 9) which provides a RHEL-compatible environment that meets Federal Information Processing Standards (FIPS) requirements for high-security enterprise environments. This template is optimized for Coder v2.20.3. + +## Features + +- Built on Red Hat Universal Base Image (UBI 9) +- Compatible with Coder v2.20.3 +- FIPS 140-2/140-3 compliant security configuration +- Validated cryptographic modules and libraries +- FIPS-enforced OpenSSL, Node.js, and Python configurations +- SELinux enabled in enforcing mode for security compliance +- Latest development toolchain with: + - Go 1.24.3 (FIPS-validated) + - Node.js 20.19.0 (LTS, FIPS-configured) + - Terraform 1.11.5 + - Docker with latest components +- Integration with updated Coder modules for IDE support and productivity +- Systemd service management for proper service initialization +- Verified checksums for all downloaded software components + +## FIPS Compliance Details + +- Enforces the use of FIPS 140-2/140-3 validated cryptographic modules +- Restricts cryptographic algorithms to NIST-approved algorithms +- Configures OpenSSL in FIPS mode with proper validation +- Forces Node.js to use FIPS-compliant OpenSSL configuration +- Verifies integrity of all downloaded packages with SHA-256 checksums +- Maintains SELinux in enforcing mode for system integrity + +## Usage + +1. Create a new workspace using this template +2. The template will build a Red Hat compatible container using the devcontainer.json +3. Connect to your workspace with your preferred IDE (VS Code, JetBrains Gateway) + +## Customization + +The startup script runs your `~/personalize` file if it exists, allowing you to customize your environment further. +Your home directory under `/home/coder` is persisted as a Docker volume, preserving your settings between workspace restarts. + +## Parameters + +- **Devcontainer Repository**: Git repository containing the devcontainer.json +- **Devcontainer Directory**: Directory within the repository containing the devcontainer.json +- **Region**: Geographic region for hosting your workspace + +## Security Features + +- FIPS 140-2/140-3 compliance fully enabled and enforced +- Cryptographic module validation at startup +- SELinux set to enforcing mode for mandatory access control +- SHA-256 checksum verification for all downloads +- Restricted cryptographic algorithms to NIST-approved only +- Red Hat security updates and vulnerability patching +- Enterprise-grade security policies with proper audit logging + +## Enterprise Requirements + +This template is specifically designed for environments with Red Hat Enterprise Linux requirements, providing a compatible development environment that meets enterprise security and compliance standards while still enabling modern development workflows. + +## Known Issues + +- Some tools may require additional configuration to work with SELinux enabled +- If you encounter permissions issues, you may need to adjust SELinux contexts + +For troubleshooting or questions, please reach out to the DevOps team or the template maintainer. \ No newline at end of file diff --git a/dogfood/redhat-envbuilder/devcontainer.json b/dogfood/redhat-envbuilder/devcontainer.json new file mode 100644 index 0000000000000..b75489faa8c0c --- /dev/null +++ b/dogfood/redhat-envbuilder/devcontainer.json @@ -0,0 +1,8 @@ +{ + "name": "Red Hat Development Environment", + "build": { + "dockerfile": "Dockerfile" + }, + "features": {}, + "runArgs": ["--cap-add=SYS_PTRACE"] +} \ No newline at end of file diff --git a/dogfood/redhat-envbuilder/main.tf b/dogfood/redhat-envbuilder/main.tf new file mode 100644 index 0000000000000..f4035e18d75ff --- /dev/null +++ b/dogfood/redhat-envbuilder/main.tf @@ -0,0 +1,454 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 2.0" # Compatible with Coder v2.20.3 + } + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0.0" + } + envbuilder = { + source = "coder/envbuilder" + version = "~> 1.1.0" # Latest stable version + } + } +} + +locals { + // These are cluster service addresses mapped to Tailscale nodes. + // Ask #dogfood-admins for help. + // NOTE: keep these up to date with those in ../dogfood-envbuilder/main.tf! + docker_host = { + "" = "tcp://dogfood-ts-cdr-dev.tailscale.svc.cluster.local:2375" + "us-pittsburgh" = "tcp://dogfood-ts-cdr-dev.tailscale.svc.cluster.local:2375" + // For legacy reasons, this host is labelled `eu-helsinki` but it's + // actually in Germany now. + "eu-helsinki" = "tcp://katerose-fsn-cdr-dev.tailscale.svc.cluster.local:2375" + "ap-sydney" = "tcp://wolfgang-syd-cdr-dev.tailscale.svc.cluster.local:2375" + "sa-saopaulo" = "tcp://oberstein-sao-cdr-dev.tailscale.svc.cluster.local:2375" + "za-jnb" = "tcp://greenhill-jnb-cdr-dev.tailscale.svc.cluster.local:2375" + } + + envbuilder_repo = "ghcr.io/coder/envbuilder-preview" + container_name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + // Envbuilder clones repos to /workspaces by default. + repo_dir = "/workspaces/redhat-env" +} + +data "coder_parameter" "devcontainer_repo" { + type = "string" + name = "Devcontainer Repository" + default = "https://github.com/coder/coder" + description = "Repo containing the Red Hat devcontainer.json. This is only cloned once." + mutable = false +} + +data "coder_parameter" "devcontainer_dir" { + type = "string" + name = "Devcontainer Directory" + default = "dogfood/redhat-envbuilder/" + description = "Directory containing the Red Hat devcontainer.json relative to the repository root" + mutable = true +} + +data "coder_parameter" "region" { + type = "string" + name = "Region" + icon = "/emojis/1f30e.png" + default = "us-pittsburgh" + option { + icon = "/emojis/1f1fa-1f1f8.png" + name = "Pittsburgh" + value = "us-pittsburgh" + } + option { + icon = "/emojis/1f1e9-1f1ea.png" + name = "Falkenstein" + // For legacy reasons, this host is labelled `eu-helsinki` but it's + // actually in Germany now. + value = "eu-helsinki" + } + option { + icon = "/emojis/1f1e6-1f1fa.png" + name = "Sydney" + value = "ap-sydney" + } + option { + icon = "/emojis/1f1e7-1f1f7.png" + name = "São Paulo" + value = "sa-saopaulo" + } + option { + icon = "/emojis/1f1ff-1f1e6.png" + name = "Johannesburg" + value = "za-jnb" + } +} + +# This file is mounted as a Kubernetes secret on provisioner pods. +# It contains the required credentials for the envbuilder cache repo. +variable "envbuilder_cache_dockerconfigjson_path" { + type = string + sensitive = true +} + +provider "docker" { + host = lookup(local.docker_host, data.coder_parameter.region.value) + registry_auth { + address = "us-central1-docker.pkg.dev" + config_file = pathexpand(var.envbuilder_cache_dockerconfigjson_path) + } +} + +provider "coder" {} + +data "coder_external_auth" "github" { + id = "github" +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +module "slackme" { + source = "registry.coder.com/modules/slackme/coder" + version = "1.0.2" + agent_id = coder_agent.dev.id + auth_provider_id = "slack" +} + +module "dotfiles" { + source = "registry.coder.com/modules/dotfiles/coder" + version = "1.0.16" + agent_id = coder_agent.dev.id +} + +module "personalize" { + source = "registry.coder.com/modules/personalize/coder" + version = "1.0.3" + agent_id = coder_agent.dev.id +} + +module "code-server" { + source = "registry.coder.com/modules/code-server/coder" + version = "1.0.16" + agent_id = coder_agent.dev.id + folder = local.repo_dir + auto_install_extensions = true +} + +module "jetbrains_gateway" { + source = "registry.coder.com/modules/jetbrains-gateway/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id + agent_name = "dev" + folder = local.repo_dir + jetbrains_ides = ["GO", "WS"] + default = "GO" + latest = true +} + +module "filebrowser" { + source = "registry.coder.com/modules/filebrowser/coder" + version = "1.0.9" + agent_id = coder_agent.dev.id +} + +module "coder-login" { + source = "registry.coder.com/modules/coder-login/coder" + version = "1.0.15" + agent_id = coder_agent.dev.id +} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = local.repo_dir + env = { + OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, + } + startup_script_behavior = "blocking" + + metadata { + display_name = "CPU Usage" + key = "cpu_usage" + order = 0 + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "ram_usage" + order = 1 + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "CPU Usage (Host)" + key = "cpu_usage_host" + order = 2 + script = "coder stat cpu --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage (Host)" + key = "ram_usage_host" + order = 3 + script = "coder stat mem --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Swap Usage (Host)" + key = "swap_usage_host" + order = 4 + script = < /dev/null; then break; fi + sleep 1 + done + sudo chmod a+rw /var/run/docker.sock + + # Install additional Red Hat specific dependencies + sudo dnf install -y epel-release + sudo dnf install -y crypto-policies-scripts fipscheck openssl-fips + sudo dnf update -y + + # Verify and enforce FIPS compliance + # Check current FIPS status + echo "Current FIPS policy: $(sudo update-crypto-policies --show)" + + # Ensure FIPS mode is active + sudo update-crypto-policies --set FIPS + echo "Updated FIPS policy: $(sudo update-crypto-policies --show)" + + # Verify OpenSSL is running in FIPS mode + openssl md5 /etc/hosts 2>&1 | grep -q "disabled for FIPS" && echo "OpenSSL FIPS mode is active" || echo "WARNING: OpenSSL FIPS mode is NOT active" + + # Configure Node.js for FIPS compliance + echo 'export NODE_OPTIONS="$NODE_OPTIONS --openssl-config=/etc/crypto-policies/back-ends/openssl.config"' >> ~/.bashrc + + # Configure SELinux in enforcing mode for FIPS compliance + if command -v setenforce &>/dev/null; then + sudo setenforce 1 || echo "Unable to set SELinux to enforcing mode" + echo "SELinux status: $(getenforce)" + fi + + # Create symlinks for FIPS-validated crypto libraries if they don't exist + if [ ! -f /lib64/libcrypto.so.10.FIPS ]; then + sudo ln -sf /lib64/libcrypto.so.10 /lib64/libcrypto.so.10.FIPS || true + fi + EOT +} + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace.me.id}-home" + # Protect the volume from being deleted due to changes in attributes. + lifecycle { + ignore_changes = all + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + # This field becomes outdated if the workspace is renamed but can + # be useful for debugging or cleaning out dangling volumes. + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } +} + +resource "docker_volume" "workspaces" { + name = "coder-${data.coder_workspace.me.id}" + # Protect the volume from being deleted due to changes in attributes. + lifecycle { + ignore_changes = all + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + # This field becomes outdated if the workspace is renamed but can + # be useful for debugging or cleaning out dangling volumes. + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } +} + +# This file is mounted as a Kubernetes secret on provisioner pods. +# It contains the required credentials for the envbuilder cache repo. +data "local_sensitive_file" "envbuilder_cache_dockerconfigjson" { + filename = var.envbuilder_cache_dockerconfigjson_path +} + +data "docker_registry_image" "envbuilder" { + name = "${local.envbuilder_repo}:latest" +} + +resource "docker_image" "envbuilder" { + name = "${local.envbuilder_repo}@${data.docker_registry_image.envbuilder.sha256_digest}" + pull_triggers = [data.docker_registry_image.envbuilder.sha256_digest] + keep_locally = true +} + +locals { + cache_repo = "us-central1-docker.pkg.dev/coder-dogfood-v2/envbuilder-cache/redhat-envbuilder" + envbuilder_env = { + "CODER_AGENT_TOKEN" : coder_agent.dev.token, + "CODER_AGENT_URL" : data.coder_workspace.me.access_url, + "ENVBUILDER_GIT_USERNAME" : data.coder_external_auth.github.access_token, + # "ENVBUILDER_GIT_URL" : data.coder_parameter.devcontainer_repo.value, # The provider sets this via the `git_url` property. + "ENVBUILDER_DEVCONTAINER_DIR" : data.coder_parameter.devcontainer_dir.value, + "ENVBUILDER_INIT_SCRIPT" : coder_agent.dev.init_script, + "ENVBUILDER_FALLBACK_IMAGE" : "registry.access.redhat.com/ubi9/ubi:latest", # This image runs if builds fail + "ENVBUILDER_PUSH_IMAGE" : "true", # Push the image to the remote cache + # "ENVBUILDER_CACHE_REPO" : local.cache_repo, # The provider sets this via the `cache_repo` property. + "ENVBUILDER_DOCKER_CONFIG_BASE64" : data.local_sensitive_file.envbuilder_cache_dockerconfigjson.content_base64, + "USE_CAP_NET_ADMIN" : "true", + # Set git commit details correctly + "GIT_AUTHOR_NAME" : coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name), + "GIT_AUTHOR_EMAIL" : data.coder_workspace_owner.me.email, + "GIT_COMMITTER_NAME" : coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name), + "GIT_COMMITTER_EMAIL" : data.coder_workspace_owner.me.email, + } +} + +# Check for the presence of a prebuilt image in the cache repo +# that we can use instead. +resource "envbuilder_cached_image" "cached" { + count = data.coder_workspace.me.start_count + builder_image = docker_image.envbuilder.name + git_url = data.coder_parameter.devcontainer_repo.value + cache_repo = local.cache_repo + extra_env = local.envbuilder_env +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = envbuilder_cached_image.cached.0.image + name = local.container_name + # Hostname makes the shell more user friendly: coder@my-workspace:~$ + hostname = data.coder_workspace.me.name + # CPU limits are unnecessary since Docker will load balance automatically + memory = 32768 + runtime = "sysbox-runc" + # Use environment computed from the provider + env = envbuilder_cached_image.cached.0.env + host { + host = "host.docker.internal" + ip = "host-gateway" + } + volumes { + container_path = "/home/coder/" + volume_name = docker_volume.home_volume.name + read_only = false + } + volumes { + container_path = local.repo_dir + volume_name = docker_volume.workspaces.name + read_only = false + } + capabilities { + add = ["CAP_NET_ADMIN", "CAP_SYS_NICE"] + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } +} + +resource "coder_metadata" "container_info" { + count = data.coder_workspace.me.start_count + resource_id = coder_agent.dev.id + item { + key = "memory" + value = docker_container.workspace[0].memory + } + item { + key = "runtime" + value = docker_container.workspace[0].runtime + } + item { + key = "region" + value = data.coder_parameter.region.option[index(data.coder_parameter.region.option.*.value, data.coder_parameter.region.value)].name + } +} \ No newline at end of file