diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..9e3aba3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + python3 \ + python3-pip \ + curl \ + git \ + sudo \ + && rm -rf /var/lib/apt/lists/* +RUN curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | sudo -E bash +RUN apt install task -y +RUN useradd -m -s /bin/bash vscode && \ + echo "vscode ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.36.4/pack-v0.36.4-linux.tgz" \ + | tar -C /usr/local/bin/ --no-same-owner -xzv pack + +USER vscode \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7527239 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,36 @@ +{ + "name": "CNB java", + "build": { + "dockerfile": "Dockerfile" + }, + "containerEnv": { + "DOCKER_API_VERSION": "1.41" + }, + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "latest", + "enableNonRootChildContainerUsage": true + }, + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": false, + "username": "vscode" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-kubernetes-tools.vscode-kubernetes-tools", + "redhat.vscode-yaml", + "ms-azuretools.vscode-docker", + "pkief.material-icon-theme", + "ms-python.python", + "tamasfe.even-better-toml" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + } + } + }, + "remoteUser": "vscode", + "postCreateCommand": "sudo chmod 666 /var/run/docker.sock && pip3 install rich && sudo chown vscode:vscode /var/run/docker.sock || true" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 215b506..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "1. Prepare Builder", - "type": "shell", - "command": "cd build-image && docker build -t taha/wolfi-build-image:latest . && cd ../2-builder && pack builder create taha/wolfi-fips-builder:latest --config builder.toml" - }, - { - "label": "2. Prepare Run Image", - "type": "shell", - "command": "cd run-image && docker build -t taha/wolfi-run-image:latest ." - }, - { - "label": "3. Build App Gradle", - "type": "shell", - "command": "pack build taha/gradle-fips-app --path 4-gradle-app --builder taha/wolfi-fips-builder:latest --env BP_JVM_TYPE=JRE --publish=false --clear-cache" - }, - { - "label": "3. Build App Maven", - "type": "shell", - "command": "pack build taha/maven-fips-app --path 3-sample-app --builder taha/wolfi-fips-builder:latest --env BP_JVM_TYPE=JRE --publish=false --clear-cache" - }, - { - "label": "4. Run App", - "type": "shell", - "command": "docker rm -f fips-app-container || true && docker run -d -p 8080:8080 --name fips-app-container taha/maven-fips-app" - } - ] -} \ No newline at end of file diff --git a/2-builder/builder.toml b/2-builder/builder.toml index 6f58fb8..4c5431c 100644 --- a/2-builder/builder.toml +++ b/2-builder/builder.toml @@ -1,141 +1,210 @@ -description = "My Custom Wolfi FIPS Builder" - +description = "Custom Wolfi FIPS Builder" [stack] -id = "io.buildpacks.stacks.jammy" -build-image = "taha/wolfi-build-image:latest" -run-image = "taha/wolfi-run-image:latest" + id = "taha/wolfi-build-image" + build-image = "taha/wolfi-build-image:latest" + run-image = "taha/wolfi-run-image:latest" + [[targets]] -os = "linux" -arch = "amd64" - + os = "linux" + arch = "amd64" [[targets]] -os = "linux" -arch = "arm64" - + os = "linux" + arch = "arm64" [[buildpacks]] -uri = "../fips-java-shim" + uri = "docker://taha/fips-java:1.0.0" [[buildpacks]] -uri = "docker://paketobuildpacks/ca-certificates:latest" + uri = "docker://paketobuildpacks/ca-certificates:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/syft:latest" + uri = "docker://paketobuildpacks/syft:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/environment-variables:latest" + uri = "docker://paketobuildpacks/environment-variables:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/image-labels:latest" + uri = "docker://paketobuildpacks/image-labels:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/procfile:latest" + uri = "docker://paketobuildpacks/procfile:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/jattach:latest" + uri = "docker://paketobuildpacks/jattach:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/java-memory-assistant:latest" + uri = "docker://paketobuildpacks/java-memory-assistant:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/datadog:latest" + uri = "docker://paketobuildpacks/datadog:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/azure-application-insights:latest" + uri = "docker://paketobuildpacks/azure-application-insights:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/google-stackdriver:latest" + uri = "docker://paketobuildpacks/google-stackdriver:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/maven:latest" + uri = "docker://paketobuildpacks/maven:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/gradle:latest" + uri = "docker://paketobuildpacks/gradle:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/sbt:latest" + uri = "docker://paketobuildpacks/sbt:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/executable-jar:latest" + uri = "docker://paketobuildpacks/executable-jar:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/spring-boot:latest" + uri = "docker://paketobuildpacks/spring-boot:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/apache-tomcat:latest" + uri = "docker://paketobuildpacks/apache-tomcat:latest" [[buildpacks]] -uri = "docker://paketobuildpacks/dist-zip:latest" + uri = "docker://paketobuildpacks/dist-zip:latest" [[order]] - [[order.group]] id = "paketo-buildpacks/ca-certificates" optional = true - [[order.group]] id = "taha/fips-java" - [[order.group]] id = "paketo-buildpacks/syft" optional = true - - [[order.group]] - id = "paketo-buildpacks/gradle" - optional = true - [[order.group]] id = "paketo-buildpacks/maven" - optional = true - [[order.group]] - id = "paketo-buildpacks/sbt" + id = "paketo-buildpacks/executable-jar" optional = true - [[order.group]] - id = "paketo-buildpacks/executable-jar" + id = "paketo-buildpacks/spring-boot" optional = true - [[order.group]] id = "paketo-buildpacks/apache-tomcat" optional = true - [[order.group]] id = "paketo-buildpacks/dist-zip" optional = true + [[order.group]] + id = "paketo-buildpacks/procfile" + optional = true + [[order.group]] + id = "paketo-buildpacks/jattach" + optional = true + [[order.group]] + id = "paketo-buildpacks/azure-application-insights" + optional = true + [[order.group]] + id = "paketo-buildpacks/google-stackdriver" + optional = true + [[order.group]] + id = "paketo-buildpacks/datadog" + optional = true + [[order.group]] + id = "paketo-buildpacks/java-memory-assistant" + optional = true + [[order.group]] + id = "paketo-buildpacks/environment-variables" + optional = true + [[order.group]] + id = "paketo-buildpacks/image-labels" + optional = true +[[order]] + [[order.group]] + id = "paketo-buildpacks/ca-certificates" + optional = true + [[order.group]] + id = "taha/fips-java" + [[order.group]] + id = "paketo-buildpacks/syft" + optional = true + [[order.group]] + id = "paketo-buildpacks/gradle" + [[order.group]] + id = "paketo-buildpacks/executable-jar" + optional = true [[order.group]] id = "paketo-buildpacks/spring-boot" optional = true - + [[order.group]] + id = "paketo-buildpacks/apache-tomcat" + optional = true + [[order.group]] + id = "paketo-buildpacks/dist-zip" + optional = true [[order.group]] id = "paketo-buildpacks/procfile" optional = true - [[order.group]] id = "paketo-buildpacks/jattach" optional = true - [[order.group]] id = "paketo-buildpacks/azure-application-insights" optional = true - [[order.group]] id = "paketo-buildpacks/google-stackdriver" optional = true - [[order.group]] id = "paketo-buildpacks/datadog" optional = true - [[order.group]] id = "paketo-buildpacks/java-memory-assistant" optional = true - [[order.group]] id = "paketo-buildpacks/environment-variables" optional = true - [[order.group]] id = "paketo-buildpacks/image-labels" + optional = true + +[[order]] + [[order.group]] + id = "paketo-buildpacks/ca-certificates" + optional = true + [[order.group]] + id = "taha/fips-java" + [[order.group]] + id = "paketo-buildpacks/syft" + optional = true + [[order.group]] + id = "paketo-buildpacks/sbt" + [[order.group]] + id = "paketo-buildpacks/executable-jar" + optional = true + [[order.group]] + id = "paketo-buildpacks/spring-boot" + optional = true + +[[order]] + [[order.group]] + id = "paketo-buildpacks/ca-certificates" + optional = true + [[order.group]] + id = "taha/fips-java" + [[order.group]] + id = "paketo-buildpacks/syft" + optional = true + [[order.group]] + id = "paketo-buildpacks/executable-jar" + [[order.group]] + id = "paketo-buildpacks/spring-boot" + optional = true + [[order.group]] + id = "paketo-buildpacks/jattach" + optional = true + [[order.group]] + id = "paketo-buildpacks/java-memory-assistant" + optional = true + [[order.group]] + id = "paketo-buildpacks/datadog" + optional = true + [[order.group]] + id = "paketo-buildpacks/azure-application-insights" + optional = true + [[order.group]] + id = "paketo-buildpacks/google-stackdriver" optional = true \ No newline at end of file diff --git a/4-gradle-app/src/main/java/com/taha/wolfidemo/DemoApplication.java b/4-gradle-app/src/main/java/com/taha/wolfidemo/DemoApplication.java index 8d10897..8758241 100644 --- a/4-gradle-app/src/main/java/com/taha/wolfidemo/DemoApplication.java +++ b/4-gradle-app/src/main/java/com/taha/wolfidemo/DemoApplication.java @@ -44,7 +44,7 @@ public String testFipsConstraint() { // FipsUnapprovedOperationError or specific FIPS messages indicate success if (errorType.contains("FipsUnapprovedOperationError") || (message != null && message.toLowerCase().contains("approved only mode"))) { - return "SUCCESS: FIPS is strictly enforced. System blocked insecure RSA-1024 key generation. Error Type: " + errorType; + return " just change SUCCESS: FIPS is strictly enforced. System blocked insecure RSA-1024 key generation. Error Type: " + errorType; } return "TERMINATED: An unexpected error occurred: " + errorType + " - " + message; diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..177cac4 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,30 @@ +version: '3' +env: + DOCKER_API_VERSION: "1.50" + PACK_VOLUME_KEY: "taha-fips-cache-key" +includes: + img: taskfile/image.yml + builder: taskfile/builder.yml + app: taskfile/app.yml + util: taskfile/util.yml + bp: taskfile/buildpack.yml + + +vars: + BUILD_IMAGE: taha/wolfi-build-image:latest + RUN_IMAGE: taha/wolfi-run-image:latest + BUILDER_NAME: taha/wolfi-fips-builder:latest + BUILDER_CONFIG: 2-builder/builder.toml + +tasks: + default: + cmds: + - task: all + + all: + desc: Build everything from scratch + cmds: + - task: img:build-img + - task: img:run-img + - task: bp:package + - task: builder:create \ No newline at end of file diff --git a/build-image/Dockerfile b/build-image/Dockerfile index 354ca47..bf39485 100644 --- a/build-image/Dockerfile +++ b/build-image/Dockerfile @@ -15,7 +15,12 @@ FROM chainguard/wolfi-base@sha256:9925d3017788558fa8f27e8bb160b791e56202b60c91fb USER root RUN apk update && \ - apk add --no-cache bash gzip curl jq ca-certificates shadow + apk add --no-cache bash gzip curl jq ca-certificates shadow python3 py3-pip + + +RUN pip install --no-cache-dir rich --break-system-packages + +RUN mkdir -p /opt/jdks /opt/jres RUN mkdir -p /opt/jdks /opt/jres @@ -37,7 +42,7 @@ ENV CNB_GROUP_ID=1001 RUN groupadd -g ${CNB_GROUP_ID} cnb && \ useradd -u ${CNB_USER_ID} -g ${CNB_GROUP_ID} -s /bin/bash -m cnb -LABEL io.buildpacks.stack.id="io.buildpacks.stacks.jammy" +LABEL io.buildpacks.stack.id="taha/wolfi-build-image" RUN chown -R ${CNB_USER_ID}:${CNB_GROUP_ID} /opt/jdks /opt/jres && \ chmod -R 755 /opt/jdks /opt/jres diff --git a/fips-java-shim/bin/build b/fips-java-shim/bin/build index 5766d82..a1b8011 100755 --- a/fips-java-shim/bin/build +++ b/fips-java-shim/bin/build @@ -1,69 +1,225 @@ -#!/usr/bin/env bash -set -e - -layers_dir="$1" -plan_path="$3" - -VERSION="" - -if [ -n "$BP_JVM_VERSION" ]; then - VERSION="$BP_JVM_VERSION" -elif [ -f "$plan_path" ]; then - VERSION=$(grep -E 'version\s*=\s*"[0-9.]+"' "$plan_path" | head -n1 | grep -oE "[0-9.]+" | head -n1 || echo "") -fi - -if [ -z "$VERSION" ]; then - if [ -f "pom.xml" ]; then - VERSION=$(grep -E "<(java\.version|maven\.compiler\.source)>[0-9.]+" pom.xml | head -n1 | grep -oE "[0-9.]+" | head -n1 || echo "") - elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then - VERSION=$(grep -E "JavaLanguageVersion\.of\(|sourceCompatibility|targetCompatibility" build.gradle build.gradle.kts 2>/dev/null | head -n1 | grep -oE "[0-9.]+" | head -n1 || echo "") - fi -fi - -VERSION="${VERSION:-21}" -[[ "$VERSION" == "1.8"* ]] && VERSION="8" -VERSION=$(echo "$VERSION" | cut -d. -f1) - -echo "---> FIPS Java Shim: Using Java $VERSION" - -if [ ! -d "/opt/jdks/$VERSION" ]; then - VERSION="21" - echo "---> Fallback: Using Java $VERSION" -fi - -jdk_layer="${layers_dir}/jdk" -jre_layer="${layers_dir}/jre" - -mkdir -p "${jdk_layer}/env.build" -echo -e "[types]\nlaunch = false\nbuild = true\ncache = true" > "${jdk_layer}.toml" -echo -n "/opt/jdks/${VERSION}" > "${jdk_layer}/env.build/JAVA_HOME" -echo -n "/opt/jdks/${VERSION}/bin" > "${jdk_layer}/env.build/PATH.prepend" -echo -n ":" > "${jdk_layer}/env.build/PATH.delim" - -mkdir -p "${jre_layer}/env.launch" -echo -e "[types]\nlaunch = true\nbuild = false\ncache = true" > "${jre_layer}.toml" -cp -a /opt/jres/${VERSION}/* "${jre_layer}/" -echo -n "${jre_layer}" > "${jre_layer}/env.launch/JAVA_HOME" -echo -n "${jre_layer}/bin" > "${jre_layer}/env.launch/PATH.prepend" -echo -n ":" > "${jre_layer}/env.launch/PATH.delim" - -TOTAL_MEMORY=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || echo "1073741824") -MAX_HEAP=$(( (TOTAL_MEMORY * 75) / 100 / 1024 / 1024 )) -FIPS_BASE="-Dorg.bouncycastle.fips.approved_only=true -Dkeystore.type=BCFKS -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStorePassword=changeit" - -if [ "$VERSION" == "8" ]; then - JDK_TRUST="/opt/jdks/${VERSION}/jre/lib/security/cacerts" - JDK_BOOT="/opt/jdks/${VERSION}/jre/lib/ext/bc-fips.jar:/opt/jdks/${VERSION}/jre/lib/ext/bcutil-fips.jar:/opt/jdks/${VERSION}/jre/lib/ext/bctls-fips.jar" - JRE_TRUST="${jre_layer}/lib/security/cacerts" - JRE_BOOT="${jre_layer}/lib/ext/bc-fips.jar:${jre_layer}/lib/ext/bcutil-fips.jar:${jre_layer}/lib/ext/bctls-fips.jar" -else - JDK_TRUST="/opt/jdks/${VERSION}/lib/security/cacerts" - JDK_BOOT="/opt/jdks/${VERSION}/lib/bc-fips.jar:/opt/jdks/${VERSION}/lib/bcutil-fips.jar:/opt/jdks/${VERSION}/lib/bctls-fips.jar" - JRE_TRUST="${jre_layer}/lib/security/cacerts" - JRE_BOOT="${jre_layer}/lib/bc-fips.jar:${jre_layer}/lib/bcutil-fips.jar:${jre_layer}/lib/bctls-fips.jar" -fi - -echo -n "${FIPS_BASE} -Djavax.net.ssl.trustStore=${JDK_TRUST} -Xbootclasspath/a:${JDK_BOOT}" > "${jdk_layer}/env.build/JAVA_TOOL_OPTIONS.override" -echo -n "${FIPS_BASE} -Djavax.net.ssl.trustStore=${JRE_TRUST} -Xbootclasspath/a:${JRE_BOOT} -XX:+ExitOnOutOfMemoryError -Xmx${MAX_HEAP}m" > "${jre_layer}/env.launch/JAVA_TOOL_OPTIONS.override" - -exit 0 \ No newline at end of file +#!/usr/bin/env python3 +import os +import sys +import re +import hashlib +import shutil +import urllib.request +from pathlib import Path + +# ============================================================================== +# [0] UTILITIES & CONFIGURATION +# ============================================================================== +G, Y, C, R, RESET, BOLD = "\033[0;32m", "\033[0;33m", "\033[0;36m", "\033[0;31m", "\033[0m", "\033[1m" +TICK, BOX, ARROW = "✓", "❒", "↳" + +def log_header(title): + print(f"\n{BOLD}{C}===> {title}{RESET}") + +def log_step(layer, action, detail=""): + color = G if action in ["REUSE", "HIT", "READY"] else Y + symbol = TICK if action in ["REUSE", "HIT", "READY"] else BOX + print(f" {BOLD}{layer:<10}{RESET} : {color}{symbol} {action:<10}{RESET} {R}{ARROW}{RESET} {detail}") + +def get_fingerprint(directory: Path): + if not directory.exists(): return "MISSING" + hasher = hashlib.sha256() + for p in sorted(directory.glob('bin/*')): + if p.is_file(): + hasher.update(f"{p.name}{p.stat().st_size}".encode()) + return hasher.hexdigest()[:12] + +def download_jma(target_dir: Path): + jma_url = "https://github.com/SAP-archive/java-memory-assistant/releases/download/0.5.0/java-memory-assistant-0.5.0.jar" + jma_jar = target_dir / "java-memory-assistant.jar" + if not jma_jar.exists(): + urllib.request.urlretrieve(jma_url, jma_jar) + return jma_jar + +# ============================================================================== +# [1] GLOBAL PARAMETERS +# ============================================================================== +def main(): + if len(sys.argv) < 3: sys.exit(1) + layers_dir = Path(sys.argv[1]) + plan_path = Path(sys.argv[3]) if len(sys.argv) > 3 else Path("") + + log_header("FIPS Java Shim v4.18 (FIPS Env Restored)") + + version = "21" + if plan_path.exists(): + match = re.search(r'version\s*=\s*"([0-9.]+)"', plan_path.read_text()) + if match: version = match.group(1) + + jvm_type = os.getenv("BP_JVM_TYPE", "JRE").upper() + headroom = int(os.getenv("BPL_JVM_HEAD_ROOM", "25")) + ram_percentage = float(100 - headroom) + fips_base = "-Dorg.bouncycastle.fips.approved_only=true -Dkeystore.type=BCFKS -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStorePassword=changeit" + + # ========================================================================== + # [LAYER 1] JDK LAYER (DIRECTORY: jdk/ + METADATA: jdk.toml) + # ========================================================================== + jdk_layer = layers_dir / "jdk" + jdk_toml = layers_dir / "jdk.toml" + jdk_src = Path(f"/opt/jdks/{version}") + jdk_fp = get_fingerprint(jdk_src) + + if jdk_toml.exists() and jdk_fp in jdk_toml.read_text() and jdk_layer.exists(): + log_step("JDK", "REUSE", f"Fingerprint: {jdk_fp}") + else: + log_step("JDK", "UPDATE", f"Source: {jdk_src}") + if jdk_layer.exists(): shutil.rmtree(jdk_layer) + jdk_layer.mkdir(parents=True) + + jdk_toml.write_text(f""" + [types] + build = true + cache = true + launch = false + [metadata] + fingerprint = "{jdk_fp}" + """.strip()) + + env_build = jdk_layer / "env.build" + env_build.mkdir(exist_ok=True) + (env_build / "JAVA_HOME").write_text(str(jdk_src)) + (env_build / "PATH.prepend").write_text(f"{jdk_src}/bin") + (env_build / "PATH.delim").write_text(":") + + jdk_trust_dir = "jre/lib" if version == "8" else "lib" + jdk_trust = jdk_src / jdk_trust_dir / "security/cacerts" + + if version == "8": + jdk_boot = ( + f"{jdk_src}/{jdk_trust_dir}/ext/bc-fips.jar:" + f"{jdk_src}/{jdk_trust_dir}/ext/bcutil-fips.jar:" + f"{jdk_src}/{jdk_trust_dir}/ext/bctls-fips.jar" + ) + else: + jdk_boot = ( + f"{jdk_src}/lib/bc-fips.jar:" + f"{jdk_src}/lib/bcutil-fips.jar:" + f"{jdk_src}/lib/bctls-fips.jar" + ) + + java_opts = ( + f"{fips_base} " + f"-Djavax.net.ssl.trustStore={jdk_trust} " + f"-Xbootclasspath/a:{jdk_boot}" + ) + + (env_build / "JAVA_TOOL_OPTIONS.override").write_text(java_opts) + + + # ========================================================================== + # [LAYER 2] JRE LAYER (DIRECTORY: jre/ + METADATA: jre.toml) + # ========================================================================== + jre_layer = layers_dir / "jre" + jre_toml = layers_dir / "jre.toml" + + jre_src = jdk_src if jvm_type == "JDK" else Path(f"/opt/jres/{version}") + jre_fp = get_fingerprint(jre_src) + + use_cache = ( + jre_toml.exists() and + jre_layer.exists() and + jre_fp in jre_toml.read_text() + ) + + if use_cache: + log_step("JRE", "REUSE", "Restored from cache") + else: + log_step("JRE", "SYNC", f"Syncing {jre_src}") + if jre_layer.exists(): + shutil.rmtree(jre_layer) + shutil.copytree(jre_src, jre_layer) + + jre_toml.write_text(f""" + [types] + launch = true + cache = true + + [metadata] + fingerprint = "{jre_fp}" + ram_percentage = "{ram_percentage}%" + """.strip()) + + env_launch = jre_layer / "env.launch" + env_launch.mkdir(exist_ok=True) + + (env_launch / "JAVA_HOME").write_text(str(jre_layer)) + (env_launch / "PATH.prepend").write_text(str(jre_layer / "bin")) + (env_launch / "PATH.delim").write_text(":") + + if version == "8": + if jvm_type == "JRE": + jre_trust_dir = "lib" + else: + jre_trust_dir = "jre/lib" + else: + jre_trust_dir = "lib" + + jre_trust = jre_layer / jre_trust_dir / "security/cacerts" + + if version == "8": + base_path = f"{jre_layer}/{jre_trust_dir}/ext" + else: + base_path = f"{jre_layer}/lib" + + jars = ["bc-fips.jar", "bcutil-fips.jar", "bctls-fips.jar"] + jre_boot = ":".join(f"{base_path}/{jar}" for jar in jars) + + jma_opts = "" + if os.getenv("BP_JMA_ENABLED", "false").lower() == "true": + jma_jar = download_jma(jre_layer / "lib") + jma_opts = f"-javaagent:{jma_jar} -Djma.check_interval=5s -Djma.log_level=ERROR -Djma.max_frequency=1/1m -Djma.heap_dump_folder=/tmp -Djma.thresholds.heap=80% " + if version != "8": + jma_opts += "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED " + log_step("JMA", "READY", "Java Memory Assistant Configured") + + ram_opts = f"-XX:+UseContainerSupport -XX:MaxRAMPercentage={ram_percentage}" if version == "8" else f"-XX:MaxRAMPercentage={ram_percentage}" + + runtime_opts = ( + f"{fips_base} " + f"-Djavax.net.ssl.trustStore={jre_trust} " + f"-Xbootclasspath/a:{jre_boot} " + f"{jma_opts}" + f"-XX:+ExitOnOutOfMemoryError " + f"{ram_opts}" + ) + + (env_launch / "JAVA_TOOL_OPTIONS.append").write_text(runtime_opts.strip()) + (env_launch / "JAVA_TOOL_OPTIONS.delim").write_text(" ") + + log_step("SUMMARY", "READY", f"RAM%: {ram_percentage}% | FIPS: Enabled | Distroless: True") + print(f"{G}{BOLD}===> Buildpack successfully finished{RESET}\n") + + launch_toml = layers_dir / "launch.toml" + java_binary = jre_layer / "bin" / "java" + + launch_toml.write_text(f''' +[[processes]] +type = "check-java" +command = "{java_binary}" +args = ["-version"] +working-dir = "/workspace" +direct = true +''') + # Logic to handle Vendor SBOM + vendor_sbom = jre_src / "java.cdx.json" + + if vendor_sbom.exists(): + # Define destination path based on CNB Spec + # Path: /sbom/// + sbom_dir = jre_layer / "sbom" / "launch" / "jre" + sbom_dir.mkdir(parents=True, exist_ok=True) + + # Copy the file + shutil.copy2(vendor_sbom, sbom_dir / "java.cdx.json") + + # Log discovery + log_step("JRE", "SBOM", f"Detected and linked: {vendor_sbom.name}") + else: + log_step("JRE", "SBOM", "No vendor SBOM found") +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/fips-java-shim/bin/detect b/fips-java-shim/bin/detect index 7204fe0..29c987b 100755 --- a/fips-java-shim/bin/detect +++ b/fips-java-shim/bin/detect @@ -1,14 +1,99 @@ -#!/usr/bin/env bash -set -e +#!/usr/bin/env python3 +import os +import sys +import re +from pathlib import Path -PLAN="$2" +# ===================================================================== +# 0. UI & LOGGING SETUP +# ===================================================================== +try: + from rich.console import Console + from rich.panel import Panel +except ImportError: + class Console: + def print(self, *args, **kwargs): print(*args) + class Panel: + @staticmethod + def fit(text, title=""): return f"--- {title} ---\n{text}" -cat > "${PLAN}" <([0-9.]+) 8, 17.0.1 -> 17 + clean_version = "8" if "1.8" in version else version.split('.')[0] + + # Validate that we actually have this version in the Builder + jdk_path = Path(f"/opt/jdks/{clean_version}") + if not jdk_path.exists(): + console.print(f"[bold red]ERROR:[/bold red] Java {clean_version} requested but not found in Builder!") + # Force fallback to 21 to prevent build failure if possible + clean_version = "21" + source += " (Fallback to 21 due to missing version)" + + console.print(Panel.fit( + f"Detected Java: [bold yellow]{clean_version}[/bold yellow]\nSource: [bold blue]{source}[/bold blue]", + title="FIPS Java Shim: DETECT" + )) + + # ================================================================= + # 5. WRITE BUILD PLAN + # ================================================================= + # This writes to $2 (plan.toml) so the BUILD phase can read it via $3 + with open(plan_path, "w") as f: + f.write('[[provides]]\nname = "jdk"\n') + f.write('[[provides]]\nname = "jre"\n') + f.write('[[requires]]\nname = "jdk"\n[requires.metadata]\nversion = "' + clean_version + '"\n') + f.write('[[requires]]\nname = "jre"\n[requires.metadata]\nversion = "' + clean_version + '"\n') + + sys.exit(0) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/fips-java-shim/buildpack.toml b/fips-java-shim/buildpack.toml index b65f8f0..5077712 100755 --- a/fips-java-shim/buildpack.toml +++ b/fips-java-shim/buildpack.toml @@ -1,10 +1,50 @@ -api = "0.7" +api = "0.12" + + +[[stacks]] + id = "*" + + +[metadata] + [[metadata.configurations]] + name = "BP_JVM_VERSION" + description = "The Java version to install (8, 11, 17, 21, 25)" + default = "21" + build = true + + [[metadata.configurations]] + name = "BP_JVM_TYPE" + description = "The JVM type to provide at runtime (JDK or JRE)" + default = "JRE" + build = true + + [[metadata.configurations]] + name = "BP_JMA_ENABLED" + description = "Enable downloading Java Memory Assistant agent during build" + default = "false" + build = true + + [[metadata.configurations]] + name = "BPL_JVM_HEAD_ROOM" + description = "The percentage of memory to leave as headroom (0-100)" + default = "25" + launch = true [buildpack] id = "taha/fips-java" version = "1.0.0" name = "FIPS Java Shim" + +[[targets]] +os = "linux" +arch = "amd64" + +[[targets]] +os = "linux" +arch = "arm64" + + [[provides]] name = "jdk" diff --git a/layers.py b/layers.py new file mode 100755 index 0000000..b1e1b32 --- /dev/null +++ b/layers.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import sys +import subprocess +import json +import csv + +def main(): + if len(sys.argv) < 3: + print("Usage: ./analyze.py ") + sys.exit(1) + + image_name, csv_path = sys.argv[1], sys.argv[2] + + try: + subprocess.run(f"dive {image_name} --json image_report.json", shell=True, check=True, capture_output=True) + + with open("image_report.json", 'r') as f: + data = json.load(f) + + layers = [] + for layer in data.get("layer", []): + size_mb = layer.get("sizeBytes", 0) / (1024 * 1024) + cmd = layer.get("command", "").replace("\n", " ") + layers.append({"size": size_mb, "cmd": cmd, "sha": layer.get("digestId")}) + + with open(csv_path, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(["LAYER_TYPE", "SIZE_MB", "SHA256_HASH", "COMMAND"]) + + for l in layers: + if "taha/fips-java" in l['cmd']: + layer_type = "JRE/FIPS Layer" + elif "Application Slice" in l['cmd']: + layer_type = "App Code Layer" + elif "paketo-buildpacks" in l['cmd']: + layer_type = "Buildpack Helper" + else: + layer_type = "OS/Base Layer" + + writer.writerow([layer_type, f"{l['size']:.6f} MB", l['sha'], l['cmd']]) + + print(f"Success! Detailed report: {csv_path}") + + print("\n--- Top 5 Largest Layers ---") + top_layers = sorted(layers, key=lambda x: x['size'], reverse=True)[:5] + for i, l in enumerate(top_layers, 1): + print(f"{i}. {l['size']:.6f} MB | {l['sha'][:12]}... | {l['cmd'][:40]}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/run-image/Dockerfile b/run-image/Dockerfile index bc1f25f..1756e54 100644 --- a/run-image/Dockerfile +++ b/run-image/Dockerfile @@ -35,7 +35,7 @@ ENV CNB_USER_ID=1001 ENV CNB_GROUP_ID=1001 ENV LANG=C.UTF-8 TZ=UTC -LABEL io.buildpacks.stack.id="io.buildpacks.stacks.jammy" +LABEL io.buildpacks.stack.id="taha/wolfi-build-image" USER 1001 WORKDIR /home/java diff --git a/run-image/Dockerfile.run b/run-image/Dockerfile.run new file mode 100644 index 0000000..5de80c9 --- /dev/null +++ b/run-image/Dockerfile.run @@ -0,0 +1,8 @@ +FROM cgr.dev/chainguard/wolfi-base:latest + +RUN addgroup -S cnb --gid 1000 && \ + adduser -S cnb -G cnb --uid 1000 + +LABEL io.buildpacks.stack.id="taha/wolfi-build-image" + +USER 1000 \ No newline at end of file diff --git a/taskfile/app.yml b/taskfile/app.yml new file mode 100644 index 0000000..1af72b0 --- /dev/null +++ b/taskfile/app.yml @@ -0,0 +1,37 @@ +version: '3' + +vars: + BUILDER: taha/wolfi-fips-builder:latest + MAVEN_APP_IMG: taha/maven-fips-app:latest + MAVEN_PATH: 3-sample-app + GRADLE_APP_IMG: taha/gradle-fips-app:latest + GRADLE_PATH: 4-gradle-app + # only for github codespace Docker API 1.43 + LIFECYCLE_IMG: buildpacksio/lifecycle:0.17.2 + +tasks: + build-maven: + desc: Build the Maven application + cmds: + - pack build {{.MAVEN_APP_IMG}} --path {{.MAVEN_PATH}} --builder {{.BUILDER}} --env BP_JVM_TYPE=JRE --lifecycle-image {{.LIFECYCLE_IMG}} + + run-maven: + desc: Run the Maven application + cmds: + - docker run -it --rm -m 512m {{.MAVEN_APP_IMG}} + + build-gradle: + desc: Build the Gradle application + cmds: + - pack build {{.GRADLE_APP_IMG}} --path {{.GRADLE_PATH}} --builder {{.BUILDER}} --env BP_JVM_TYPE=JRE --lifecycle-image {{.LIFECYCLE_IMG}} + + run-gradle: + desc: Run the Gradle application + cmds: + - docker run -it --rm -m 512m {{.GRADLE_APP_IMG}} + + build-all: + desc: Build both Maven and Gradle apps + cmds: + - task: build-maven + - task: build-gradle \ No newline at end of file diff --git a/taskfile/builder.yml b/taskfile/builder.yml new file mode 100644 index 0000000..949764e --- /dev/null +++ b/taskfile/builder.yml @@ -0,0 +1,12 @@ +version: '3' + +tasks: + create: + desc: Create the final Cloud Native Builder + cmds: + - pack builder create {{.BUILDER_NAME}} --config {{.BUILDER_CONFIG}} + + inspect: + desc: Inspect the final builder + cmds: + - pack builder inspect {{.BUILDER_NAME}} \ No newline at end of file diff --git a/taskfile/buildpack.yml b/taskfile/buildpack.yml new file mode 100644 index 0000000..4589304 --- /dev/null +++ b/taskfile/buildpack.yml @@ -0,0 +1,22 @@ +version: '3' + +vars: + BP_NAME: taha/fips-java + BP_VERSION: 1.0.0 + BP_PATH: ./fips-java-shim + +tasks: + package: + desc: Package the FIPS Java Buildpack as an OCI image + cmds: + - pack buildpack package {{.BP_NAME}}:{{.BP_VERSION}} --path {{.BP_PATH}} --format image + + inspect: + desc: Inspect the packaged buildpack metadata + cmds: + - pack buildpack inspect {{.BP_NAME}}:{{.BP_VERSION}} + + clean: + desc: Remove the local buildpack image + cmds: + - docker rmi {{.BP_NAME}}:{{.BP_VERSION}} || true \ No newline at end of file diff --git a/taskfile/image.yml b/taskfile/image.yml new file mode 100644 index 0000000..0779fa7 --- /dev/null +++ b/taskfile/image.yml @@ -0,0 +1,20 @@ +version: '3' + +tasks: + build-img: + desc: Build the Wolfi Build Image + dir: build-image + cmds: + - docker build -t {{.BUILD_IMAGE}} . + + run-img: + desc: Build the Wolfi Run Image (Distroless) + dir: run-image + cmds: + - docker build -t {{.RUN_IMAGE}} . + + run-img-with-shell: + desc: Build the Wolfi Run Image (with shell for debugging) + dir: run-image + cmds: + - docker build -t {{.RUN_IMAGE}}-shell -f Dockerfile.run . \ No newline at end of file diff --git a/taskfile/util.yml b/taskfile/util.yml new file mode 100644 index 0000000..a5c8cae --- /dev/null +++ b/taskfile/util.yml @@ -0,0 +1,10 @@ +version: '3' + +tasks: + inspect-any: + desc: Show layers for a specific image (usage > task util:inspect-any IMG=name) + vars: + IMAGE: '{{default "taha/maven-fips-app:latest" .IMG}}' + cmds: + - | + paste <(docker inspect {{.IMAGE}} --format='{{range .RootFS.Layers}}{{.}}{{println}}{{end}}') <(docker history {{.IMAGE}} --format "{{.Size}}" | grep -v SIZE | tac) | column -t > 1.txt \ No newline at end of file