diff --git a/.github/workflows/run_cedar_java_reusable.yml b/.github/workflows/run_cedar_java_reusable.yml index 5ef8b699..faa8b85e 100644 --- a/.github/workflows/run_cedar_java_reusable.yml +++ b/.github/workflows/run_cedar_java_reusable.yml @@ -63,3 +63,66 @@ jobs: - name: Generate Java Documentation working-directory: CedarJava run: ./gradlew javadoc + - name: Collect runtime dependencies for Alpine + if: matrix.os == 'ubuntu-latest' + working-directory: CedarJava + run: ./gradlew copyRuntimeDependencies + - name: Upload Alpine smoke artifacts + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: alpine-smoke + path: | + CedarJava/build/libs/*-uber.jar + CedarJava/build/dependencies/*.jar + if-no-files-found: error + + musl-smoke: + name: Authorize on Alpine + needs: build + runs-on: ubuntu-latest + steps: + - name: Download Alpine smoke artifacts + uses: actions/download-artifact@v4 + with: + name: alpine-smoke + path: smoke + - name: Run the uber JAR on musl + run: | + cat > smoke/Smoke.java <<'JAVA' + import com.cedarpolicy.BasicAuthorizationEngine; + import com.cedarpolicy.model.AuthorizationRequest; + import com.cedarpolicy.model.AuthorizationResponse.SuccessOrFailure; + import com.cedarpolicy.model.entity.Entity; + import com.cedarpolicy.model.policy.Policy; + import com.cedarpolicy.model.policy.PolicySet; + import com.cedarpolicy.value.EntityTypeName; + import com.cedarpolicy.value.EntityUID; + import java.util.Map; + import java.util.Set; + + class Smoke { + public static void main(String[] args) throws Exception { + var engine = new BasicAuthorizationEngine(); + var request = new AuthorizationRequest( + new EntityUID(EntityTypeName.parse("User").orElseThrow(), "alice"), + new EntityUID(EntityTypeName.parse("Action").orElseThrow(), "view"), + new EntityUID(EntityTypeName.parse("Resource").orElseThrow(), "doc"), + Map.of()); + for (boolean expected : new boolean[] {true, false}) { + String effect = expected ? "permit" : "forbid"; + var policies = new PolicySet(Set.of( + new Policy(effect + "(principal, action, resource);", "policy0"))); + var response = engine.isAuthorized(request, policies, Set.of()); + if (response.type != SuccessOrFailure.Success + || response.success.orElseThrow().isAllowed() != expected) { + throw new AssertionError("Unexpected " + effect + " response: " + response); + } + System.out.println(effect + ": allowed=" + expected); + } + } + } + JAVA + docker run --rm -v "$PWD/smoke:/smoke:ro" -w /smoke \ + eclipse-temurin:17-alpine \ + sh -ec 'unset CEDAR_JAVA_FFI_LIB; java -cp "libs/*:dependencies/*" Smoke.java' diff --git a/CedarJava/build.gradle b/CedarJava/build.gradle index 45f0b000..6ca3c4fe 100644 --- a/CedarJava/build.gradle +++ b/CedarJava/build.gradle @@ -119,17 +119,21 @@ def compiledLibDir = 'resources/compiled' def rustLibraryTargets = [ 'aarch64-apple-darwin' : 'libcedar_java_ffi.dylib', 'aarch64-unknown-linux-gnu' : 'libcedar_java_ffi.so', + 'aarch64-unknown-linux-musl' : 'libcedar_java_ffi.so', 'x86_64-apple-darwin' : 'libcedar_java_ffi.dylib', 'x86_64-pc-windows-gnu' : 'cedar_java_ffi.dll', - 'x86_64-unknown-linux-gnu' : 'libcedar_java_ffi.so' + 'x86_64-unknown-linux-gnu' : 'libcedar_java_ffi.so', + 'x86_64-unknown-linux-musl' : 'libcedar_java_ffi.so' ] def rustJavaTargets = [ 'aarch64-apple-darwin' : 'macos/aarch64', 'aarch64-unknown-linux-gnu' : 'linux/aarch64', + 'aarch64-unknown-linux-musl' : 'linux_musl/aarch64', 'x86_64-apple-darwin' : 'macos/x86_64', 'x86_64-pc-windows-gnu' : 'windows/x86_64', - 'x86_64-unknown-linux-gnu' : 'linux/x86_64' + 'x86_64-unknown-linux-gnu' : 'linux/x86_64', + 'x86_64-unknown-linux-musl' : 'linux_musl/x86_64' ] def RustVersion = '1.89' @@ -184,6 +188,10 @@ tasks.register('compileFFI') { } exec { workingDir = ffiDir + if (rustTarget.endsWith('-musl')) { + // Rust's static musl CRT does not support cdylibs. + environment 'RUSTFLAGS', '-C target-feature=-crt-static' + } commandLine 'cargo', '+' + RustVersion, 'zigbuild', '--features', 'partial-eval,tpe', '--release', '--target', rustTarget } @@ -219,6 +227,11 @@ tasks.register('cleanFFI', Exec) { commandLine 'cargo', 'clean' } +tasks.register('copyRuntimeDependencies', Copy) { + from configurations.runtimeClasspath + into layout.buildDirectory.dir('dependencies') +} + tasks.register('uberJar', Jar) { dependsOn('compileFFI') group 'Build' diff --git a/CedarJava/src/test/java/com/cedarpolicy/NativeLibraryPackagingTests.java b/CedarJava/src/test/java/com/cedarpolicy/NativeLibraryPackagingTests.java new file mode 100644 index 00000000..4ce1942f --- /dev/null +++ b/CedarJava/src/test/java/com/cedarpolicy/NativeLibraryPackagingTests.java @@ -0,0 +1,45 @@ +/* + * Copyright Cedar Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cedarpolicy; + +import static com.fizzed.jne.OperatingSystem.LINUX; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import com.fizzed.jne.ABI; +import com.fizzed.jne.HardwareArchitecture; +import com.fizzed.jne.NativeTarget; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** The compiled Linux libraries must be discoverable using JNE's resource paths. */ +public class NativeLibraryPackagingTests { + + @ParameterizedTest + @CsvSource({"X64,MUSL", "ARM64,MUSL", "X64,GNU", "ARM64,GNU"}) + public void linuxNativeLibraryIsPackaged(HardwareArchitecture architecture, ABI abi) { + List candidates = NativeTarget.of(LINUX, architecture, abi) + .resolveResourcePaths("jne", "libcedar_java_ffi.so"); + Path compiledResources = Path.of("build", "resources", "compiled"); + assertTrue(candidates.stream().map(compiledResources::resolve).anyMatch(Files::isRegularFile), + "Missing compiled native library for " + architecture + "/" + abi + ": " + candidates); + } +}